Jvm性能调优与监控

一、JVM分代算法内存模型及垃圾收集算法

1.根据Java虚拟机规范,JVM将内存划分为:

New(年轻代)

Tenured(年老代)

永久代(Perm)

其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:)指定的内存中分配.Perm是非堆内存,通过-XX:PermSize -XX:MaxPermSize等参数调整其大小。

年轻代(New):年轻代用来存放JVM刚分配的Java对象

年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代

永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。

New又分为几个部分:

Eden:Eden用来存放JVM刚分配的对象

Survivor1

Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了

被垃圾回收的可能性。

Jvm性能调优与监控_垃圾回收


2.垃圾回收算法

垃圾回收算法可以分为三类,都基于标记-清除(复制)算法:

Serial算法(单线程)

并行算法

并发算法

JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法,关于选择细节请参考JVM调优文档。

稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。

3、垃圾回收器

目前的收集器主要有三种:串行收集器、并行收集器、并发收集器

串行收集器:使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上

并行收集器:对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用-XX:+UseParallelGC.打开。并行收集器在J2SE5.0第6更新上引入,在Java SE6.0中进行了增强--可以对年老代进行并行收集。适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用

并发收集器:可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用

垃圾回收动作何时执行?

GC类型
GC有两种类型:Scavenge GC和Full GC

Scavenge GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。年轻代满是指Eden代满,Survivor满不会引发GC。

Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此应该尽可能减少Full GC。有如下原因可能导致Full GC:

Tenured被写满

Perm域被写满

System.gc()被显示调用

上一次GC之后Heap的各域分配策略动态变化

二、jvm参数设置

调优的目的是在保证服务正常的情况下,GC尽可能的少执行,GC 执行的时间尽可能短,Full GC的周期尽可能的长

前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

1、针对JVM堆的设置一般,可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
2、年轻代和年老代将根据默认的比例(3:8)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过-XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

3、年轻代和年老代设置多大才算合理?这个问题毫无疑问是没有答案的,否则也就不会有调优。我们看一下二者大小变化有哪些影响

A、更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC

B、更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率

C、如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:a、本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例3:8也是这个道理(B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响FullGC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

4、jvm参数说明

-Xmx3550m:设置JVM最大可用内存

-Xms3550m:设置JVM初始内存,此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存

-Xmn:设置年轻代大小整个堆大小=年轻代大小 + 年老代大小 + 持久代大小Sun官方推荐配置为整个堆的3/8

-Xss: 设置每个线程的堆栈大小,设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右,这个参数对性能的影响比较大的

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5,上为说明数据非实际应用数据

-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:PermSize=128M设置持久代大小

-XX:MaxPermSize=128M设置持久代最大值,此值可以设置与-XX:PermSize相同,防止持久代内存伸缩,持久代设置很重要,一般预留其使用空间的1/3.

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论,linux64的java6默认值是15

-XX:+UseSerialGC 开启串行收集器

-XX:+UseParallelGC开启年轻代并行收集器,JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值


-XX:ParallelGCThreads=设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等(逻辑cpu数),这个不确定是物理、还是逻辑使用默认就好

-XX:MaxGCPauseMillis=指定垃圾回收时的最长暂停时间,单位毫秒,如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值,设定此值可能会减少应用的吞吐量

-XX:GCTimeRatio=设定吞吐量为垃圾回收时间与非垃圾回收时间的比值,公式为1/(1+N)。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认情况为99,即1%的时间用于垃圾回收

-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开

-XX:+UseParallelOldGC开启老年代并行收集器

-XX:+UseConcMarkSweepGC开启老年代并发收集器(简称CMS),可以和UseParallelGC一起使用

-XX:CMSInitiatingOccupancyFraction=70老年代内存使用比例到多少激活CMS收集器,这个数值的设置有很大技巧基本上满足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn否则会出现“Concurrent Mode Failure”,promotionfailed,官方建议数值为68

-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩

-XX:+DisableExplicitGC:禁止 java 程序中的 full gc, 如System.gc() 的调用. 最好加上, 防止程序在代码里误用了对性能造成冲击

-XX:+PrintGCDetails

打应垃圾收集的情况如 :
[GC 15610.466: [ParNew: 229689K->20221K(235968K), 0.0194460 secs] 1159829K->953935K(2070976K),0.0196420secs]

-XX:+PrintGCTimeStamps
打应垃圾收集的时间情况 , :
[Times: user=0.09 sys=0.00, real=0.02 secs]

-XX:+PrintGCApplicationConcurrentTime打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用

输出形式:Application time: 0.5291524 seconds

-XX:+PrintGCApplicationStoppedTime
打应垃圾收集时 , 系统的停顿时间, :
Total time for which application threads were stopped: 0.0225920 seconds

-XX:+PrintGC
输出形式

[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC121376K->10414K(130112K), 0.0650971 secs]

-XX:PrintHeapAtGC:打印GC前后的详细堆栈信息

输出形式

{Heapbefore GC invocations=1 (full 0):

par new generation   total 204800K, used 123227K[0x00000006f3800000, 0x0000000706400000, 0x0000000706400000)

 eden space 102400K, 100% used[0x00000006f3800000, 0x00000006f9c00000, 0x00000006f9c00000)

 from space 102400K,  20% used [0x0000000700000000,0x0000000701456e50, 0x0000000706400000)

 to  space 102400K,   0% used[0x00000006f9c00000, 0x00000006f9c00000, 0x0000000700000000)

concurrent mark-sweep generation total3887104K, used 0K [0x0000000706400000, 0x00000007f3800000, 0x00000007f3800000)

concurrent-mark-sweep perm gen total 204800K,used 32721K [0x00000007f3800000, 0x0000000800000000, 0x0000000800000000)

7.673:[GC 7.673: [ParNew: 123227K->33943K(204800K), 0.0251790 secs]123227K->33943K(4091904K), 0.0252550 secs] [Times: user=0.10 sys=0.02,real=0.03 secs]

Heapafter GC invocations=2 (full 0):

par new generation   total 204800K, used 33943K [0x00000006f3800000,0x0000000706400000, 0x0000000706400000)

 eden space 102400K,   0% used [0x00000006f3800000,0x00000006f3800000, 0x00000006f9c00000)

 from space 102400K,  33% used [0x00000006f9c00000,0x00000006fbd25e60, 0x0000000700000000)

 to  space 102400K,   0% used[0x0000000700000000, 0x0000000700000000, 0x0000000706400000)

concurrent mark-sweep generation total3887104K, used 0K [0x0000000706400000, 0x00000007f3800000, 0x00000007f3800000)

concurrent-mark-sweep perm gen total 204800K,used 32721K [0x00000007f3800000, 0x0000000800000000, 0x0000000800000000)

}

说明:上面的配置除官方推荐数据,其余皆为举例说明用,具体数据按实际需要测试而定

GCFull GC日志输出图解

Jvm性能调优与监控_虚拟机_02

Jvm性能调优与监控_监控_03

三、jvm监控工具jconsole.exe

Jconsole.exe是java自带的jvm监控工具,设置其对java的连接分析:

1、本地程序(相对于开启JConsole的计算机),无需设置任何参数就可以被本地开启的JConsole连接(Java SE 6开始无需设置,之前还是需要设置运行时参数-Dcom.sun.management.jmxremote )

2、在catalina.sh或startup.sh中设置无认证连接 (下面的设置表示:连接的端口为10900、无需认证就可以被连接)

-Dcom.sun.management.jmxremote.port=10900\

-Dcom.sun.management.jmxremote.authenticate=false\

-Dcom.sun.management.jmxremote.ssl=false

运行C:\ProgramFiles\Java\jdk1.7.0_01\bin\jconsole.exe

Jvm性能调优与监控_虚拟机_04

概览:


Jvm性能调优与监控_Java_05

内存:

Jvm性能调优与监控_监控_06

线程:

Jvm性能调优与监控_垃圾回收_07

类:

Jvm性能调优与监控_规模_08

VM概要:

Jvm性能调优与监控_监控_09

MBean:

Jvm性能调优与监控_虚拟机_10