垃圾回收机制
比较好的文章:
1⃣️可回收对象算法
- 目前查看对象是否需要回收的算法主要由两种:
引用计数法
和可达性分析
; - 引用计数虽好,但是无法解决对象之间相互循环引用但是实际上这些对象没什么卵用时造成的内存泄漏;
-
java采用的是可达性分析算法(Reachability Analysis)来判断对象是否存活:从GC Roots出发,可以到达的对象就是存活的,到达不了的就要被GC;
-
GC Roots可以是虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中产量引用的对象、本地方法栈引用的对象;这些对象的特点就是不被GC管理(GC回收的是堆上对象,GC roots位于方法区、虚拟机栈和本地方法栈),可以作为GC Roots的对象见
可以作为GC Roots的对象
; -
一个对象真正的死亡需要两次被标记的过程:如果对象在进行可达性分析后没有发现和GC Roots相连的引用链,将会被
第一次标记
并进行一次筛选,筛选条件是此对象是否需要执行finalize()方法,当对象没有重写覆盖finalize()方法或者finalize()方法已经执行过一次,则虚拟机认为不需要执行finalize();
如果虚拟机认为需要执行finalize(),则会将对象放入F-Queue队列中,等待Finalizer线程执行触发finalize()方法。在这个队列等待过程中,如果重新和引用链上的对象发生关联(重新引用),第二次标记
的时候将会移除“等待回收”的对象集合,这个对象就重新存活了。若没有,将会被第二次标记然后被回收。 -
引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),这四种强度逐渐减弱。
四种引用介绍:
- 强应用:就是我们代码中最常见的引用方式,GC在强引用对象可达时不会回收;
- 软引用:使用
java.lang.ref.SoftReference
来创建软引用,软引用的特点是当内存不足时,GC会将其回收;适合作为缓存使用 - 弱引用:使用
java.lang.ref.WeakReference
来创建弱引用,特点是每次GC都会将其回收,无论内存充足与否; - 虚引用:使用
java.lang.ref.PhantomReference
来创建虚引用,其特点是和引用它的对象生命周期无关,GC任何情况下都会回收它。虚引用仅用来处理资源的清理问题,比Object里面的finalize机制更灵活。get方法返回的永远是null,Java虚拟机不负责清理虚引用,但是它会把虚引用放到引用队列里面。
使用:对于软引用和弱引用,可以有两种构造方式:使用ReferenceQueue引用队列或者不使用ReferenceQueue,例如:
WeakReference<String> wr = new WeakReference<String>(str);
或者
ReferenceQueue<String> rq = new ReferenceQueue<String>();
WeakReference<String> wr = new WeakReference<String>(str, rq);
软引用和弱引用很相似,也是最常用的非强引用,
而对于虚引用PhantomReference只能配合ReferenceQueue使用:
ReferenceQueue<String> rq = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(str, rq);
2⃣️垃圾收集算法
- 当对象判断为可回收后,需要专门的算法去执行回收动作。垃圾收集算法主要分为以下几种:
- 标记-清除算法,Mark-Sweep:顾名思义,缺点是效率慢,容易造成内存碎片;
- 复制算法,Coping:将内存空间分为几块区域,每次只使用其中一块E,当一块快用完了,就将存活的对象复制到另外一块S上,然后把使用的E一次性清除干净。实际上这种算法用于新生代的垃圾回收,E即Eden,S就是Survivor,缺点是Survivor空间不够时需要老年代内存来担保,即所谓的分配担保(Handle Promoting);
- 标记整理算法,Mark-Compact:和标记-清除算法的区别在于,标记后并不直接进行清理,而是将存活对象向内存空间的一端移动,然后直接清除存活边界以外的内存空间,避免碎片化;
- 分代收集算法,Generational Collection:实际上就是上述垃圾收集算法的组合,即在不同的内存区域采用不同的收集算法。比如在新生代采用复制算法,在老年代采用标记-整理/清理算法。
- 在Hotspot中使用一组称为OopMap的数据结构来达到这个目的,在类加载完毕的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来。在JIT中也会在特定的位置记录下栈和寄存器的引用的位置。
- HotSpot并非为每条指令都生成OopMap,只在安全点SafePoint记录。即程序执行到安全点才暂停下来开始GC。
- 收集器的性能指标:
1⃣️吞吐率(1 - GC总时间/总时间):高吞吐量能提高CPU效率,适合交互较少的程序;
2⃣️最大暂停时间:越短越适合交互比较多的程序;
3⃣️堆空间使用率: - 常见的收集器:
- Serial:单线程收集器,新生代运行,使用复制算法,在安全点暂停所有线程开始GC;
- ParNew:多线程版本的Serial,新生代运行;优先配合CMS
- Parallel Scavenge:新生代收集器,使用复制算法,可以控制吞吐量,吞吐量优先收集器;配合Serial Old使用,不能搭配CMS;
- Serial Old:Serial的老年代版本,老年代运行,使用标记-整理算法;
- Parallel Old:Parallel Scavenge的老年代版本,优先考虑配合Parallel Scavenge;
- CMS:Concurrent Mark Sweep,获取最短暂停时间,老年代运行;
- G1:Garbage First,最新的收集器,老年代和新生代都可以使用。
GC | 描述 | 适用年代 | VM参数 | 偏重 | 搭配 |
---|---|---|---|---|---|
Serial | 单线程收集器 | 新生代 | -XX:+UseSerialGC | 单核CPU | VM参数下同时启动Serial Old |
ParNew | 多线程收集器 | 新生代 | -XX:+UseConcMarkSweepGC或-XX:+UseParNewGC | 多核CPU | CMS |
Parallel Scavenge | 并行多线程 | 新生代 | -XX:+UseParallelGC | 吞吐量 | VM参数下默认Serial Old |
Serial Old | Serial的老年代版本 | 老年代 | -XX:+UseSerialGC | CMS的替补 | Parallel Scavenge(jdk1.5以前) |
Parallel Old | Parallel Scavenge的老年代版本 | 老年代 | -XX:-UseParallelOldGC | 吞吐量/CPU资源 | Parallel Scavenge |
CMS | Concurrent Mark Sweep | 老年代 | -XX:+UseConcMarkSweepGC | 暂停时间 | -XX:+UseConcMarkSweepGC同时启动ParNew |
G1 | 优先搜集占用空间大的垃圾 | 全局 | -XX:+UseG1GC | 在暂停时间内尽可能收集多内存 | 全局使用 |
-
jdk 7默认的GC回收器组合是ParNew + CMS;
-
一个典型的32位JVM,Java堆大小设置在2 GB(使用分代&并发收集器)通常为500 MB YoungGen分配空间和1.5 GB的OldGen空间。
网友评论