首页IT科技jvm调优实战pdf(深度剖析 | 【JVM深层系列】[HotSpotVM研究系列] JVM调优的"标准参数"的各种陷阱和坑点分析(攻克盲点及混淆点)「 1 」)

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

时间2025-08-03 14:23:14分类IT科技浏览5629
导读:【易错问题】Major GC和Full GC的区别是什么?触发条件呢?...

【易错问题】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

当准备要触发一次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);

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

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调优建议里使用这个参数        ,对吧?但是为什么要用它                       ,什么时候应该用而什么时候用了会掉坑里呢?

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

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

为啥要用这个参数呢?最主要的原因是为了防止某些小白同学在代码里到处写System.gc()的调用而干扰了程序的正常运行吧                       。 有些应用程序本来可能正常跑一天也不会出一次Full GC                       ,但就是因为有人在代码里调用了System.gc()而不得不间歇性被暂停               。 有些时候这些调用是在某些库或框架里写的,改不了它们的代码但又不想被这些调用干扰也会用这参数        。

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

下述三个条件同时满足时会发生的 应用本身在GC堆内的对象行为良好               ,正常情况下很久都不发生Full GC               。 应用大量使用了NIO的direct memory                       ,经常        、反复的申请DirectByteBuffer。 使用了-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有几处值得注意的地方        。

DirectByteBuffer没有finalizer                       ,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的                       。 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 cleaners 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 cleaners 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 参数

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
idea无法创建jsp文件(IDEA没有新建jsp文件按钮) c++零基础自学(C++入门级基础知识汇总)