深入理解Java虚拟机阅读笔记(二.2):垃圾收集器与内存分配策略
第二部分 自动内存管理机制
第三章 垃圾收集器与内存分配策略
3.1 概述
栈帧分配多少内存是在类结构确定下来就已知的,在运行期可能给予部分优化,而堆和方法区则在运行期间才确定对象和分配(不同的实现类大小不同,不同程序分支不同),其中堆往往被划分成以下两部分:
- 新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。触发条件为Eden区满。
- 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。触发条件为:
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 方法区,老年代空间不足
- 满足动态担保机制条件时(下面有详细讲解)
3.2 对象已死嘛
- 判断算法:
- 计数算法:引用加一,引用失效减一,问题在于对于循环引用(A和B互为对方的成员变量,则永远有一个引用+1)无法回收
- 可达性分析算法:通过GC Roots开始进行可达性分析,引用存在则可达,仍存活
- 再谈引用:
- 强引用:
Object obj = new Object()
,只要存在,一定不会被回收 - 软引用:
SoftReference
类来实现,还有用但非必需,在发生内存溢出异常之前会进行回收 - 弱引用:
WeakReference
类,只要垃圾收集器工作,就会被回收 - 虚引用/幽灵引用:
PhantomReference
,不可用其取得实例,唯一作用是在被回收时收到通知
- 强引用:
- 生存还是死亡(新生代)
- 没有引用链时检查,如果
finalize()
被覆盖且未被调用,则会执行该方法(但不等待运行结束,防止死循环) - 如何拯救自己:和引用链上任何一个对象关联即可,譬如把自己赋值给某个类变量或对象的成员变量,
对象=this
- 任何对象的
finalize()
方法都只会被调用一次,不推荐使用,运行代价高且不确定性大(无法保证各个对象的调用顺序)
- 没有引用链时检查,如果
- 回收方法区(永久代):回收“性价比”较低
- 废弃常量
- 无用的类:没有实例+类加载器(
ClassLoader
)已被回收+无法在任何地方通过反射访问(java.lang.Class
对象没有被引用之处)。虚拟机提供参数-Xnoclassgc
进行控制是否回收。
3.3 垃圾收集算法
- 标记-清除算法:先标记(上文讲的过程),再清除
- 效率问题
- 空间问题:产生内存碎片
- 复制算法:分为两块,用完时将存活的对象复制到另一半,清除未使用的
- 内存浪费:二八原则,只有10%的Survivor空间即可,剩下的90%称为Eden
- Survivor没有足够空间时将通过分配担保(后文讲述)进入老年代
- 标记-整理:即将存活对象在内存中对齐
- HotSpot的分代收集:
- 新生代:大批死去,复制算法
- 老年代:空间少,存活多,标记-清理 or 标记-整理
3.4 HotSpot算法实现:保证虚拟机的高效运行
3.5 垃圾收集器
- 是内存回收的具体实现,JVM并没有规定,故而可以采用不同的垃圾收集器,其中HotSpot提供了7种作用于不同分代的收集器,区域表示它负责的分代,连线表明它们可以搭配使用。需要明确,没有最好的or万能的收集器,只是在不同情况下更加适用而已。 这里我不再给出每种垃圾收集器的具体实现了,如果感兴趣可以留个言或者自己查阅书籍~
- 理解GC日志:
- 数字:发生时间,从JVM启动以来经过的秒数
Full GC/GC
:垃圾收集器的停顿类型DefNEW/Tenured
:垃圾收集器类型3324K->152K(11904K)
:GC前的容量和GC后的容量,其中括号中代表的是总容量,后面的数字反应的是堆的使用前后容量和总容量0.00025925secs
:占用的时间(秒)
- 垃圾收集器参数总结:
3.6 内存分配与回收策略
- 优先在新生代的Eden(即复制法改进中的90%)上分配:
- 新来的对象分配于Eden
- Eden满时,将存活的对象移植Survivor,死亡的对象清楚
- 如果Survivor空间不够,将通过分配担保机制提前转移至老年代(如下图的allocation1~3)
- 大对象直接进入老年代:
- 大对象:需要大量连续内存空间的Java对象,譬如字符串,数组
- 使用
-xx:PretenureSizeThreshold
参数,避免Eden和Survivor区之间的频繁复制(为了满足连续空间装下大对象,不得不频繁进行GC)
- 长期存活的对象直接进入老年代:
- 年龄:每在Survivor中“熬过”(即还存活着)一次Minor GC,年龄+1
- 使用
-xx:MaxTenuringThreshold
参数,代表的是对象在Survivor中的年龄阈值,超过阈值的被移动到老年代 - 动态对象年龄判定:当Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一半,此时不论是否达到年龄阈值,都将大于和等于这个相同年龄的对象移植老年代
- 空间分配担保(避免过于频繁的Full GC):
- 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果是,则一定安全(即使新生代全部被判定移植老年代也可以),此时直接进行Minor GC
- 不大于时,根据
HandlePromotionFaliure
查看是否允许担保失败,不允许意味着必须进行Full GC,否则将尝试担保(如果这个值为true,代表着JVM说,我允许在这种条件下尝试执行Minor GC,出了事我负责) - 担保:检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,大于说明这次Minor GC很有可能不会超出空间,也就是成功(尽管这里也有可能失败,此时称之为担保失败,只好重新发起一次Full GC)。否则只能进行Full GC啦。
- 另外,JDK 6Update 24之后,只要老年代的连续空间大于新生代对象总大小或历次晋升的平均大小就会进行minorGC,否则进行Full GC