【易错问题】Major GC和Full GC的区别是什么?触发条件呢?

相信大多数人的理解是Major GC只针对老年代,Full GC会先触发一次Minor GC,不知对否?我参考了R大的分析和介绍,总结了一下相关的说明和分析结论。

在基于HotSpotVM的基础角度

针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:

Partial GC(部分回收模式)

Partial GC代表着并不收集整个GC堆的模式

  • Young Generation GC(新生代回收模式):它主要是进行回收新生代范围内的内存对象的GC回收器。
  • Old/Tenured Generation GC(老年代回收模式):它主要是针对于回收老年代Old/Tenured Generation范围内的GC垃圾回收器(CMS的Concurrent Collection是这个模式)。
  • Mixed Generation GC(混合代回收模式):收集整个young gen以及部分old gen的GC。只有G1有这个模式

Full GC(全体回收模式)

Full GC代表着收集整个JVM的运行时堆+方法区+直接堆外内存的总体范围内。(甚至可以理解为JVM进程范围内的绝大部分范围的数据区域)。

它会涵盖了所有的模式和区域包含:Young Gen(新生代)、Tenured Gen(老生代)、Perm/Meta Gen(元空间)(JDK8前后的版本)等全局范围的GC垃圾回收模式。

在一般情况下Major GC通常是跟Full GC是等价的,收集整个GC堆。但如果从HotSpot VM底层的细节出发,如果再有人说“Major GC”的时候一定要问清楚他想要指的是上面的Full GC还是Old/Tenured GC。

基于最简单的分代式GC策略

触发条件是:Young GC

按HotSpot VM的Serial GC的实现来看,当Young gen中的Eden区分达到阈值(属于一定的百分比进行控制)的时候触发。

注意:Young GC中有部分存活对象会晋升到Old/Tenured Gen,所以Young GC后Old Gen的占用量通常会有所升高

触发条件是:Full GC
  1. 当准备要触发一次Young GC时,如果发现统计数据说之前Young Old/Tenured Gen剩余的空间大,则不会触发Young GC,而是转为触发Full GC(因为HotSpot VM的GC里,除了CMS的Concurrent collection之外,其它能收集Old/Tenured Gen的GC都会同时收集整个GC堆,包括Young gen,所以不需要事先触发一次单独的Young GC);

  2. 如果有Perm/Meta gen的话,要在Perm/Meta gen分配空间但已经没有足够空间时,也要触发一次full GC。

  3. System.gc()方法或者Heap Dump自带的GC,默认也是触发Full GC。HotSpot VM里其它非并发GC的触发条件复杂一些,不过大致的原理与上面说的其实一样。

注意:Parallel Scavenge(-XX:+UseParallelGC)框架下,默认是在要触发Full GC前先执行一次Young GC,并且两次GC之间能让应用程序稍微运行一小下,以期降低Full GC的暂停时间(因为young GC会尽量清理了Young Gen的垃圾对象,减少了Full GC的扫描工作量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC。

触发条件是:Concurrent GC

Concurrent GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查Old Gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对Old gen做并发收集。

GC回收器对应的GC模式列举

在Hotspot JVM实现的Serial GC, Parallel GC, CMS, G1 GC中大致可以对应到某个Young GC和Old GC算法组合;

  • Serial GC算法:Serial Young GC + Serial Old GC (实际上它是全局范围的Full GC);
  • Parallel GC算法:Parallel Young GC + 非并行的PS MarkSweep GC / 并行的Parallel Old GC(这俩实际上也是全局范围的Full GC),选PS MarkSweep GC 还是 Parallel Old GC 由参数UseParallelOldGC来控制;
  • CMS算法:ParNew(Young)GC + CMS(Old)GC (piggyback on ParNew的结果/老生代存活下来的object只做记录,不做compaction)+ Full GC for CMS算法(应对核心的CMS GC某些时候的不赶趟,开销很大);
  • G1 GC:Young GC + mixed GC(新生代,再加上部分老生代)+ Full GC for G1 GC算法(应对G1 GC算法某些时候的不赶趟,开销很大);

GC回收模式的触发总结

  • 搞清楚了上面这些组合,我们再来看看各类GC算法的触发条件。简单说,触发条件就是某GC算法对应区域满了,或是预测快满了。比如,

    • 各种Young GC的触发原因都是eden区满了;
    • Serial Old GC/PS MarkSweep GC/Parallel Old GC的触发则是在要执行Young GC时候预测其promote的object的总size超过老生代剩余size;
    • CMS GC的initial marking的触发条件是老生代使用比率超过某值;
    • G1 GC的initial marking的触发条件是Heap使用比率超过某值;
    • Full GC for CMS算法和Full GC for G1 GC算法的触发原因很明显,就是4.3 和 4.4 的fancy算法不赶趟了,只能全局范围大搞一次GC了(相信我,这很慢!这很慢!这很慢!);

【坑点与坑点】-XX:+DisableExplicitGC 与 NIO的direct memory的关系

很多人都见过JVM调优建议里使用这个参数,对吧?但是为什么要用它,什么时候应该用而什么时候用了会掉坑里呢?

  1. 首先,要了解的是这个参数的作用。在Oracle/Sun JDK这个具体实现上,System.gc()的默认效果是引发一次stop-the-world的Full GC,由上面所知就是针对于整个GC堆做内存垃圾收集。

  2. 再次,如果采用了用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,完全不会触发任何GC(但是“函数调用”本身的开销还是存在的哦~)。

    • 为啥要用这个参数呢?最主要的原因是为了防止某些小白同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧。

      1. 有些应用程序本来可能正常跑一天也不会出一次Full GC,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停。
      2. 有些时候这些调用是在某些库或框架里写的,改不了它们的代码但又不想被这些调用干扰也会用这参数。

-XX:+DisableExplicitGC看起来这参数应该总是开着嘛。有啥坑呢?

下述三个条件同时满足时会发生的

  1. 应用本身在GC堆内的对象行为良好,正常情况下很久都不发生Full GC。
  2. 应用大量使用了NIO的direct memory,经常、反复的申请DirectByteBuffer。
  3. 使用了-XX:+DisableExplicitGC。

能观察到的现象是:

java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:633)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
用一个案例来分析这现象:
import java.nio.*;
public class DisableExplicitGCDemo {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
ByteBuffer.allocateDirect(128);
}
System.out.println("Done");
}
}

然后编译、运行。

$ java -version
java version "1.6.0_25"
Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)
$ javac DisableExplicitGCDemo.java
$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC -XX:+DisableExplicitGC DisableExplicitGCDemo
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:633)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:98)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at DisableExplicitGCDemo.main(DisableExplicitGCDemo.java:6)
$ java -XX:MaxDirectMemorySize=10m -XX:+PrintGC DisableExplicitGCDemo
[GC 10996K->10480K(120704K), 0.0433980 secs]
[Full GC 10480K->10415K(120704K), 0.0359420 secs]
Done
  • 可以看到,同样的程序,不带-XX:+DisableExplicitGC时能正常完成运行,而带上这个参数后却出现了OOM。-XX:MaxDirectMemorySize=10m限制了DirectByteBuffer能分配的空间的限额,以便问题更容易展现出来。不用这个参数就得多跑一会儿了。

  • 循环不断申请DirectByteBuffer但并没有引用,所以这些DirectByteBuffer应该刚创建出来就已经满足被GC的条件,等下次GC运行的时候就应该可以被回收。

  • 实际上却没这么简单。DirectByteBuffer是种典型的“冰山”对象,也就是说它的Java对象虽然很小很无辜,但它背后却会关联着一定量的native memory资源,而这些资源并不在GC的控制之下,需要自己注意控制好。

对JVM如何使用native memory不熟悉的同学可以研究一下这篇演讲,“Where Does All the Native Memory Go”。

【盲点问题】DirectByteBuffer的回收问题

Oracle/Sun JDK的实现里,DirectByteBuffer有几处值得注意的地方。

  1. DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的。
  2. sun.misc.Cleaner是一种基于PhantomReference的清理工具,比普通的finalizer轻量些。

"A cleaner tracks a referent object and encapsulates a thunk of arbitrary cleanup code. Some time after the GC detects that a cleaner's referent has become phantom-reachable, the reference-handler thread will run the cleaner."

源码注释
/**
* General-purpose phantom-reference-based cleaners.
*
* <p> Cleaners are a lightweight and more robust alternative to finalization.
* They are lightweight because they are not created by the VM and thus do not
* require a JNI upcall to be created, and because their cleanup code is
* invoked directly by the reference-handler thread rather than by the
* finalizer thread. They are more robust because they use phantom references,
* the weakest type of reference object, thereby avoiding the nasty ordering
* problems inherent to finalization.
*
* <p> A cleaner tracks a referent object and encapsulates a thunk of arbitrary
* cleanup code. Some time after the GC detects that a cleaner's referent has
* become phantom-reachable, the reference-handler thread will run the cleaner.
* Cleaners may also be invoked directly; they are thread safe and ensure that
* they run their thunks at most once.
*
* <p> Cleaners are not a replacement for finalization. They should be used
* only when the cleanup code is extremely simple and straightforward.
* Nontrivial cleaners are inadvisable since they risk blocking the
* reference-handler thread and delaying further cleanup and finalization.
*
*
* @author Mark Reinhold
* @version %I%, %E%
*/

Oracle/Sun JDK中的HotSpot VM只会在Old Gen GC(Full GC/Major GC或者Concurrent GC都算)的时候才会对Old Gen中的对象做Reference Processing,而在Young GC/Minor GC时只会对Young Gen里的对象做Reference processing。Full GC会对Old Gen做Reference processing,进而能触发Cleaner对已死的DirectByteBuffer对象做清理工作。

  • 如果很长一段时间里没做过GC或者只做了Young GC的话则不会在Old Gen触发Cleaner的工作,那么就可能让本来已经死了的、但已经晋升到Old Gen的DirectByteBuffer关联的Native Memory得不到及时释放

  • 为DirectByteBuffer分配空间过程中会显式调用System.gc(),以通过Full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory

// These methods should be called whenever direct memory is allocated or
// freed. They allow the user to control the amount of direct memory
// which a process may access. All sizes are specified in bytes.
static void reserveMemory(long size) {
synchronized (Bits.class) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
if (size <= maxMemory - reservedMemory) {
reservedMemory += size;
return;
}
}
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
if (reservedMemory + size > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
}
}

总结分析

这几个实现特征使得Oracle/Sun JDK依赖于System.gc()触发GC来保证DirectByteMemory的清理工作能及时完成。

如果打开了-XX:+DisableExplicitGC,清理工作就可能得不到及时完成,于是就有机会见到direct memory的OOM,也就是上面的例子演示的情况。我们这边在实际生产环境中确实遇到过这样的问题。

如果你在使用Oracle/Sun JDK,应用里有任何地方用了direct memory,那么使用-XX:+DisableExplicitGC要小心。如果用了该参数而且遇到direct memory的OOM,可以尝试去掉该参数看是否能避开这种OOM。如果担心System.gc()调用造成Full GC频繁,可以尝试下面提到 -XX:+ExplicitGCInvokesConcurrent 参数

深度剖析 | 【JVM深层系列】[HotSpotVM研究系列] JVM调优的"标准参数"的各种陷阱和坑点分析(攻克盲点及混淆点)「 1 」的更多相关文章

  1. SQL Server调优系列进阶篇 - 如何索引调优

    前言 上一篇我们分析了数据库中的统计信息的作用,我们已经了解了数据库如何通过统计信息来掌控数据库中各个表的内容分布.不清楚的童鞋可以点击参考. 作为调优系列的文章,数据库的索引肯定是不能少的了,所以本 ...

  2. SQL Server 调优系列进阶篇 - 如何索引调优

    前言 上一篇我们分析了数据库中的统计信息的作用,我们已经了解了数据库如何通过统计信息来掌控数据库中各个表的内容分布.不清楚的童鞋可以点击参考. 作为调优系列的文章,数据库的索引肯定是不能少的了,所以本 ...

  3. 【Xamarin挖墙脚系列:应用的性能调优】

    原文:[Xamarin挖墙脚系列:应用的性能调优] 官方提供的工具:网盘地址:http://pan.baidu.com/s/1pKgrsrp 官方下载地址:https://download.xamar ...

  4. JVM调优:GC 参数

    2019独角兽企业重金招聘Python工程师标准>>> JVM调优:GC 参数 博客分类: java jvm 参考: <Memory Management in the Jav ...

  5. 【CNMP系列】PHP配置和调优

    上一节我们说到PHP5.6.30在CentOS7.0下的整个安装过程,http://www.cnblogs.com/riverdubu/p/6428226.html 今天我来和大家讲解一下PHP-FP ...

  6. Tomcat系列(9)——Tomcat 6方面调优(内存,线程,IO,压缩,缓存,集群)

    核心部分 内存 线程 IO 压缩 缓存 集群 一.JVM内存优化 Tomcat内存优化,包括内存大小,垃圾回收策略. Windows 下的catalina.bat,Linux 下的catalina.s ...

  7. JVM学习笔记(四)------内存调优【转】

    转自:http://blog.csdn.net/cutesource/article/details/5907418 版权声明:本文为博主原创文章,未经博主允许不得转载. 首先需要注意的是在对JVM内 ...

  8. JVM垃圾回收机制总结(7) :调优方法

    JVM调优工具 Jconsole,jProfile,VisualVM Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用.对垃圾回收算法有很详细的跟踪.详细说明参考这里 ...

  9. JVM学习笔记(四)------内存调优

    首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提 ...

  10. 【JVM虚拟机】(4)---性能调优

    JVM性能调优 一.调优策略 对于GC的性能主要有2个方面的指标:吞吐量throughput(工作时间不算gc的时间占总的时间比)和暂停pause(gc发生时app对外显示的无法响应). 1.调优的目 ...

随机推荐

  1. Ceph 存储集群 - 搭建存储集群---教程走到osd激活这一步执行不下去了,报错

    目录 一.准备机器 [1. 修改主机名](所有节点)(https://www.cnblogs.com/zengzhihua/p/9829472.html#1-修改主机名) [2. 修改hosts文件] ...

  2. .NET下数据库的负载均衡(有趣实验)

    相关下载: 数据库的负载均衡-示例代码(dp1-DbBalance.rar) 数据库的负载均衡-示例代码(dp1-DbBalance.rar) 支持.Net/.Net Core/.Net Framew ...

  3. python流程控制下-for、while循环补充

    循环结构之for循环 实现循环结构还可以用关键字for. for关键字 我们来看这一段代码: emotions = ['smile', 'laugh', 'cry', 'angry'] for emo ...

  4. 关于Struts访问不到静态资源的问题

    今天重新配置了Struts的项目进行开发,但是项目静态资源一直访问不到. 将一些静态资源放在WebRoot下的static包下面便于管理. 一开始以为采用拦截.do,只拦截do后缀的请求,解决了静态资 ...

  5. 【算法训练营day4】LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表II

    [算法训练营day4]LeetCode24. 两两交换链表中的结点 LeetCode19. 删除链表的倒数第N个结点 LeetCode面试题 02.07. 链表相交 LeetCode142. 环形链表 ...

  6. 方法的重载(overload)

    1.定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可. "两同一不同":同一个类.相同方法名 参数列表不同:参数个数不同,参数类型不同 2.举 ...

  7. C语言二叉树遍历及路径查找

    #include<iostream> #include<stdio.h> #include<math.h> #include<malloc.h> usi ...

  8. 9.pygame-键盘捕获

    创建英雄类 """英雄精灵""" class Hero(GameSprite): def __init__(self): # 调用父类方法, ...

  9. 18.-cookies和session

    一.会话定义 从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次绘画 HTTP协议是无状态的,导致绘画状态难以保持 Cookies和session就是为了保持会话状态而诞生的两个存储技术 ...

  10. windows和虚拟机上的Ubuntu互传文件

    1.简介 本文讲述的是通过ssh登录虚拟机上的Ubuntu系统,实现互传文件 2.Ubuntu端 2.1.安装ssh sudo apt-get update sudo apt-get install ...