二、垃圾回收与内存分配, 来自《深入理解Java虚拟机》

所有内容均来自《深入理解Java虚拟机》, 部分比较拗口的内容用自己能明白理解的白话转述了下, 书本讲的非常详细, 这里只做简单引导试写出来, 供快速了解大致内容, 如果对原书知识理解有错, 请大家指出, 必虚心接受

1. 垃圾回收与内存分配

1.1 可达性分析算法

这个算法的基本思路就是通过一系列被称为GC Roots的根对象作为起始节点, 从这些节点开始, 根据引用关系向下搜索, 搜索过程走过的路径称为"引用链", 如果某个对象到根节点没有引用链相连接, 那么则判断这个对象已死.

1.1.1 那些可以作为GC Roots对象
  • 在虚拟机栈中引用的对象, 比如各个线程被调用的方法堆栈中的参数,局部变量,临时变量等等
  • 在方法区中类静态属性引用的对象, 比如Java类的引用类型静态变量
  • 在方法区中类常量引用的对象, 例如字符串常量池里的引用
  • 本地方法中JNI引用的对象
  • Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象, 比如NullPointException, OutOfMemeoryError等, 还有系统类加载器
  • 所有被同步锁(Synchronized)持有的对象
  • 反映Java虚拟机内部情况的JMXBean, JVMTI中注册的回调, 本地代码缓存等

即使可达性分析判断到一个对象是不可达的, 也不是非死不可的, 要真正死亡还要经历两阶段标记过程, 上面判断到没有和GC Roots根对象关联是第一次被标记, 之后还要进行一次筛选, 条件是是否有必要执行finalize()方法, 如果对象没有覆盖finalize()方法, 或者已经执行过一次finalize()方法那么虚拟机都判断为没有必要执行.如果对象在finalize() 方法里自救成功, 那么还可以逃离一次被回收.

1.2 垃圾收集算法

1.2.1 分代收集理论
  • 弱分代假说: 绝大多数对象都是朝生夕灭的
  • 强分代假说: 熬过越多次垃圾回收过程的对象就越难死亡

这两个假说表明垃圾收集器应该将Java堆划分出不同的区域, 然后将对象依据其年龄分配到不同的区域之中去

  • 跨代引用假说: 跨代引用相对于同代引用来说仅占极少数

存在相互引用关系的两个对象应该倾向于同生同灭, 举个例子: 如果某个新生代中的对象被老年代引用,那么该引用会导致新生代对象在垃圾收集时仍得以存活, 进而晋升到老年代,这样跨代引用也就被消除了

1.2.2 垃圾收集算法
  1. 标记-清除

原理: 首先标记出所有要回收的对象, 在标记完成后统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收未被标记的对象, 标记过程就是对象是否属于垃圾的判定过程

缺点:

1.执行效率不稳定,如果需要回收的对象很多,那么标记和清除这两个过程的效率会随着对象的数量增长而下降
2.内存碎片化严重, 标记和清除会导致大量的不连续内存

  1. 标记-复制

原理:可以将内存划分为两块, 每次只是用其中的一块, 当这一块儿用完了, 可以将存活的对象复制到另外一块上面, 然后再把使用过的这一块内存全部清理掉

缺点也很显然就是太浪费空间了

  1. 标记-整理

原理: 类似于标记清除, 但是它标记后不是清除, 而是让存活的对象向空间一端移动, 然后直接清理掉边界意外的内存

1.3 垃圾收集器

对一些单词特地放出了美式发音的音标, 谁说程序员英语不行的, 必须给他秀起来. 书本里对垃圾收集器做了非常详细的介绍, 从经典垃圾收集器到低延迟垃圾收集器等等, 但是很多东西还没有很好的理解, 所以这里只简单介绍了几款经典垃圾收集器的概念, 详细内容请阅读原书

1.3.1 经典垃圾收集器
  • Serial ['sɪriəl]

HotSpot虚拟机运行于客户端模式下的默认新生代垃圾收集器,在单线程下它比其他垃圾收集器更加简单高效, 而且在资源有效的情况下, 他是额外内存消耗最小的

  • ParNew

实际上是Serial收集器的多线程版本, 除此之外,他的行为以及控制参数, 收集算法, 对象分配规则等都与Serial完全一致

在JDK5发布后推出了CMS收集器, 他是第一个支持并发的垃圾收集器, 而ParNew也是激活CMS后的默认新生代收集器

不过在G1后, G1是一个面向全堆的垃圾收集器, 不再需要其他新生代垃圾收集器配合, 所以自JDK9开始, ParNew加CMS的组合就不再是官方推荐的服务端模式下的收集器解决方案了

  • Parallel Scavenge [ˈpærəlel ˈskævɪndʒ]

也叫吞吐量优先收集器, 他是基于标记-复制算法实现的新生代多线程垃圾收集器, 他与其他收集器不同的是, CMS等收集器的关注点是在尽可能的缩短垃圾收集时用户线程的停顿时间, 而parallel Scavenge的目标是达到一个可控制的吞吐量

即处理器用于运行用户代码的时间与处理器总消耗时间的比值,吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾回收时间)

它提供了两个参数-XX:MaxGCPauseMillis参数允许设置一个大于0的毫秒数, 收集器将保证内存回收花费的时间不超过用户设定的值, 垃圾收集停顿时间的缩短是以牺牲吞吐量和新生代空间为代价的, 回收时间减小了, 吞吐量也减少了, 回收也将变得更加频繁

还有一个参数是-XX:GCTimeRatio, 他是一个大于0小于100的整数, 也就是垃圾收集时间占总时间的比例, 相当于吞吐量的倒数, 默认值为99, 即允许最大百分之1的垃圾时间收集

  • Serial Old

是Serial收集器的老年代版本, 他是基于标记-整理的单线程收集器, 主要供客户端模式下的HotSpot虚拟机使用

  • Parallel Old

是Parallel Scavenge收集器的老年代版本, 是JDK1.6提供的基于标记-整理并支持多线程并发的收集器

  • CMS

是基于标记-清除算法实现的以获取最短停顿时间为目标的收集器, 比较适用于互联网网站或者基于B/S系统的服务端上, 这类应用通常比较关注响应速度, 希望停顿时间尽可能的短

CMS的运作过程分为四部分, 初始标记, 并发标记, 重新标记, 并发清除. 其中初始标记重新标记这两个步骤仍然需要停止用户线程,

初始标记就是简单标记一下哪些对象与GC Roots直接关联, 并发标记是从第一次标记的对象开始遍历整个对象图的过程, 这一次是与用户线程并发执行的, 重新标记则是为了修正在并发标记过程中因用户线程活动导致变更的那一部分对象,所以重新标记也需要停止用户线程, 最后是并发清除已经死亡的对象

CMS收集器对处理器资源非常敏感, CMS默认启动的回收线程数是(处理器核心数量+3)/4, 也就是说核心处理器在四个或者四个以上, 并发回收时垃圾收集器线程只占用不少于百分之25的处理器运算资源, 但是当处理器核心数量不足4个时CMS对用户程序的影响就可能变得很大

  • Garbage First(G1)

G1是一款主要面向服务端应用的垃圾收集器, 在JDK9中已经取代Parallel Scavenge加Parallel Old组合的垃圾收集器成为服务端模式下的默认垃圾收集器

在G1之前, 所有的垃圾收集器都是针对整个新生代(Minor GC)或者老年代(Major GC)或者全堆(Full GC)实现的, 而G1则是可以面向堆内任何部分来组成回收集进行回收, 他的回收标准也不再是哪个分代, 而是那块内存中垃圾最多, 回收效益最大, 他将堆内存划分为多个大小相等的独立区域Region, 每个Region都可以根据需要扮演Eden空间, Survivor空间以及老年代, 收集器能根据不同角色的Region采用不同的策略去处理.

1.4 内存分配与回收策略

在经典分代设计下, 新生对象一般会分配在新生代中, 少数情况下例如对象大小超过一定阈值也会直接分配在老年代, 对象分配的规则并不是固定的, 这需要根据使用的垃圾收集器决定, 以下内容基于Serial加serial Old客户端默认收集器组合下的内存分配和回抽策略

  • 大多数情况下, 对象在新生代Eden中分配, 当Eden区没有足够空间进行分配时, 虚拟机将进行一次Minor GC
  • 大对象直接进入老年代, 虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配, 防止对象在Eden和两个Survivor区之间来回复制
  • 长期存活的对象将进入老年代, 对象通常在Eden里诞生, 但是如果经历过第一次Minor GC后仍然存活, 并且能被Survivor容纳的话, 该对象就会被移动到Survivor中, 并将其年龄设置为1岁, 当他的年龄增加到一定程度(默认为15), 就会晋升到老年代, HotSpot虚拟机并不要求对象一定要达到15或者-XX:NaxTenuringThreshold设置的大小才能晋升老年代, 如果在Survivor空间中低于或等于某年龄的所有对象大小总和超过了Survivor空间的一般, 那么年龄大于等于该年龄的对象就可以直接进入老年代.
1.4.1 空间分配担保

在发生Minor GC之前, 虚拟机必须先检查老年代最大可用的连续空间是否大于新生代多有对象总空间, 如果这个条件成立, 那么这一次Minor GC就可以确保是安全的. 如果不成立, 则虚拟机会先检查-XX:HandlePromotionFailure参数设置的值是否允许担保失败, 如果是允许的, 那么会继续检查老年代最大可用的连续空间是否大于历代晋升到老年代对象的平均大小, 如果大于, 将尝试进行一次Minor GC 如果小于或者-XX:HandlePromotionFailure设置为不允许冒险, 那么就要进行一次Full GC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值