http://blog.csdn.net/historyasamirror/article/details/6233007(转)
正文之前,先介绍一人:Jon Masamitsu。此人背景不详,不过他在SUN做的就是JVM,所以他的blog我认为是每一个想对JVM调优的人都应该读一读的。本文的很多观点和一些图也是取自他的blog。
blog link:http://blogs.sun.com/jonthecollector/
在他的一篇blog【1】中,写到了GC调优的最重要的三个选项:
排在第三位的是young generation在整个JVM heap中所占的比重;
排在第二位的是整个JVM heap的大小;
排在第一位的就是选择合适的GC collector,这也是本文的内容所在。
基本概念
先科普一些基本知识。JVM Heap在实现中被切分成了不同的generation(很多中文翻译成‘代’),比如生命周期短的对象会放在young generation,而生命周期长的对象放在tenured generation中,如下图(摘自【2】)。
当GC只发生在young generation中,回收young generation中的对象,称为Minor GC;当GC发生在tenured generation时则称为Major GC或者Full GC。一般的,Minor GC的发生频率要比Major GC高很多。
关于generation的知识,这里不多谈了,感兴趣的参见【2】,或者很多网上的文章。
上图(摘自【3】)很清楚的列出了JVM提供的几种GC collector。
其中负责Young Generation的collector有三种:
Serial :最简单的collector,只有一个thread负责GC,并且,在执行GC的时候,会暂停整个程序(所谓的“stop-the-world”),如下图所示;
Parallel Scavenge:
和Serial相比,它的特点在于使用multi-thread来处理GC,当然,在执行的时候,仍然会“stop-the-world”,好处在于,暂停的时间也许更短;
ParNew:
它基本上和Parallel Scavenge非常相似,唯一的区别,在于它做了强化能够和CMS一起使用;
负责Tenured Generation的collector也有三种:
Serial Old:
单线程,采用的是mark-sweep-compact回收方法(好吧,我承认我不知道什么是mark-sweep-compact,对我来说,只记住了它是单线程的),图示和Serial类似;
Parallel Old:
同理,多线程的GC collector;
CMS:
全称“concurrent-mark-sweep”,它是最并发,暂停时间最低的collector,之所以称为concurrent,是因为它在执行GC任务的时候,GC thread是和application thread一起工作的,基本上不需要暂停application thread,如下图所示;
6种collector介绍完了。不过,在设定JVM参数的时候,很少有人去分别制定young generation和tenured generation的collector,而是提供了几套选择方案:
-XX:+UseSerialGC:
相当于”Serial” + “SerialOld”,这个方案直观上就应该是性能最差的,我的实验证明也确实如此;
-XX:+UseParallelGC:
相当于” Parallel Scavenge” + “SerialOld”,也就是说,在young generation中是多线程处理,但是在tenured generation中则是单线程;
-XX:+UseParallelOldGC:
相当于” Parallel Scavenge” + “ParallelOld”,都是多线程并行处理;
-XX:+UseConcMarkSweepGC:
相当于"ParNew" + "CMS" + "Serial Old",即在young generation中采用ParNew,多线程处理;在tenured generation中使用CMS,以求得到最低的暂停时间,但是,采用CMS有可能出现”Concurrent Mode Failure”(这个后面再说),如果出现了,就只能采用”SerialOld”模式了;
【3】中还提到了一个方案:UseParNewGC,不过我在其它地方很少看见有人用它,就不介绍了。
实验和结果
说了这么多,还是用实验验证一下吧。
被实验的系统可以看做是一个内存数据库,所有的数据和查询都是在内存中进行,测试用的workload即包含将数据写入到内存中,也包含各式各样的查询。这样一个系统和测试数据,在运行过程中,由于处理大量的查询,所以肯定会随时产生很多的短周期对象,所以young generation对应的Minor GC会比较频繁,同时,由于不断有新的数据写入到数据库,而这些数据都属于长周期对象,所以tenured generation的使用率是不断增长。
实验使用的是一台DELL的服务器,有48G内存,有2个CPU,各4个core,所以总共8个core。为了实验的方便,我只是用了大概16G内存给JVM使用。
SerialGC
先看第一张图:
图中的采样点有些密集,截取其中的一小段放大如下:
图的纵轴表示的是系统的throughput,单位是request/second。我设定的计算throughput的时间间隔是5秒,也就是说,图中的每一个点,都是一个5秒时间区间内系统throughput的平均值。
图的横轴表示的是时间维度。
后面几张图的设定和这个图是一样的。
这张图采用的是SerialGC ,整个JVM的参数设置如下:
Java -jar -Xms10g -Xmx15g -XX:+UseSerialGC -XX:NewSize=6g -XX:MaxNewSize=6g -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:./log/gc.log Slaver.jar
其中,-XX:NewSize=6g -XX:MaxNewSize=6g 表示的是将young generation的初始化size和最大size都设置成了6G,也就是说在整个系统运行过程中,young generation的size是不会变的。这是因为JVM会根据实际的运行情况动态的调节young generation和tenured generation的比例,而这样的设置能够避免这种动态的调整,便于观察实验结果。
-Xms10g -Xmx15g 表示将JVM Heap的初始Size设置为10G,而它的最大Size设置为15G。相当于初始的时候tenured generation的大小约等于(10-6)G,而它可以增长为(15-6)G (只是近似计算,忽略了perm generation的size)。
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:./log/gc.log 这些参数,是将GC的细节和Heap的变化记录到gc.log中。
Slaver.jar 是俺的程序....
对于后面的实验,这些参数都不会变动。唯一变动的就只是选择不同的Collector。
OK,回到实验结果图。
这张图有一个很有趣的现象是它的抖动非常大,几乎总是一个点高,紧挨着一个点就会低。这意味着系统的throughput在不断的震荡。这个现象的原因是实验过程中,Minor GC的发生频率基本上是7-9秒一次(通过log观察到的),而前面提到了我们计算throughput的时间区间是5秒,所以,基本上每隔一个时间区间就会发生一次Minor GC,而发生Minor GC的时间区间它的平均throughput就会降低。
为了证明这一点,我随意找了一段Minor GC的log:
900.692: [GC 900.692: [DefNew: 5334222K->300781K(5662336K), 0.8988610 secs] 7654628K->2641401K(9856640K), 0.8990190 secs] [Times: user=0.86 sys=0.03, real=0.89 secs ]
解释一下这段log:
900.692 表明这次GC发生在系统启动后的900.062秒这一时刻;
5334222K->300781K(5662336K) 表明young generation在这次GC中从5334222K降低到了300781K,而young generation的size是5662336K(注意,这个值近似于我们之前设定的6G),这个过程花费了0.8988610秒;
7654628K->2641401K(9856640K) 表明这个JVM Heap从7654628K降低到了2641401K(这两个值之差应该和上面的那两个值之差几乎相等,因为这是Minor GC,所以整个Heap清理出的空间其实就是young generation清理出的空间);
user=0.86 sys=0.03, real=0.89 secs 表明这次GC的user time是0.86,而real time是0.89秒 (不理解user/sys/real的见【4】)
我们之前提到过,SerialGC是需要将整个application暂停的,所以,这次GC将整个application暂停了0.89秒,就是这个暂停导致了系统throughput的下降;
图中更有意思的现象是在横轴1700附近,系统的throughput直线下降到几乎为0。直观的猜测,这里应该是发生了Full GC。果然,在log中找到了它:
1700.750: [GC 1700.750: [DefNew: 5335802K->302541K(5662336K), 0.9367800 secs ]1701.687: [Tenured: 4210828K->4211008K(4211008K), 17.6799010 secs ] 9526754K->4513370K(9873344K), [Perm : 11048K->11048K(21248K)], 18.6220490 secs] [Times: user=18.61 sys=0.01, real=18.62 secs ].
这里最重要的是这句:[Tenured: 4210828K->4211008K(4211008K), 17.6799010 secs] ,表示对tenured generation进行了GC,花费了17.6799010秒(由于我写入的数据是要一直保存的,所以这次GC几乎没有在tenured generation中清除任何的dead object,所以下降幅度不大4210828K->4211008K)。
而这次GC总的花费了18.62 secs,意味着系统在这18秒内都被暂停了,在这18秒内系统几乎没有任何的throughput。所以,采用Seral GC,意味着系统在发生Full GC的时候,将会有大概十几秒的时间对外界的请求没有响应,如果是一个Web Server的话,意味着这十几秒用户没法浏览网页。这个感觉可不好。
此外,log还记录了Full GC发生前后Heap的情况:
{Heap before GC invocations=204 (full 0):
...........................................
tenured generation total 4194304K , used 4190951K [0x00007fe2c82e0000, 0x00007fe3c82e0000, 0x00007fe5082e0000)
the space 4194304K, 99% used [0x00007fe2c82e0000, 0x00007fe3c7f99dd8, 0x00007fe3c7f99e00, 0x00007fe3c82e0000)
........................................
........................................
Heap after GC invocations=205 (full 1):
....................................................................
tenured generation total 7018348K , used 4211008K [0x00007fe2c82e0000, 0x00007fe4748bb000, 0x00007fe5082e0000)
the space 7018348K, 59% used [0x00007fe2c82e0000, 0x00007fe3c9330000, 0x00007fe3c9330000, 0x00007fe4748bb000)
......................................................
}
从log可以看到,在GC发生之前,Heap里面的tenured generation的占用率已经到了99%,意味着tenured generation已经满了。所以,可以判断出,Full GC发生的条件就是tenured generation已经满了。
而在GC发生之后,发现total space从4194304K增长到了7018348K。还记得吗,我们在设置JVM的时候,初始Heap的Size是10G,最大的Size是15G,那多出的5G就是用于generation的增长的。
最后,你能够发现,无论是Minor GC还是Major GC,它的user time和real time的值相差不大,如果你了解user time和real time的意义,就能够知道这意味着GC这个任务是有单线程执行的,才会出现这种情况。这也和SerialGC的概念相吻合了。
(未完待续)
Reference:
【1】The Second Most Important GC Tuning Knob
【2】Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning
【3】Our Collectors
【4】 user / sys /real time
相关推荐
- **G1 Collector**:一种面向服务端应用的垃圾回收器,能够实现低延迟的同时处理大量的数据。 #### 五、常用参数设置 - **栈上分配**:将小对象直接分配在栈上,减少了对象分配的开销。可以通过参数`-XX:+UseTLAB...
《深入解析JVM调优与监控》 在Java开发领域,JVM(Java Virtual Machine)是运行Java程序的核心,它的性能直接影响着应用的效率和稳定性。JVM调优是优化Java应用程序性能的关键环节,而"jvm-monitor"则提供了一种...
"Java面试题-内存+GC+类加载器+JVM调优" 在 Java 面试中,内存、GC、类加载器和 JVM 调优是非常重要的知识点,本文将对这些知识点进行详细的解释和分析。 一、Java 内存模型 在 Java 中,内存主要分为两部分:堆...
### jvm调优建议文档知识点解析 #### 一、JVM基本概念及组成部分 - **JVM内存区域划分**:JVM内存分为新生代、老年代以及元空间(Metaspace)三大区域。其中,新生代负责存放新创建的对象,经过多次垃圾回收后存活的...
### JVM_GC调优详解 #### 一、JVM体系结构概览 Java虚拟机(JVM)作为Java程序的运行环境,其内部结构复杂且高效。为了更好地理解JVM_GC调优,我们首先来了解一下JVM的基本组成部分。 1. **类装载器子系统(Class ...
理解不同类型的GC工作原理、触发条件以及如何选择合适的GC策略对于减少系统停顿时间至关重要。 3. **内存分配与GC调优**:包括对象的分配策略、新生代与老年代的划分、内存大小调整、存活对象的判断算法(如Mark-...
- **选择合适的垃圾回收器**:根据应用的特点选择不同的GC策略,如CMS、G1等。 - **监控工具**:利用VisualVM、JConsole等工具监控JVM的运行状态,分析GC行为。 - **GC日志分析**:通过配置日志参数(`-XX:+...
一个PPT包含 java内存模型,class运行机制。 java jvm垃圾回收算法 java jvm gc常见垃圾回收算法分析 java jvm调优
Java虚拟机(JVM)是Java程序运行的基础,它的垃圾收集器(GC)是自动管理内存的核心机制。...这个"share-jvm-gc"项目很可能是为了提供一个实践和学习这些知识的平台,通过实践来加深理解和掌握JVM GC调优的技巧。
其次,选择合适的垃圾收集器(Garbage Collector,简称GC)对性能影响很大。不同类型的GC适用于不同的应用场景。例如,Serial Collector适合单线程环境,Parallel Collector适用于多CPU环境,可以提高并行收集的效率...
每种算法在不同场景下有不同的性能表现,需要根据应用需求选择合适的GC。 2. **内存分配策略**:调整新生代、老年代、永久代(或元空间)的大小,以平衡吞吐量和响应时间。过大可能导致内存浪费,过小可能导致频繁...
2. **选择合适的垃圾收集器**:考虑系统资源和应用特点,如响应时间优先或吞吐量优先。 3. **监控和分析GC日志**:通过`-XX:+PrintGCDetails`等选项输出GC日志,分析垃圾回收行为。 4. **减少Full GC**:优化对象...
选择合适的GC策略至关重要,如低延迟场景下通常使用CMS或G1。 - **GC日志**:开启-XX:+PrintGCDetails和-XX:+PrintGCDateStamps,以便分析和优化。 3. **类加载机制** - **类加载器**:Bootstrap、Extension、...
5. **JVM调优**:通过调整JVM参数,可以控制GC的行为,例如设置堆大小、新生代和老年代的比例、GC策略等。常用的JVM参数有`-Xms`, `-Xmx`, `-Xmn`, `-XX:NewRatio`, `-XX:SurvivorRatio`, `-XX:+UseConcMarkSweepGC`...
JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解 本文将对一些常用的 JVM 性能调优监控工具进行介绍,包括 jps、jstack、jmap、jhat、jstat 等工具的使用详解。这些工具对于 Java 程序员来说是必备的,...
JVM调优还包括对垃圾收集器(Garbage Collector,GC)的选择和参数调整。不同类型的GC有不同的性能特征,如Serial GC适合轻量级应用,Parallel GC和Concurrent Mark Sweep (CMS) GC适用于高并发场景,G1 GC则试图...
在Java世界中,Java虚拟机(JVM)是运行所有Java应用程序的核心。JVM内存设置与调优是提升应用性能的...理解内存结构、选择合适的垃圾收集器、合理设置参数,并结合监控工具进行调优,是优化Java应用性能的关键步骤。
JVM性能调优涉及到对内存模型的理解、垃圾收集机制的选择与优化等多个方面。通过对这些知识点的深入掌握,开发者可以更好地应对实际开发中的性能问题,提高系统的稳定性和响应速度。此外,熟练运用各种调试工具也是...