Java垃圾回收机制(GC)
自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇
本文作者: Jeffrey
本文链接: https://www.bytelife.net/articles/46172.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
判断对象是否可回收的常见方法
引用计数算法(Reference Counting)
给对象中添加一个引用计数器,每当有一个地方引用时,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能再被使用的。
- 优点:实现简单,判定效率高;
- 缺点:很难解决对象之间的循环引用问题;
JVM并不使用这种算法。
可达性分析算法(Reachability Analysis)
通过一些列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径叫做引用链,当一个对象到GC Roots没有任何引用链与之相连时,则证明该对象是不可用的。
在Java语言中,可作为GC Roots的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 方法区中静态属性引用的对象;
- 方法区中常量引用的对象;
- 本地方法栈中JNI(Native方法)引用的对象;
二次标记
如果一个对象真正宣告“死亡”,至少要经过两次标记过程:
如果一个对象经过可达性分析算法判定为可回收对象,则它将会被第一次标记并进行一次筛选,筛选的条件是判断该对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已经执行过一次finalize()方法,则虚拟将将判定为“没有必要执行”。如果判定为“有必要执行finalize()方法”,那么这个对象将被置入F-Queue队列,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程区执行(仅仅是触发)它。
finalize()方法是逃脱死亡的最后机会,稍后GC将堆F-Queue队列进行再一次的小规模标记,如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,那么在第二次标记的时候将会被移除“即将回收”的集合;如果对象这时候还没有逃脱,那么基本上对象宣告“死亡”并将被回收。
垃圾收集算法
标记-清除算法(Mark-Sweep)
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
主要缺点:
- 效率问题,标记和清除过程的效率都不高;
- 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作;
复制算法(Copying)
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。
现在的商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象98%是朝生夕死的,所以并不需要按照1∶1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。
当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被“浪费”的。
当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。
标记整理算法(Mark-Compact)
复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。 一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
垃圾收集器
Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。
- Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效。
- Serial Old收集器(标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本。
- ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
- Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。
- Parallel Old收集器(停止-复制算法):Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先
- CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择
1. Serial Garbage Collector
Serial Garbage Collector通过暂停所有应用的线程来工作。它是为单线程工作环境而设计的。它中使用一个线程来进行垃圾回收。这种暂停应用线程来进行垃圾回收的方式可能不太适应服务器环境。它最适合简单的命令行程序。通过 -XX:+UseSerialGC 参数来选用Serial Garbage Collector。
2. Parallel Garbage Collector
Parallel Garbage Collector也被称为吞吐量收集器(throughput collector)。它是Java虚拟机的默认垃圾收集器。与Serial Garbage Collector不同,Parallel Garbage Collector使用多个线程进行垃圾回收。与Serial Garbage Collector相似的地方时,它也是暂停所有的应用线程来进行垃圾回收。
3. CMS Garbage Collector
Concurrent Mark Sweep (CMS) Garbage Collector使用多个线程来扫描堆内存来标记需要回收的实例,然后再清除被标记的实例。CMS Garbage Collector只有在如下两种情景才会暂停所有的应用线程:
- 当标记永久代内存空间中的对象时;
- 当进行垃圾回收时,堆内存同步发生了一些变化。
相比Parallel Garbage Collector,CMS Garbage Collector使用更多的CPU资源来确保应用有一个更好的吞吐量。如果分配更多的CPU资源可以获得更好的性能,那么CMS Garbage Collector是一个更好的选择。 通过 XX:+USeParNewGC 参数来选用CMS Garbage Collector。
4. G1 Garbage Collector
G1 Garbage Collector用于大的堆内存区域。它将堆内存分割成多个独立区域(Region),然后并发地对它们进行垃圾回收。在释放内存后,G1还可以压缩空闲的堆内存。但是,CMS Garbage Collector是通过“Stop The World (STW)”来进行内存压缩的。G1优先收集可回收更多内存的区域。Java 8 的改进:在用G1 Garbage Collector时,可以开启 -XX:+UseStringDeduplication 参数。它通过将重复的字符串移动到同一个 char 数组中来优化堆内存的使用。该选项在Java 8u20时引用进来。 通过 –XX:+UseG1GC 参数来选用G1 Garbage Collector。
Java虚拟机中的垃圾回收选项配置
下面是与Java收集器相关的Java虚拟机选项。
垃圾收集器选择
Option | Description |
---|---|
-XX:+UseSerialGC | Serial Garbage Collector |
-XX:+UseParallelGC | Parallel Garbage Collector |
-XX:+UseConcMarkSweepGC | CMS Garbage Collector |
-XX:ParallelCMSThreads= | CMS Collector – 使用的线程数 |
-XX:+UseG1GC | G1 Gargbage Collector |
垃圾回收优化选项
Option | Description |
---|---|
-Xms | 堆内存初始化尺寸 |
-Xmx | 堆内存最大尺寸 |
-Xmn | 新生代(Young Generation)的尺寸 |
-XX:PermSize | 永久代(Permanent Generation)初始化尺寸 |
-XX:MaxPermSize | 永久代(Permanent Generation)最大尺寸 |