垃圾收集 & 内存分配
一、对象已死
1.1 引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的[存在问题]。
1.2 可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
图1-1 可达性分析 可作为GC Roots的对象包括:1)虚拟机栈中引用的对象 2)方法区中类静态属性引用的对象 3)方法区中常量引用的对象 4)本地方法栈中JNI引用的对象。引用分为强引用、软引用、弱引用、虚引用。
宣告一个对象死亡,至少要经历两次标记过:对象进行可达性分析后若没有与GC Roots相连接的引用链,则进行第一次标记并且进行一次筛选,筛选条件是对象是否有必要执行finalize()方法。若对象未覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,则视为没必要执行。对象可在finalize()中进行逃脱。(一个对象的finalize()方法最多只会被系统自动调用一次)
方法区(永久代)垃圾收集主要回收:1)废弃常量 2)无用的类。
二、垃圾收集算法
2.1 标记-清除算法(老年代)
标记 & 清除过程:标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
存在问题 :1) 效率低 2)产生内存碎片
2.2 复制算法(新生代)
将可用内存按容量划分为相等的两块,每次只使用其中一块。当这一块的内存用完了,就将活着的对象复制到另外一块,然后清除已使用的内存空间。
图2-2 复制算法 新的复制算法,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。在回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和使用过的Survivor空间。默认Eden和Survivor的比例为8:1。
当存活的对象超过10%(Survivor空间不够),需要依赖老年代进行分配担保。
2.3 标记-整理算法(老年代)
步骤同标记-清除算法,让所有存活的对象都向一端移动,直接清理掉端边界以外的内存。
图2-3 标记-整理算法2.4 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于标记-整理算法实现。分为4个步骤:
1. 初始标记(需要终止其他线程)
2. 并发标记(可以与线程一起工作)
3. 重新标记(需要终止其他线程)
4. 并发清除(可以与线程一起工作)
2.5 零碎知识点
1)按照对象存活周期将Java堆分为:老年代和新生代。
2)垃圾收集器搭配使用,作用于不同的对象,垃圾收集器分为单线程/多线程。
3)枚举根节点 & 安全点 & 安全区域相关概念。
三、内存分配与回收策略
内存主要分配在新生代的Eden区,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况会直接分配在老年代,分配规则取决于使用的垃圾收集器组合,虚拟机中内存参数设置。
3.1 对象优先在Eden分配
新生代GC(Minor GC):指发生在新生代的垃圾收集动作。
老年代GC(Major GC/Full GC):指发生在老年代的GC,经常会伴随至少一次Minor GC(并非绝对)。
3.2 大对象直接进入老年代
大对象:需要大量连续内存空间的Java对象 ,比如很长的字符串以及数组。
3.3 长期存活的对象将进入老年代
虚拟机给每个对象定义了对象年龄(Age)计数器。对象在Eden出生并经过一次Minor GC后仍存活,并且能被Survivor容纳,移动Survivor空间,对象年龄设为1。在Survivor每熬过一次Minor GC,年龄加1,超过年龄阈值(默认为15)进入老年代。
3.4 动态对象年龄判断
如果在Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
3.5 空间分配担保
发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果大于,Minor GC确保安全,否则检查HandlePromotionFailure设置值是否允许担保失败。如果允许,继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试一次Minor GC。若小于或HandlePromotionFailure设置不允许冒险,则进行一次Full GC。