深入理解Java虚拟机(四)垃圾收集器和内存分配策略
如何判断对象已死?
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1,;当引用失效时,计数器值减1,任何时刻计数器都为0的对象就是不可能再被使用的;
缺点:很难解决对象之间的相互循环引用的问题;
如:
objA.instance=objB;
objB.instance=objA;
两个对象互相引用者对方,导致他们的计数都不为0,于是引用计数算法无法通知GC收集器回收它们;
根搜索法
通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象GCRoots没有任何引用链相连时,则证明此对象是不可用的;
可作为GC Roots的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象;
- 方法区中的类静态属性引用的对象;
- 方法区中的常量引用的对象;
- 本地方法栈中JNI的引用对象;
引用
- 强引用:只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象;
- 软引用:还有用但是并非必需的对象,在系统将要发生内存溢出异常前,将会把这些对象列进回收范围之中并进行第二次回收。
- 弱引用:强度比软引用还弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前;
- 虚引用:最弱的一种,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例,设置他的目的就是希望在这个对象被收集器回收时收到一个系统通知;
垃圾收集算法
标记-清除算法
首先标记处所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象;
缺点:效率问题;空间问题,标记清除之后会产生大量不连续的内存碎片,碎片太多可能会导致下一次提前的垃圾收集动作;
复制算法
它将可用内存安容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了就将还存活着的对象复制到另外一块里,然后再把已使用过的内存空间一次清理掉;
大多采用这种算法来回收新生代,将内存分为一块较大得Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还活着的对象一次性拷贝到另一块Survivor中,最后清理掉Eden和刚才用过的Survivor空间;Eden:Survivor为8:1;如果另外一块Survivor没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代;
标记-整理算法
根据老年代的特点,提出了标记-整理法,标记过程和标记-清除法一样,但后续步骤是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存;
分代收集算法
根据对象的存活周期的不同将内存划分为几块,一般分为新生代和老年代,新生代中每次垃圾收集时都发现有大量对象死去,只有少量存活,选用复制算法;老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记清理或标记整理算法来进行回收;
垃圾回收器
如果两个垃圾回收器之间有连线,则表示可以搭配使用;
Serial收集器
JDK1.3之前是虚拟机新生代收集的唯一选择,单线程收集器,它进行垃圾回收时必须暂停其它所有工作线程(Stop the World),直到收集结束,虽然也有更好的收集器相继出现,但是它也有自己的优点,简单高效;
ParNew收集器
其实就是Serial收集器的多线程版本,就是使用多个线程来收集;
Parallel Scavenge收集器
新生代收集器,使用复制算法,并且是并行的多线程收集器。Parallel Scavenge更关注于达到一个可控制的吞吐量(吞吐量=CPU运行代码的时间与CPU总消耗时间的比值)。
参数-XX:+UseAdaptiveSizePolicy
这是一个开关参数,打开之后就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数,虚拟机会根据当系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大吞吐量,这种调节方式称为GC自适应的调节策略。
Serial Old收集器
是Serial收集器的老年代版本,他也是一个单线程收集器,使用标记整理算法。
Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。在注重吞吐量及CPU资源敏感的场合,都可以优先考虑parallel Scavenge加Parallel Old收集器。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿实践为目标的收集器,重视服务的响应速度,基于标记清除算法。
- 运作过程
- 初始标记:仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;
- 并发标记:进行Roots Tracing的过程;
- 重新标记:为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录;
- 并发清除
- CMS收集器的缺点:
- 对CPU资源非常敏感;
- 无法处理浮动垃圾;
- 收集时会产生大量空间碎片;
G1收集器
- 基于标记整理算法实现,不会产生大量空间碎片;
- 可以精确的控制停顿;
- 在基本不牺牲吞吐量的情况下完成低停顿的内存回收;
G1将整个java堆划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。
内存分配与回收策略
对象优先分配在Eden
当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代
大对象是指需要大量连续内存空间的java对象,这样做避免在Eden区以及两个Survivor区之间发生发亮内存拷贝。
长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能够被Survivor容纳的话,将被移动到Survivor空间,并将对象年龄设为1,对象在Survivor中每熬过一次Minor GC年龄就增加1岁,当它的年龄到一定程度,默认为15时,就会被晋升到老年代。
动态对象年龄判定
并不是总要求对象年龄必须达到MaxTenuringThreshold才晋升老年代,如果在Survivor空间中相同年龄对象大小的总和大于Survivor空间的一半,年龄大于或或等于该年龄的对象就可以直接进去老年代。
空间分配担保
在发生Minor GC时,虚拟机会检测之间每次晋升到老年代的平均大小是否都大于老年代剩余空间的大小,如果大于,则改为直接进行一次Full GC,如果小于,则查看参数是都允许担保失败,如果允许那只会进行Minor GC,否则也要改为进行一次Full GC;