3.1概述
垃圾收集器关注Java堆和方法区的内存回收。
3.2对象已死吗?
判断方法:1.引用计数算法2.可达性分析算法
3.2.1引用计数算法
给对象添加引用计数器,当有地方引用它时就加1,引用失效就减1,为0时就认为对象不再被使用可回收。
简单,但是无法解决对象间循环引用的问题
3.2.2可达性分析算法
给对象添加引用计数器,当有地方引用它时就加1,引用失效就减1,为0时就认为对象不再被使用可回收。
可以作为GC Roots对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(即一般说的Native方法)引用的对象;
3.2.3再谈引用
JDK1.2以后,Java 提供了四种强度不同的引用类型。
Java 提供了四种强度不同的引用类型。
1.强引用
被强引用关联的对象不会被回收。
使用 new 一个新对象的方式来创建强引用。
Object obj = new Object();
2.软引用
被软引用关联的对象只有在内存不够的情况下才会被回收。
使用 SoftReference 类来创建软引用。
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
3.弱引用
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
使用 WeakReference 类来创建弱引用。
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
4.虚引用
又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。
为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。
使用 PhantomReference 来创建虚引用。
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;
3.2.4生存还是死亡
对象被清理要至少进行两次标识。第一次就是可达性分析,第二次是在执行finalize()之后的可达性分析。
finalize()方法是对象逃脱死亡命运的最后一次机会。
如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何一个对象建立关联即可。
建议大家完全可以忘掉Java语言中有这个方法的存在。
3.2.5回收方法区
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
类可回收需要满足以下三个条件:
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
是否对类回收,HotSpot通过配置参数实现,以此来保证永久代不会回收。
3.3垃圾收集算法
3.3.1标记-清除算法
首先标记出所有需要回收的对象,然后统一回收所有被标记的对象;缺点是效率不高且容易产生大量不连续的内存碎片;
![](https://i-blog.csdnimg.cn/blog_migrate/dafde9f5e96c1c7adc5fd57d9167a1de.png)
3.3.2复制算法
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
主要不足是只使用了内存的一半。
3.3.3标记-整理算法
首先标记出所有需要回收的对象,然后让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
3.3.4 分代收集算法
一般把Java堆分新生代和老年代,在新生代用复制算法,在老年代用标记-清理或标记-整理算法,是现代虚拟机通常采用的算法。
3.4HotSpot的算法实现
3.4.1枚举根节点
3.4.2安全点
3.4.3安全区域
3.5垃圾收集器
- 垃圾收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现。
- 这里讨论JDK 1.7 Update 14之后的HotSpot虚拟机(此时G1仍处于实验状态),包含的虚拟机如下图所示(存在连线的表示可以搭配使用):
![](https://i-blog.csdnimg.cn/blog_migrate/38d8524b7dfd586784f6fcda4dc93a24.png)
在讨论垃圾收集器的上下文语境中:
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态;
并发(Concurrent):指用户线程与垃圾收集线程同时执行,用户线程在继续执行而垃圾收集程序运行在另外一个CPU上;
3.5.1Serial收集器
单线程,新生代,复制算法
现在依然是虚拟机运行在Client模式下的默认新生代收集器,主要就是因为它简单而高效(没有线程交互的开销)。
3.5.2ParNew 收集器
多线程,新生代,复制算法
是许多运行在Server模式下虚拟机首选的新生代收集器,重要原因就是除了Serial收集器外,只有它能与CMS收集器配合工作;
3.5.3Parallel Scavenge 收集器
多线程,新生代,复制算法
与CMS关注于尽可能缩短垃圾收集时用户线程停顿时间不同,PS的目标是达到一个可控制的吞吐量;
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
支持GC自适应的调节策略;
3.5.4Serial Old 收集器
单线程,老年代,标记-整理算法
主要是给Client模式下的虚拟机使用的;
在Server模式下主要是给JDK 1.5及之前配合Parallel Scavenge使用或作为CMS收集器的后备预案;
3.5.5Parallel Old 收集器
多线程,老年代,标记-整理算法
3.5.6CMS 收集器
是一种以获取最短回收停顿时间为目标的收集器
基于标记-清除 算法实现的,主要包括4个步骤:
初始标记(STW-stop the world,只是初始标记一下GC Roots能直接关联到的对象
并发标记(执行GC RootsTracing,耗时比较长)
重新标记(STW,修正并发标记期间因用户程序继续导致变动的那一部分对象标记)
并发清除(耗时较长);
CMS的缺点:
CMS收集器对CPU非常敏感(占用部分线程及CPU资源,影响总吞吐量)
无法处理浮动垃圾(默认达到92%就触发垃圾回收)
大量内存碎片产生(可以通过参数启动压缩);
3.5.7G1收集器
一款面向服务端应用的垃圾收集器,后续会替换掉CMS垃圾收集器;
特点:
并行与并发(充分利用多核多CPU缩短Stop-The-World时间)
分代收集(独立管理整个Java堆,但针对不同年龄的对象采取不同的策略)
空间整合(基于标记-整理)
可预测的停顿(将堆分为大小相等的独立区域,避免全区域的垃圾收集);
G1分为几个步骤:
1.初始标记(标记一下GC Roots能直接关联的对象并修改TAMS值,需要STW但耗时很短)
2.并发标记(从GC Root从堆中对象进行可达性分析找存活的对象,耗时较长但可以与用户线程并发执行)
3.最终标记(为了修正并发标记期间产生变动的那一部分标记记录,这一期间的变化记录在Remembered Set Log里,然后合并到Remembered Set里,该阶段需要STW但是可并行执行)
4.筛选回收(对各个Region回收价值排序,根据用户期望的GC停顿时间制定回收计划来回收);
3.5.8理解GC日志
3.5.9垃圾收集器参数总结
3.6内存分配与回收策略
- 对象优先在新生代分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判断:如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,大于或等于该年龄的对象直接进入老年代;
- 空间分配担保:发生Minor GC前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果不成立,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许继续检查老年代最大可用的连续空间是否大于历次晋升到老年代的平均大小,如果大于会尝试进行一次Minor GC;如果小于或者不允许冒险,会进行一次Full GC;
3.7本章小结
本章介绍了垃圾收集的算法、几款JDK 1.7中提供的垃圾收集器特点以及运作原理。通过代码实例验证了Java虛拟机中自动内存分配及回收的主要规则。