注意:java垃圾收集器主要关注的是java堆。
3.2 对象已死吗
判断对象是否已死的方法:
1. 引用计数法
给对象添加一个引用计数器,每当一个地方引用它时,计数器加一,引用失效时计数器减一,则当计数器为0时代表其可以被回收。但是无法解决对象相互循环引用的问题,因此主流的虚拟机都不用这种算法判断对象的死活。
2. 可达性分析算法
通过一些“GC Roots"的对象作为起始点,当一个对象到GC Roots没有任何引用链相连时,证明此对象不可用。
可作为GC Roots的对象包括:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(Native方法)引用的对象。
3. 引用分类
为了更好的回收对象,JDk1.2后将引用分为4种:
强引用:在程序中普遍存在的引用,如:Object o=new Object();
软引用:如果将要发生内存溢出异常才会回收,只有当软引用回收完了内存依然不足才会抛出内存溢出异常。
弱引用:只要进行垃圾回收就会被回收。
虚引用:对对象的生存时间毫无影响,也无法通过虚引用来获取一个对象实例;为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
4. 生存还是死亡
要真正宣告一个对象死亡至少要经过两次标记,第一次是它的引用已经失效,第二次是表示它的finalize()方法已经执行过一次或不需要执行(如果finalize()方法没有被覆盖则判定为不需要执行)。
这意味着在第二次标记中对象有一次“自救”的机会,即通过在finalize()方法中重新与某个存活的对象建立链接即可。但要注意finalize()方法只会被执行一次,当他的引用第二次失效时将会被直接回收,没有自救的机会。
5. 回收方法区
方法区(永久代)的垃圾收集主要针对两个部分:
(1)废弃常量:如“abd”;
(2)无用的类:
当满足下面三个条件时,认为该类是无用的:
该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
3.3 垃圾收集算法
1. 标记-清除算法
就是先标记所有需要回收的对象,在标记完成后统一回收被标记的对象。
最基础的收集算法,其他收集算法都是基于这种思路并对其不足改进。
它的不足有两个:
(1)效率问题:标记和清除过程效率都不高
(2)空间问题:回收后会产生大量的不连续的空间碎片。
2. 复制算法
HotSpot为例:把内存分为Eden(80%)和两块survivor(各占10%),然后每次使用90%,回收时把Eden和survivor中存活的对象一次性复制到另一个survivor中。如果回收时survivor不够,那么多余的对象需要依赖其它内存(这里指老年代)进行分配担保。
3. 标记整理算法
上述的复制算法在对象存活率较高时效率会很低。因此提出了标记-整理算法:
先标记所有对象,然后让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
3.4. HotSpot(Sun公司的虚拟机)的算法实现
(1)枚举根节点
如今都是使用准确式GC,因此虚拟机是有办法直接知道哪些地方存放着对象引用,这样遍历起来就方便多了。在HotSpot的实现中是采用一组称为OopMap的数据结构来达到这个目的的。
(2)安全点
会导致OopMap变化的指令非常多,虚拟机并不会为每一条指令都生成OopMap,而是选定一些特定的位置——”安全点“ 来记录信息,因此程序只有在运行到安全点的时候才能停下来进行GC。
使多个线程都进入安全点的方式有两个:抢先式中断和主动式中断,主流的方式是主动式中断:
即设置一个轮询标志,每当线程运行到安全点时就去检查这个标志,如果为真就自动挂起,因此,垃圾回收时只要设置其为真即可。
(3)安全区域
所谓安全区域就是指引用关系不会发生变化的区域,如果线程执行到了Safe Region,那么JVM就可以不用等它到安全点就进行回收,但是线程在离开safe Region时必须要检查是否完成了根节点枚举(或是整个GC过程),如果没完成就必须等待直到完成才能继续执行。
主要应用在某些线程处于Sleep或Blocked状态时无法自动进入safe point的场景。
3.5. 垃圾收集器
HotSpot的收集器如下(基于1.7):
![](https://img.haomeiwen.com/i13992808/7389aa546e75f682.png)
1. Serial
只使用一个CPU或一条线程去完成垃圾收集工作,在垃圾收集的过程中必须暂停其他所有的工作线程,直到它收集结束。
2. ParNew
serial的多线程版本,其控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial一致,在单线程环境中性能不如serial,除Serial外唯一一个可以和CMS配合的新生代收集器。
3. Parallel Scavenge(拍若雷儿 死该文指)
与其他收集器的不同在于其关注点,CMS等收集器的目标是尽可能缩短停顿时间,而Parallel Scavenge的目标则是达到一个特定的吞吐量(吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)),因为一般追求的停顿时间越短吞吐量就越低,所以停顿时间短的收集器适合需要与用户交互的程序,而高吞吐量的程序适合在后台执行的程序。
可以通过-XX:+UseAdaptiveSizePolicy指令来使系统自动的动态调整各项参数(如eden,survivor比例,新生代大小,晋升老年代对象大小等)
4. Serial Old
Serial的老年代版本,使用标记整理算法 。
和Serial一样,主要用于运行于Client模式的虚拟机,同时也是当CMS失败时的后备预案。
5. Parallel Old
Parallel Scavenge的老年代版本,主要用于在吞吐量优先的场合与Parallel Scavenge配合使用
6. CMS(Concurrent Mark Sweep)
一种以获取最短停顿时间为目标的收集器,基于标记清除算法,回收过程分四步:
初始标记:标记GC Roots能直接关联到的对象(Stop The World)
并发标记:GC Roots Tracing的过程。
重新标记:修正并发标记期间引用发生变动的对象的记录(Stop The World)
并发清除:清除失效引用
优点:并发收集,低停顿
缺点:
对CPU资源很敏感,当CPU总数较低时可能会有很差的表现
无法处理浮动垃圾(在并发清理阶段产生的垃圾),因此相比其他收集器,需要在老年代不太满的时候就触发一次full GC,并且可能出现“Concurrent Mode Failure"失败而导致另一次Full GC的产生(会转变为使用Serial OLD)。
因为采用标记-清除算法,需要定期进行带压缩的Full GC。
7. G1收集器
是一款面向服务端应用的垃圾收集器,特点:
并行与并发:能最大程度上利用多CPU、多核环境的优势
分代收集:依然采用分代概念
空间整合:G1在局部上采用”复制“算法,整体采用”标记整理“,因此不会产生空间碎片。
可预测的停顿:使用者可以指定在一段时间内的垃圾收集时间不得超过多少。
原理:把内存分为多个大小相等的独立区域(region),G1跟踪各个region里面的垃圾堆积的价值大小(回收所需时间越短,可腾出空间越大则价值越高),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region,因此能在有限的时间里获取尽可能高的收集效率。
G1收集器的运作步骤:
1. 初始标记
2. 并发标记(只有并发标记不会停用户线程)
3. 最终标记
4. 筛选回收
3.6. 内存分配与回收策略
1. 对象优先在内存分配
多数情况下,对象在新生代Eden区中分配。当Eden区中空间不足时虚拟机会发起一次minor GC
2. 大对象直接进入老年代
通过-XX:PretenureSizeThreshold可以指定一个门限,内存超过这个值的对象将被直接分配到老年代。
3. 长期存活的对象将进入老年代
每个对象都有一个年龄计数器,对象每在Survivor中”熬“过一次MinorGC,计数器就加一,但超过设定限度就会被放入老年代(默认15),晋升阙值可以通过-XX:MaxTenuringThreshold来设置。
4. 动态对象年龄判定
如果在Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一般,那么年龄大于等于该年龄的对象就可以直接进入老年代,无需达到晋升阙值。
5. 空间分配担保
在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于则认为是安全的,否则会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于将进行一次有风险的minor GC,否则进行Full GC。
网友评论