内存分配和回收原则
1. 对象优先在Eden区分配
Eden区不够时,发起一次Minor GC(Young GC)
2. 大对象直接进入老年代
不同的垃圾回收器有不同的算法
3. 长期存活的对象将进入老年代
使用年龄计数器
大部分情况,对象都会首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间(s0 或者 s1)中,并将对象年龄设为 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。
此后,每进行一次MinorGC,年龄+1,到15(默认为15,最高也是15,但也分垃圾收集器),放到老年代
GC有几种
-
部分收集(Partial GC):
-
新生代收集(Minor GC/ Young GC):只对新生代进行垃圾收集;
-
老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集
-
混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
-
整堆收集 (Full GC):收集整个 Java 堆和方法区。
什么是空间分配担保
空间分配担保是为了确保在Minor GC之前老年代本身还有容纳新生代所有对象的剩余空间
死亡对象判断方法
引用计数法
有引用,计数+1,否则-1.
无法解决循环引用,因此不会用这种方法
可达性分析算法
通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
哪些对象可以作为Root?
-
虚拟机栈(栈帧中的局部变量表)中引用的对象
-
本地方法栈(Native 方法)中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
所有被同步锁持有的对象
-
JNI(Java Native Interface)引用的对象
被确认可以回收的对象还会经历两次标记,才会回收:
-
第一次会检查finalize方法是否有必要执行(没覆盖或者被调用过就没必要)
-
需要执行就会第二次标记,如果还是没有引用到Root引用链上,就回收
垃圾收集算法
标记-清除算法
最基础的算法,标记后,清除
复制算法
改进了标记清除,将内存分成大小相同的两块,每次回收完之后把还存活的对象复制到另一块,解决碎片问题。
导致可用内存变小、不适合老年代,存活对象数量大时,性能差。
标记-整理算法
标记完,将存活对象往一段移动,边界外的清理。
适合老年代,频率不高的场景
分代收集算法
不同代的回收用不同的策略。
比如老年代用标记整理,新生代用复制
垃圾收集器
-
Serial收集器:串行、新生代标记复制、老年代标记整理
-
ParNew: Serial的多线程版本
-
Parallel Scavenge: JDK1.8默认,与ParNew很像,更关注CPU执行效率,吞吐量
-
Serial Old: Serial老年代版本
-
Parallel Old: Parallel Scavenge老年代版本
-
CMS: 最短回收停顿。多线程标记清除,有三次标记,并发标记阶段会监听跟踪用户线程发生引用更新的地方
-
G1:高吞吐、短停顿。并发、标记整理,后台维护优先列表,根据允许的收集时间,优先选择回收价值最大的区域
-
ZGC:与ParNew和G1类似,牺牲了吞吐量,但暂停时间控制在几毫秒,且暂停时间不受堆内存大小影响
引用
文章参考了Java Guide JVM相关文章,做了整理和重点提取。