垃圾回收
如果Java虚拟机创建的对象无论是否被使用都不会得到释放从而占满了所有的内存空间,那么在创建新的对象时,将会产生OutOfMemoryException。
所以Java虚拟机有一套自动内存管理机制,叫做垃圾回收机制。
对象的是否需要进行回收,需要进行判断,判断的方式有两种:
引用计数法
当对象每被引用一次时,其引用计数器加一;当其中一个引用失效时,其引用计数器减一,当对象的引用计数器值为零时,标记此对象可以被清除。
优点:
- 判定效率高
缺点:
- 当两个对象循环引用时,计数器便无法标记为0,于是导致两个对象不会被回收。
可达性分析
从一个根节点向下搜索,搜索的路径被称作引用链,当一个对象到根节点没有任何引用链相连时,则证明此对象是不可达的,标记次对象可以被清除。
优点:
- 更加精确和严谨,可以分析出循环数据结构相互引用的情况
缺点:
- 实现比较复杂
- 需要分析大量数据,消耗大量时间
- 分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题)
可达性分析法中的根叫做GC Roots。
在Java中,能够当作GC Roots的有:
- VM Stack中Stack Frame的局部地变量表引用的对象
- 方法区/元空间中的静态属性引用的对象
- 方法区/元空间中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
目前所有的Java虚拟机中都采用可达性分析法来判断对象是否需要被清除。
垃圾回收算法
标记-清除算法
首先遍历GC Roots标记出存活对象,然后再次遍历所有对象,将没有被标记为存活的对象清除。
缺点:效率低,且容易产生内存碎片,如果新的对象无法找到合适的连续空间进行存储,将会触发GC。
复制算法
将内存区等分为两个区域,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的。当活动区间被占满时,将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。在空间转换的同时垃圾对象被一次性清除。
优点:无内存碎片;
缺点:可用内存减少一半,降低性能。
标记-整理算法
首先遍历GC Roots标记出存活对象,然后再次遍历所有对象,移动存活对象按照内存地址依次排列,最后,将末端内存地址以后的内存全部回收。
优点:
- 产生较少的内存碎片
- 消除了复制算法中内存减半额高额待见
缺点:效率低
垃圾收集器
Serial GC
单线程串行垃圾收集器,新生代使用复制算法,老年代使用标记-整理算法。
Stop The Word时间长。
ParNew GC
多线程串行垃圾收集器,Serial GC的多线程版本,新生代使用复制算法,老年代使用标记-整理算法。
Stop The Word时间相对Serial GC较短,但在单线程环境中不一定表现比Serial GC更好。
Parallel Scavenge GC
并行清除垃圾收集器,是一个新生代收集器,采用复制算法,其关注点并非尽可能的降低STW时间,而是旨在达到一个可以控制的吞吐量。
吞吐量指的是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即 吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
Serial Old GC
单线程老年代串行垃圾收集器,Serial GC的老年代版本,是一个老年代收集器,老年代采用标记-整理算法。
Parallel Old GC
并行清除老年代垃圾收集器,Parallel Scavenge GC的老年代版本,是一个老年代收集器,使用多线程和标记-整理算法。
Concurrent Mark Sweep GC
并发标记清除垃圾收集器,是使用标记-清除算法的老年代收集器。
工作时主要分为四个步骤:
- 初始标记(单线程,STW)
- 并发标记(同用户线程一起工作)
- 重新标记(并行,STW)
- 并发清除(同用户线程一起工作)
问题:
- CPU资源敏感
- 无法处理浮动垃圾
- 易产生内存碎片
Garbage First GC
简称G1垃圾收集器,非实时收集器。
特点:
- 并行并发
- 分代收集
- 空间整合
- 可预测的停顿
工作时分为四个步骤:
- 初始标记(单线程,STW)
- 并发标记(同用户线程一起工作)
- 最终标记(并行)
- 筛选回收(并行)
在G1垃圾收集器中,采用了全新的内存布局,将Heap分成了一块块大小相等的Heap Region,一般在2000多块,Region在逻辑上时连续的,每个Region都会被打上唯一分代标记(eden,survivor,old)。在逻辑上,eden regions构成Eden空间,survivor regions构成Survivor空间,old regions构成了old 空间。
在并发标记时,G1会确定堆中对象的存活情况,标记完成后,G1知道哪些regions空闲空间多(可回收对象多),优先回收这些空的regions,释放出大量的空闲空间。
内存分配策略
- 对象优先在 Young Gen 的 Eden Space 分配
- 大对象直接进入 Old Gen
- 长期存活对象进入 Old Gen (对象在Eden区出生且经过第一次GC后,仍然存活,将其放入Survivor的活动区域中,年龄记为1,再次GC后,存活的对象从活动区域转换到原先的空闲区域,并且年龄加一,如此反复,直至对象年龄达到指定年龄,进入Old Gen)
- 动态对象年龄判定(当Survivor中相同年龄所有对象的大小已经达到了或者超过了Survivor的一半,那么年龄大于等于该年龄的对象可以直接进入Old Gen)
- 空间分配担保