jvm工具

jps显示系统中所有Hotspot虚拟机进程
jstat收集Hotspot虚拟机各方面运行数据
jstack显示虚拟机的线程栈信息
jinfo显示虚拟机的配置信息
jmap用于生成虚拟机的内存快照信息

jvm内存结构

左边蓝色是虚存(应用运行时额外使用的内存,主要是系统级线程栈)、右边是实存(可以通过jvm参数控制)

  • 方法栈&本地方法栈:
    线程创建时产生,方法执行时生成栈帧
  • 方法区
    存储类的元数据信息 常量等

  • java代码中所有的new操作
  • native Memory(C heap)
    Direct Bytebuffer JNI Compile GC;

堆内存分配

       JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指 定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。

 

组成详解
Young Generation即图中的Eden + From Space + To Space

Eden

存放新生的对象

Survivor Space

有两个,存放每次垃圾回收后存活的对象
Old GenerationTenured Generation 即图中的Old Space
主要存放应用程序中生命周期长的存活对象
 持久代内存分配(了解,对于垃圾回收主要集中在新生代和老生代)
      JVM使用-XX:PermSize设置持久代内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大持久代内存的大小,默认是物理内存的1/4。

组成详解
Permanent Generation保存虚拟机自己的静态(refective)数据
主要存放加载的Class类级别静态对象如class本身,method,field等等
permanent generation空间不足会引发full GC(详见HotSpot VM GC种类)
Code Cache用于编译和保存本地代码(native code)的内存
JVM内部处理或优化

JVM内存限制(最大值)

      JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然 可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统 下为2G-3G),而64bit以上的处理器就不会有限制了。

Java启动参数共分为三类;
其一是标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容;如:-classpath  ; -jar ; -help
其二是非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容;-Xms; -Xmx ; -Xss
其三是非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用;-XX:permSize ; -XX:MaxPermSize

行为参数(Behavioral Options):用于改变jvm的一些基础行为;
性能调优(Performance Tuning):用于jvm的性能调优;
调试参数(Debugging Options):一般用于打开跟踪、打印、输出等jvm参数,用于显示jvm更加详细的信息;

由于sun官方文档中对各参数的描述也都非常少(大多只有一句话),而且大多涉及OS层面的东西,很难描述清楚,所以以下是挑选了一些我们开发中可能会用得比较多的配置项,若需要查看所有参数列表,可以点击HotSpot VM Specific Options.查看原文;

首先来介绍行为参数


参数及其默认值描述
-XX:-DisableExplicitGC禁止调用System.gc();但jvm的gc仍然有效
-XX:+MaxFDLimit最大化文件描述符的数量限制
-XX:+ScavengeBeforeFullGC新生代GC优先于Full GC执行
-XX:+UseGCOverheadLimit在抛出OOM之前限制jvm耗费在GC上的时间比例
-XX:-UseConcMarkSweepGC对老生代采用并发标记交换算法进行GC(web项目一般使用这个)
-XX:-UseParallelGC启用并行GC
-XX:-UseParallelOldGC对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用
-XX:-UseSerialGC启用串行GC
-XX:+UseThreadPriorities启用本地线程优先级







加颜色三个参数代表着jvm中GC执行的三种方式,即串行收集器、并行收集器、并发收集器
串行(SerialGC)是jvm的默认GC方式,一般适用于小型应用和单处理器算法比较简单,GC效率也较高,但可能会给应用带来
停顿当垃圾回收程序运行时,需要暂停运行环境,只能垃圾回收程序运行);
并行(ParallelGC)是指GC运行时,对吞吐量有高的要求的应用程序,GC和app两者的线程在并发执行,这样可以最大限度不影响app的运行;
并发(ConcMarkSweepGC)是指多个线程并发执行GC,一般适用于多处理器系统中,可以提高GC的效率,但算法复杂,系统消耗较大;


并行,强调同时, 并发,强调多个


性能调优参数列表:

参数及其默认值描述
-XX:LargePageSizeInBytes=4m设置用于Java堆的大页面尺寸
-XX:MaxHeapFreeRatio=70GC后java堆中空闲量占的最大比例
-XX:MaxNewSize=size新生成对象能占用内存的最大值
-XX:MaxPermSize=64m老生代对象能占用内存的最大值
-XX:MinHeapFreeRatio=40GC后java堆中空闲量占的最小比例
-XX:NewRatio=2新生代内存容量与老生代内存容量的比例
-XX:NewSize=2.125m新生代对象生成时占用内存的默认值
-XX:ReservedCodeCacheSize=32m保留代码占用的内存容量
-XX:ThreadStackSize=512设置线程栈大小,若为0则使用系统默认值
-XX:+UseLargePages使用大页面内存









我们在日常性能调优中基本上都会用到以上加颜色的这几个属性;



调试参数列表:

参数及其默认值描述
-XX:-CITime打印消耗在JIT编译的时间
-XX:ErrorFile=./hs_err_pid<pid>.log保存错误日志或者数据到文件中
-XX:-ExtendedDTraceProbes开启solaris特有的dtrace探针
-XX:HeapDumpPath=./java_pid<pid>.hprof指定导出堆信息时的路径或文件名
-XX:-HeapDumpOnOutOfMemoryError当首次遭遇OOM时导出此时堆中相关信息
-XX:OnError="<cmd args>;<cmd args>"出现致命ERROR之后运行自定义命令
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"当首次遭遇OOM时执行自定义命令
-XX:-PrintClassHistogram遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同
-XX:-PrintConcurrentLocks遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同
-XX:-PrintCommandLineFlags打印在命令行中出现过的标记
-XX:-PrintCompilation当一个方法被编译时打印相关信息
-XX:-PrintGC每次GC时打印相关信息
-XX:-PrintGC Details每次GC时打印详细信息
-XX:-PrintGCTimeStamps打印每次GC的时间戳
-XX:-TraceClassLoading跟踪类的加载信息
-XX:-TraceClassLoadingPreorder跟踪被引用到的所有类的加载信息
-XX:-TraceClassResolution跟踪常量池
-XX:-TraceClassUnloading跟踪类的卸载信息
-XX:-TraceLoaderConstraints跟踪类加载器约束的相关信息
 














对于JVM的内存写过的文章已经有点多了,而且有点烂了,不过说那么多大多数在解决OOM的情况,于此,本文就只阐述这个内容,携带一些分析和理解和部分扩展内容,也就是JVM宕机中的一些问题,OK,下面说下OOM的常见情况:

第一类内存溢出,也是大家认为最多,第一反应认为是的内存溢出,就是堆栈溢出:

那什么样的情况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了:

java.lang.OutOfMemoryError: ......java heap space.....

也就是当你看到heap相关的时候就肯定是堆栈溢出了,此时如果代码没有问题的情况下,适当调整-Xmx和-Xms是可以避免的,不过一定是代码没有问题的前提,为什么会溢出呢,要么代码有问题,要么访问量太多并且每个访问的时间太长或者数据太多,导致数据释放不掉,因为垃圾回收器是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,自然不会去回收了;注意这个溢出之前,可能系统会提前先报错关键字为:

java.lang.OutOfMemoryError:GC over head limit exceeded

这种情况是当系统处于高频的GC状态,而且回收的效果依然不佳的情况,就会开始报这个错误,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致,但是java heap space的内存溢出有可能提前不会报这个错误,也就是可能内存就直接不够导致,而不是高频GC.


第二类内存溢出,PermGen的溢出,或者PermGen 满了的提示,你会看到这样的关键字:


java.lang.OutOfMemoryError: PermGen space(首先考虑spring使用大量动态反射生成的类被不断加载造成perm满

原因:系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法,导致常量池的膨胀,虽然JDK 1.5以后可以通过设置对永久带进行回收,但是我们希望的是这个地方是不做GC的,它够用就行,所以一般情况下今年少做类似的操作,所以在面对这种情况常用的手段是:增加-XX:PermSize和-XX:MaxPermSize的大小。


第三类内存溢出:使用ByteBuffer中的allocateDirect()的时候会用到,很多javaNIO的框架中被封装为其他的方法

溢出关键字:

java.lang.OutOfMemoryError: Direct buffer memory

如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,如果常规的应用程序你要将一个文件的内容输出到客户端需要通过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),然后再输出到直接内存由操作系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存可以直接由应用程序自己控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,所以要注意了哦。

如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize


第四类内存溢出错误:

溢出关键字:

java.lang.StackOverflowError(说明栈值太小了)

这个参数直接说明一个内容,就是-Xss太小了,我们申请很多局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4以前默认是256K,1.5以后是1M,如果报这个错,只能说明-Xss设置得太小,当然有些厂商的JVM不是这个参数,本文仅仅针对Hotspot VM而已;不过在有必要的情况下可以对系统做一些优化,使得-Xss的值是可用的。

第五类内存溢出错误:

溢出关键字:

java.lang.OutOfMemoryError: unable to create new native thread

上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了,说明了原因,如何去修改,不用我多说,你懂的。

第六类内存溢出:

溢出关键字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

这类错误一般是由于地址空间不够而导致。

六大类常见溢出已经说明JVM中99%的溢出情况,要逃出这些溢出情况非常困难,除非一些很怪异的故障问题会发生,比如由于物理内存的硬件问题,导致了code cache的错误(在由byte code转换为native code的过程中出现,但是概率极低),这种情况内存 会被直接crash掉,类似还有swap的频繁交互在部分系统中会导致系统直接被crash掉,OS地址空间不够的话,系统根本无法启动,呵呵;JNI的滥用也会导致一些本地内存无法释放的问题,所以尽量避开JNI;socket连接数据打开过多的socket也会报类似:IOException: Too many open files等错误信息。

JNI就不用多说了,尽量少用,除非你的代码太牛B了,我无话可说,呵呵,这种内存如果没有在被调用的语言内部将内存释放掉(如C语言),那么在进程结束前这些内存永远释放不掉,解决办法只有一个就是将进程kill掉。

另外GC本身是需要内存空间的,因为在运算和中间数据转换过程中都需要有内存,所以你要保证GC的时候有足够的内存哦,如果没有的话GC的过程将会非常的缓慢。

顺便这里就提及一些新的CMS GC的内容和策略(有点乱,每次写都很乱,但是能看多少看多少吧):

首先我再写一次一前博客中的已经写过的内容,就是很多参数没啥建议值,建议值是自己在现场根据实际情况科学计算和测试得到的综合效果,建议值没有绝对好的,而且默认值很多也是有问题的,因为不同的版本和厂商都有很大的区别,默认值没有永久都是一样的,就像-Xss参数的变化一样,要看到你当前的java程序heap的大致情况可以这样看看(以下参数是随便设置的,并不是什么默认值):

$sudo jmap -heap `pgrep java` 
Attaching to process ID 4280, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 134217728 (128.0MB)
MaxNewSize = 134217728 (128.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 134217728 (128.0MB)
MaxPermSize = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 85721088 (81.75MB)
used = 22481312 (21.439849853515625MB)
free = 63239776 (60.310150146484375MB)
26.22611602876529% used
From Space:
capacity = 24051712 (22.9375MB)
used = 478488 (0.45632171630859375MB)
free = 23573224 (22.481178283691406MB)
1.9894134770946867% used
To Space:
capacity = 24248320 (23.125MB)
used = 0 (0.0MB)
free = 24248320 (23.125MB)
0.0% used
PS Old Generation
capacity = 939524096 (896.0MB)
used = 16343864 (15.586723327636719MB)
free = 923180232 (880.4132766723633MB)
1.7395896571023124% used
PS Perm Generation
capacity = 134217728 (128.0MB)
used = 48021344 (45.796722412109375MB)
free = 86196384 (82.20327758789062MB)
35.77868938446045% used

付:sudo是需要拿到管理员权限,如果你的系统权限很大那么就不需要了,最后的grep java那个内容如果不对,可以直接通过jps或者ps命令将和java相关的进程号直接写进去,如:java -map 4280,这个参数其实完全可以通过jstat工具来替代,而且看到的效果更加好,这个参数在线上应用中,尽量少用(尤其是高并发的应用中),可能会触发JVM的bug,导致应用挂起;在jvm 1.6u14后可以编写任意一段程序,然后在运行程序的时候,增加参数为:-XX:+PrintFlagsFinal来输出当前JVM中运行时的参数值,或者通过jinfo来查看,jinfo是非常强大的工具,可以对部分参数进行动态修改,当然内存相关的东西是不能修改的,只能增加一些不是很相关的参数,有关JVM的工具使用,后续文章中如果有机会我们再来探讨,不是本文的重点;补充:关于参数的默认值对不同的JVM版本、不同的厂商、运行于不同的环境(一般和位数有关系)默认值会有区别。

OK,再说下反复的一句,没有必要的话就不要乱设置参数,参数不是拿来玩的,默认的参数对于这门JDK都是有好处的,关键是否适合你的应用场景,一般来讲你常规的只需要设置以下几个参数就可以了:

-server 表示为服务器端,会提供很多服务器端默认的配置,如并行回收,而服务器上一般这个参数都是默认的,所以都是可以省掉,与之对应的还有一个-client参数,一般在64位机器上,JVM是默认启动-server参数,也就是默认启动并行GC的,但是是ParallelGC而不是ParallelOldGC,两者算法不同(后面会简单说明下),而比较特殊的是windows 32位上默认是-client,这两个的区别不仅仅是默认的参数不一样,在jdk包下的jre包下一般会包含client和server包,下面分别对应启动的动态链接库,而真正看到的java、javac等相关命令指示一个启动导向,它只是根据命令找到对应的JVM并传入jvm中进行启动,也就是看到的java.exe这些文件并不是jvm;说了这么多,最终总结一下就是,-server和-client就是完全不同的两套VM,一个用于桌面应用,一个用于服务器的。

-Xmx 为Heap区域的最大值

-Xms 为Heap区域的初始值,线上环境需要与-Xmx设置为一致,否则capacity的值会来回飘动,飘得你心旷神怡,你懂的。

-Xss(或-ss) 这个其实也是可以默认的,如果你真的觉得有设置的必要,你就改下吧,1.5以后是1M的默认大小(指一个线程的native空间),如果代码不多,可以设置小点来让系统可以接受更大的内存。注意,还有一个参数是-XX:ThreadStackSize,这两个参数在设置的过程中如果都设置是有冲突的,一般按照JVM常理来说,谁设置在后面,就以谁为主,但是最后发现如果是在1.6以上的版本,-Xss设置在后面的确都是以-Xss为主,但是要是-XX:ThreadStackSize设置在后面,主线程还是为-Xss为主,而其它线程以-XX:ThreadStackSize为主,主线程做了一个特殊判定处理;单独设置都是以本身为主,-Xss不设置也不会采用其默认值,除非两个都不设置会采用-Xss的默认值。另外这个参数针对于hotspot的vm,在IBM的jvm中,还有一个参数为-Xoss,主要原因是IBM在对栈的处理上有操作数栈和方法栈等各种不同的栈种类,而hotspot不管是什么栈都放在一个私有的线程内部的,不区分是什么栈,所以只需要设置一个参数,而IBM的J9不是这样的;有关栈上的细节,后续我们有机会专门写文章来说明。

-XX:PermSize-XX:MaxPermSize两个包含了class的装载的位置,或者说是方法区(但不是本地方法区),在Hotspot默认情况下为64M,主意全世界的JVM只有hostpot的VM才有Perm的区域,或者说只有hotspot才有对用户可以设置的这块区域,其他的JVM都没有,其实并不是没有这块区域,而是这块区域没有让用户来设置,其实这块区域本身也不应该让用户来设置,我们也没有一个明确的说法这块空间必须要设置多大,都是拍脑袋设置一个数字,如果发布到线上看下如果用得比较多,就再多点,如果用的少,就减少点,而这块区域和性能关键没有多大关系,只要能装下就OK,并且时不时会因为Perm不够而导致Full GC,所以交给开发者来调节这个参数不知道是怎么想的;所以Oracle将在新一代JVM中将这个区域彻底删掉,也就是对用户透明,G1的如果真正稳定起来,以后JVM的启动参数将会非常简单,而且理论上管理再大的内存也是没有问题的,其实G1(garbage first,一种基于region的垃圾收集回收器)已经在hotspot中开始有所试用,不过目前效果不好,还不如CMS呢,所以只是试用,G1已经作为ORACLE对JVM研发的最高重点,CMS自现在最高版本后也不再有新功能(可以修改bug),该项目已经进行5年,尚未发布正式版,CMS是四五年前发布的正式版,但是是最近一两年才开始稳定,而G1的复杂性将会远远超越CMS,所以要真正使用上G1还有待考察,全世界目前只有IBM J9真正实现了G1论文中提到的思想(论文于05年左右发表),IBM已经将J9应用于websphere中,但是并不代表这是全世界最好的jvm,全世界最好的jvm是Azul(无停顿垃圾回收算法和一个零开销的诊断/监控工具),几乎可以说这个jvm是没有暂停的,在全世界很多顶尖级的公司使用,不过价格非常贵,不能直接使用,目前这个jvm的主导者在研究JRockit,而目前hotspot和JRockit都是Oracle的,所以他们可能会合并,所以我们应该对JVM的性能充满信心。

也就是说你常用的情况下只需要设置4个参数就OK了,除非你的应用有些特殊,否则不要乱改,那么来看看一些其他情况的参数吧:

先来看个不大常用的,就是大家都知道JVM新的对象应该说几乎百分百的在Eden里面,除非Eden真的装不下,我们不考虑这种变态的问题,因为线上环境Eden区域都是不小的,来降低GC的次数以及全局 GC的概率;而JVM习惯将内存按照较为连续的位置进行分配,这样使得有足够的内存可以被分配,减少碎片,那么对于内存最后一个位置必然就有大量的征用问题,JVM在高一点的版本里面提出了为每个线程分配一些私有的区域来做来解决这个问题,而1.5后的版本还可以动态管理这些区域,那么如何自己设置和查看这些区域呢,看下英文全称为:Thread Local Allocation Buffer,简称就是:TLAB,即内存本地的持有的buffer,设置参数有:

-XX:+UseTLAB 启用这种机制的意思
-XX:TLABSize=<size in kb> 设置大小,也就是本地线程中的私有区域大小(只有这个区域放不下才会到Eden中去申请)。
-XX:+ResizeTLAB 是否启动动态修改

这几个参数在多CPU下非常有用。

-XX:+PrintTLAB 可以输出TLAB的内容。

下面再闲扯些其它的参数:

如果你需要对Yong区域进行并行回收应该如何修改呢?在jdk1.5以后可以使用参数:

-XX:+UseParNewGC

注意: 与它冲突的参数是:-XX:+UseParallelOldGC-XX:+UseSerialGC,如果需要用这个参数,又想让整个区域是并行回收的,那么就使用-XX:+UseConcMarkSweepGC参数来配合,其实这个参数在使用了CMS后,默认就会启动该参数,也就是这个参数在CMS GC下是无需设置的,后面会提及到这些参数。

默认服务器上的对Full并行GC策略为(这个时候Yong空间回收的时候启动PSYong算法,也是并行回收的):

-XX:+UseParallelGC

另外,在jdk1.5后出现一个新的参数如下,这个对Yong的回收算法和上面一样,对Old区域会有所区别,上面对Old回收的过程中会做一个全局的Compact,也就是全局的压缩操作,而下面的算法是局部压缩,为什么要局部压缩呢?是因为JVM发现每次压缩后再逻辑上数据都在Old区域的左边位置,申请的时候从左向右申请,那么生命力越长的对象就一般是靠左的,所以它认为左边的对象就是生命力很强,而且较为密集的,所以它针对这种情况进行部分密集,但是这两种算法mark阶段都是会暂停的,而且存活的对象越多活着的越多;而ParallelOldGC会进行部分压缩算法(主意一点,最原始的copy算法是不需要经过mark阶段,因为只需要找到一个或活着的就只需要做拷贝就可以,而Yong区域借用了Copy算法,只是唯一的区别就是传统的copy算法是采用两个相同大小的内存来拷贝,浪费空间为50%,所以分代的目标就是想要实现很多优势所在,认为新生代85%以上的对象都应该是死掉的,所以S0和S1一般并不是很大),该算法为jdk 1.5以后对于绝大部分应用的最佳选择。

-XX:+UseParallelOldGC

-XX:ParallelGCThread=12:并行回收的线程数,最好根据实际情况而定,因为线程多往往存在征用调度和上下文切换的开销;而且也并非CPU越多线程数也可以设置越大,一般设置为12就再增加用处也不大,主要是算法本身内部的征用会导致其线程的极限就是这样。

设置Yong区域大小:

-Xmn Yong区域的初始值和最大值一样大

-XX:NewSize-XX:MaxNewSize如果设置以为一样大就是和-Xmn,在JRockit中会动态变化这些参数,根据实际情况有可能会变化出两个Yong区域,或者没有Yong区域,有些时候会生出来一个半长命对象区域;这里除了这几个参数外,还有一个参数是NewRatio是设置Old/Yong的倍数的,这几个参数都是有冲突的,服务器端建议是设置-Xmn就可以了,如果几个参数全部都有设置,-Xmn和-XX:NewSize与-XX:MaxNewSize将是谁设置在后面,以谁的为准,而-XX:NewSize -XX:MaxNewSize与-XX:NewRatio时,那么参数设置的结果可能会以下这样的(jdk 1.4.1后):

min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))

-XX:NewRatio为Old区域为Yong的多少倍,间接设置Yong的大小,1.6中如果使用此参数,则默认会在适当时候被动态调整,具体请看下面参数UseAdaptiveSizepollcy 的说明。

三个参数不要同时设置,因为都是设置Yong的大小的。

-XX:SurvivorRatio:该参数为Eden与两个求助空间之一的比例,注意Yong的大小等价于Eden + S0 + S1,S0和S1的大小是等价的,这个参数为Eden与其中一个S区域的大小比例,如参数为8,那么Eden就占用Yong的80%,而S0和S1分别占用10%。

以前的老版本有一个参数为:-XX:InitialSurivivorRatio,如果不做任何设置,就会以这个参数为准,这个参数的默认值就是8,不过这个参数并不是Eden/Survivor的大小,而是Yong/Survivor,所以所以默认值8,代表每一个S区域的空间大小为Yong区域的12.5%而不是10%。另外顺便提及一下,每次大家看到GC日志的时候,GC日志中的每个区域的最大值,其中Yong的空间最大值,始终比设置的Yong空间的大小要小一点,大概是小12.5%左右,那是因为每次可用空间为Eden加上一个Survivor区域的大小,而不是整个Yong的大小,因为可用空间每次最多是这样大,两个Survivor区域始终有一块是空的,所以不会加上两个来计算。

-XX:MaxTenuringThreshold=15:在正常情况下,新申请的对象在Yong区域发生多少次GC后就会被移动到Old(非正常就是S0或S1放不下或者不太可能出现的Eden都放不下的对象),这个参数一般不会超过16(因为计数器从0开始计数,所以设置为15的时候相当于生命周期为16)。

要查看现在的这个值的具体情况,可以使用参数:-XX:+PrintTenuringDistribution

通过上面的jmap应该可以看出我的机器上的MinHeapFreeRatio和MaxHeapFreeRatio分别为40个70,也就是大家经常说的在GC后剩余空间小于40%时capacity开始增大,而大于70%时减小,由于我们不希望让它移动,所以这两个参数几乎没有意义,如果你需要设置就设置参数为:

-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70

JDK 1.6后有一个动态调节板块的,当然如果你的每一个板块都是设置固定值,这个参数也没有用,不过如果是非固定的,建议还是不要动态调整,默认是开启的,建议将其关掉,参数为:

-XX:+UseAdaptiveSizepollcy 建议使用-XX:-UseAdaptiveSizepollcy关掉,为什么当你的参数设置了NewRatio、Survivor、MaxTenuringThreshold这几个参数如果在启动了动态更新情况下,是无效的,当然如果你设置-Xmn是有效的,但是如果设置的比例的话,初始化可能会按照你的参数去运行,不过运行过程中会通过一定的算法动态修改,监控中你可能会发现这些参数会发生改变,甚至于S0和S1的大小不一样。

如果启动了这个参数,又想要跟踪变化,那么就使用参数:-XX:+PrintAdaptiveSizePolicy

上面已经提到,javaNIO中通过Direct内存来提高性能,这个区域的大小默认是64M,在适当的场景可以设置大一些。

-XX:MaxDirectMemorySize

一个不太常用的参数:

-XX:+ScavengeBeforeFullGC 默认是开启状态,在full GC前先进行minor GC。

对于java堆中如果要设置大页内存,可以通过设置参数:

付:此参数必须在操作系统的内核支持的基础上,需要在OS级别做操作为:

echo 1024 > /proc/sys/vm/nr_hugepages

echo 2147483647 > /proc/sys/kernel/shmmax

-XX:+UseLargePages

-XX:LargePageSizeInBytes

此时整个JVM都将在这块内存中,否则全部不在这块内存中。

javaIO的临时目录设置

-Djava.io.tmpdir

jstack会去寻找/tmp/hsperfdata_admin下去寻找与进程号相同的文件,32位机器上是没有问题的,64为机器的是有BUG的,在jdk 1.6u23版本中已经修复了这个bug,如果你遇到这个问题,就需要升级JDK了。

还记得上次说的平均晋升大小吗,在并行GC时,如果平均晋升大小大于old剩余空间,则发生full GC,那么当小于剩余空间时,也就是平均晋升小于剩余空间,但是剩余空间小于eden + 一个survivor的空间时,此时就依赖于参数:

-XX:-HandlePromotionFailure

启动该参数时,上述情况成立就发生minor gc(YGC),大于则发生full gc(major gc)。

一般默认直接分配的对象如果大于Eden的一半就会直接晋升到old区域,但是也可以通过参数来指定:

-XX:PretenureSizeThreshold=2m 我个人不建议使用这个参数

也就是当申请对象大于这个值就会晋升到old区域。

传说中GC时间的限制,一个是通过比例限制,一个是通过最大暂停时间限制,但是GC时间能限制么,呵呵,在增量中貌似可以限制,不过不能限制住GC总体的时间,所以这个参数也不是那么关键。

-XX:GCTimeRatio=

-XX:MaxGCPauseMillis

-XX:GCTimeLimit

要看到真正暂停的时间就一个是看GCDetail的日志,另一个是设置参数看:

-XX:+PrintGCApplicationStoppedTime

有些人,有些人就是喜欢在代码里面里头写System.gc(),耍酷,这个不是测试程序是线上业务,这样将会导致N多的问题,不多说了,你应该懂的,不懂的话看下书吧,而RMI是很不听话的一个鸟玩意,EJB的框架也是基于RMI写的,RMI为什么不听话呢,就是它自己在里面非要搞个System.gc(),哎,为了放置频繁的做,频繁的做,你就将这个命令的执行禁用掉吧,当然程序不用改,不然那些EJB都跑步起来了,呵呵:

-XX:+DisableExplicitGC 默认是没有禁用掉,写成+就是禁用掉的了,但是有些时候在使用allocateDirect的时候,很多时候还真需要System.gc来强制回收这块资源。

内存溢出时导出溢出的错误信息:
-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/xieyu/logs/ 这个参数指定导出时的路径,不然导出的路径就是虚拟机的目标位置,不好找了,默认的文件名是:java_pid<进程号>.hprof,这个文件可以类似使用jmap -dump:file=....,format=b <pid>来dump类似的内容,文件后缀都是hprof,然后下载mat工具进行分析即可(不过内存有多大dump文件就多大,而本地分析的时候内存也需要那么大,所以很多时候下载到本地都无法启动是很正常的),后续文章有机会我们来说明这些工具,另外jmap -dump参数也不要经常用,会导致应用挂起哦;另外此参数只会在第一次输出OOM的时候才会进行堆的dump操作(java heap的溢出是可以继续运行再运行的程序的,至于web应用是否服务要看应用服务器自身如何处理,而c heap区域的溢出就根本没有dump的机会,因为直接就宕机了,目前系统无法看到c heap的大小以及内部变化,要看大小只能间接通过看JVM进程的内存大小(top或类似参数),这个大小一般会大于heap+perm的大小,多余的部分基本就可以认为是c heap的大小了,而看内部变化呢只有google perftools可以达到这个目的),如果内存过大这个dump操作将会非常长,所以hotspot如果以后想管理大内存,这块必须有新的办法出来。

最后,用dump出来的文件,通过mat分析出来的结果往往有些时候难以直接确定到底哪里有问题,可以看到的维度大概有:那个类使用的内存最多,以及每一个线程使用的内存,以及线程内部每一个调用的类和方法所使用的内存,但是很多时候无法判定到底是程序什么地方调用了这个类或者方法,因为这里只能看到最终消耗内存的类,但是不知道谁使用了它,一个办法是扫描代码,但是太笨重,而且如果是jar包中调用了就不好弄了,另一种方法是写agent,那么就需要相应的配合了,但是有一个非常好的工具就是btrace工具(jdk 1.7貌似还不支持),可以跟踪到某个类的某个方法被那些类中的方法调用过,那这个问题就好说了,只要知道开销内存的是哪一个类,就能知道谁调用过它,OK,关于btrace的不是本文重点,网上都有,后续文章有机会再探讨,
原理:
No performance impact during runtime(无性能影响)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M内存就dump出512M的空间大小)
JVM is “dead” during dumping(死掉时dump)
Restarting JVM during this dump will cause unusable .hprof file(重启导致文件不可用)

注明的NUMA架构,在JVM中开始支持,当然也需要CPU和OS的支持才可以,需要设置参数为:

-XX:+UseNUMA 必须在并行GC的基础上才有的

老年代无法分配区域的最大等待时间为(默认值为0,但是也不要去动它):

-XX:GCExpandToAllocateDelayMillis

让JVM中所有的set和get方法转换为本地代码:

-XX:+UseFastAccessorMethods

以时间戳输出Heap的利用率

-XX:+PrintHeapUsageOverTime

在64bit的OS上面(其实一般达不到57位左右),由于指针会放大为8个byte,所以会导致空间使用增加,当然,如果内存够大,就没有问题,但是如果升级到64bit系统后,只是想让内存达到4G或者8G,那么就完全可以通过很多指针压缩为4byte就OK了,所以在提供以下参数(本参数于jdk 1.6u23后使用,并自动开启,所以也不需要你设置,知道就OK):

-XX:+UseCompressedOops 请注意:这个参数默认在64bit的环境下默认启动,但是如果JVM的内存达到32G后,这个参数就会默认为不启动,因为32G内存后,压缩就没有多大必要了,要管理那么大的内存指针也需要很大的宽度了。

后台JIT编译优化启动

-XX:+BackgroundCompilation

如果你要输出GC的日志以及时间戳,相关的参数有:

-XX:+PrintGCDetails 输出GC的日志详情,包含了时间戳

-XX:+PrintGCTimeStamps 输出GC的时间戳信息,按照启动JVM后相对时间的每次GC的相对秒值(毫秒在小数点后面),也就是每次GC相对启动JVM启动了多少秒后发生了这次GC

-XX:+PrintGCDateStamps输出GC的时间信息,会按照系统格式的日期输出每次GC的时间

-XX:+PrintGCTaskTimeStamps输出任务的时间戳信息,这个细节上比较复杂,后续有文章来探讨。

-XX:-TraceClassLoading 跟踪类的装载

-XX:-TraceClassUnloading 跟踪类的卸载

-XX:+PrintHeapAtGC 输出GC后各个堆板块的大小。

将常量信息GC信息输出到日志文件:

-Xloggc:/home/xieyu/logs/gc.log

现在面对大内存比较流行是是CMS GC(最少1.5才支持),首先明白CMS的全称是什么,不是传统意义上的内容管理系统(Content Management System)哈,第一次我也没看懂,它的全称是:Concurrent Mark Sweep,三个单词分别代表并发、标记、清扫(主意这里没有compact操作,其实CMS GC的确没有compact操作),也就是在程序运行的同时进行标记和清扫工作,至于它的原理前面有提及过,只是有不同的厂商在上面做了一些特殊的优化,比如一些厂商在标记根节点的过程中,标记完当前的根,那么这个根下面的内容就不会被暂停恢复运行了,而移动过程中,通过读屏障来看这个内存是不是发生移动,如果在移动稍微停一下,移动过去后再使用,hotspot还没这么厉害,暂停时间还是挺长的,只是相对其他的GC策略在面对大内存来讲是不错的选择。

下面看一些CMS的策略(并发GC总时间会比常规的并行GC长,因为它是在运行时去做GC,很多资源征用都会影响其GC的效率,而总体的暂停时间会短暂很多很多,其并行线程数默认为:(上面设置的并行线程数 + 3)/ 4

付:CMS是目前Hotspot管理大内存最好的JVM,如果是常规的JVM,最佳选择为ParallelOldGC,如果必须要以响应时间为准,则选择CMS,不过CMS有两个隐藏的隐患:

1、CMS GC虽然是并发且并行运行的GC,但是初始化的时候如果采用默认值92%JVM 1.5的白皮书上描述为68%其实是错误的,1.6是正确的),就很容易出现问题,因为CMS GC仅仅针对Old区域,Yong区域使用ParNew算法,也就是Old的CMS回收和Yong的回收可以同时进行,也就是回收过程中Yong有可能会晋升对象Old,并且业务也可以同时运行,所以92%基本开始启动CMS GC很有可能old的内存就不够用了,当内存不够用的时候,就启动Full GC,并且这个Full GC是串行的,所以如果弄的不好,CMS会比并行GC更加慢,为什么要启用串行是因为CMS GC、并行GC、串行GC的继承关系决定的,简单说就是它没办法去调用并行GC的代码,细节说后续有文章来细节说明),建议这个值设置为70%左右吧,不过具体时间还是自己决定。

2、CMS GC另一个大的隐患,其实不看也差不多应该清楚,看名字就知道,就是不会做Compact操作,它最恶心的地方也在这里,所以上面才说一般的应用都不使用它,它只有内存垃圾非常多,多得无法分配晋升的空间的时候才会出现一次compact,但是这个是Full GC,也就是上面的串行,很恐怖的,所以内存不是很大的,不要考虑使用它,而且它的算法十分复杂。

还有一些小的隐患是:和应用一起征用CPU(不过这个不是大问题,增加CPU即可)、整个运行过程中时间比并行GC长(这个也不是大问题,因为我们更加关心暂停时间而不是运行时间,因为暂停会影响非常多的业务)。

启动CMS为全局GC方法(注意这个参数也不能上面的并行GC进行混淆,Yong默认是并行的,上面已经说过

-XX:+UseConcMarkSweepGC

在并发GC下启动增量模式,只能在CMS GC下这个参数才有效。

-XX:+CMSIncrementalMode

启动自动调节duty cycle,即在CMS GC中发生的时间比率设置,也就是说这段时间内最大允许发生多长时间的GC工作是可以调整的。

-XX:+CMSIncrementalPacing

在上面这个参数设定后可以分别设置以下两个参数(参数设置的比率,范围为0-100):

-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10

增量GC上还有一个保护因子(CMSIncrementalSafetyFactor),不太常用;CMSIncrementalOffset提供增量GC连续时间比率的设置;CMSExpAvgFactor为增量并发的GC增加权重计算。

-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset= 
-XX:CMSExpAvgFactor=

是否启动并行CMS GC(默认也是开启的)

-XX:+CMSParallelRemarkEnabled

要单独对CMS GC设置并行线程数就设置(默认也不需要设置):

-XX:ParallelCMSThreads

对PernGen进行垃圾回收:

JDK 1.5在CMS GC基础上需要设置参数(也就是前提是CMS GC才有):

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

1.6以后的版本无需设置:-XX:+CMSPermGenSweepingEnabled,注意,其实一直以来Full GC都会触发对Perm的回收过程,CMS GC需要有一些特殊照顾,虽然VM会对这块区域回收,但是Perm回收的条件几乎不太可能实现,首先需要这个类的classloader必须死掉,才可以将该classloader下所有的class干掉,也就是要么全部死掉,要么全部活着;另外,这个classloader下的class没有任何object在使用,这个也太苛刻了吧,因为常规的对象申请都是通过系统默认的,应用服务器也有自己默认的classloader,要让它死掉可能性不大,如果这都死掉了,系统也应该快挂了。

CMS GC因为是在程序运行时进行GC,不会暂停,所以不能等到不够用的时候才去开启GC,官方说法是他们的默认值是68%,但是可惜的是文档写错了,经过很多测试和源码验证这个参数应该是在92%的时候被启动,虽然还有8%的空间,但是还是很可怜了,当CMS发现内存实在不够的时候又回到常规的并行GC,所以很多人在没有设置这个参数的时候发现CMS GC并没有神马优势嘛,和并行GC一个鸟样子甚至于更加慢,所以这个时候需要设置参数(这个参数在上面已经说过,启动CMS一定要设置这个参数):

-XX:CMSInitiatingOccupancyFraction=70

这样保证Old的内存在使用到70%的时候,就开始启动CMS了;如果你真的想看看默认值,那么就使用参数:-XX:+PrintCMSInitiationStatistics 这个变量只有JDK 1.6可以使用 1.5不可以,查看实际值-XX:+PrintCMSStatistics;另外,还可以设置参数-XX:CMSInitiatingPermOccupancyFraction来设置Perm空间达到多少时启动CMS GC,不过意义不大。

JDK 1.6以后有些时候启动CMS GC是根据计算代价进行启动,也就是不一定按照你指定的参数来设置的,如果你不想让它按照所谓的成本来计算GC的话,那么你就使用一个参数:-XX:+UseCMSInitiatingOccupancyOnly,默认是false,它就只会按照你设置的比率来启动CMS GC了。如果你的程序中有System.gc以及设置了ExplicitGCInvokesConcurrent在jdk 1.6中,这种情况使用NIO是有可能产生问题的。

启动CMS GC的compation操作,也就是发生多少次后做一次全局的compaction:

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:发生多少次CMS Full GC,这个参数最好不要设置,因为要做compaction的话,也就是真正的Full GC是串行的,非常慢,让它自己去决定什么时候需要做compaction。

-XX:CMSMaxAbortablePrecleanTime=5000 设置preclean步骤的超时时间,单位为毫秒,preclean为cms gc其中一个步骤,关于cms gc步骤比较多,本文就不细节探讨了。

并行GC在mark阶段,可能会同时发生minor GC,old区域也可能发生改变,于是并发GC会对发生了改变的内容进行remark操作,这个触发的条件是:

-XX:CMSScheduleRemarkEdenSizeThreshold

-XX:CMSScheduleRemarkEdenPenetration

即Eden区域多大的时候开始触发,和eden使用量超过百分比多少的时候触发,前者默认是2M,后者默认是50%。

但是如果长期不做remark导致old做不了,可以设置超时,这个超时默认是5秒,可以通过参数:

-XX:CMSMaxAbortablePrecleanTime

-XX:+ExplicitGCInvokesConcurrent 在显示发生GC的时候,允许进行并行GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 几乎和上面一样,只不过多一个对Perm区域的回收而已。

补充:

其实JVM还有很多的版本,很多的厂商,与其优化的原则,随便举两个例子hotspot在GC中做的一些优化(这里不说代码的编译时优化或运行时优化):

Eden申请的空间对象由Old区域的某个对象的一个属性指向(也就是Old区域的这个空间不回收,Eden这块就没有必要考虑回收),所以Hotspot在CPU写上面,做了一个屏障,当发生赋值语句的时候(对内存来讲赋值就是一种写操作),如果发现是一个新的对象由Old指向Eden,那么就会将这个对象记录在一个卡片机里面,这个卡片机是有很多512字节的卡片组成,当在YGC过程中,就基本不会去移动或者管理这块对象(付:这种卡片机会在CMS GC的算法中使用,不过和这个卡片不是放在同一个地方的,也是CMS GC的关键,对于CMS GC的算法细节描述,后续文章我们单独说明)。

Old区域对于一些比较大的对象,JVM就不会去管理个对象,也就是compact过程中不会去移动这块对象的区域等等吧。

以上大部分参数为hotspot的自带关于性能的参数,参考版本为JDK 1.5和1.6的版本,很多为个人经验说明,不足以说明所有问题,如果有问题,欢迎探讨;另外,JDK的参数是不是就只有这些呢,肯定并不是,我知道的也不止这些,但是有些觉得没必要说出来的参数和一些数学运算的参数我就不想给出来了,比如像禁用掉GC的参数有神马意义,我们的服务器要是把这个禁用掉干个屁啊,呵呵,做测试还可以用这玩玩,让它不做GC直接溢出;还有一些什么计算因子啥的,还有很多复杂的数学运算规则,要是把这个配置明白了,就太那个了,而且一般情况下也没那个必要,JDK到现在的配置参数多达上500个以上,要知道完的话慢慢看吧,不过意义不大,而且要知道默认值最靠谱的是看源码而不是看文档,官方文档也只能保证绝大部是正确的,不能保证所有的是正确的。

本文最后追加在jdk 1.6u 24后通过上面说明的-XX:+PrintFlagsFinal输出的参数以及默认值(还是那句话,在不同的平台上是不一样的),输出的参数如下,可以看看JVM的参数是相当的多,参数如此之多,你只需要掌握关键即可,参数还有很多有冲突的,



  1. 堆大小设置
    JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
    典型设置:
    • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
      -
      Xmx3550m:设置JVM最大可用内存为3550M。
      -Xms3550m
      :设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
      -Xmn2g
      :设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
      -Xss128k
      :设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
    • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
      -XX:NewRatio=4
      :设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
      -XX:SurvivorRatio=4
      :设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
      -XX:MaxPermSize=16m:设置持久代大小为16m。
      -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  2. 回收器选择
    JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。
    1. 吞吐量优先的并行收集器
      如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
      典型配置
      • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
        -XX:+UseParallelGC
        :选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
        -XX:ParallelGCThreads=20
        :配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
        -XX:+UseParallelOldGC
        :配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100
        -XX:MaxGCPauseMillis=100
        :设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
        -XX:+UseAdaptiveSizePolicy
        :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
    2. 响应时间优先的并发收集器
      如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
      典型配置
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
        -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
        -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
        -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
        -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
  3. 辅助信息
    JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
    • -XX:+PrintGC
      输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]

                      [Full GC 121376K->10414K(130112K), 0.0650971 secs]

    • -XX:+PrintGCDetails
      输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

                      [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]

    • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
      输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
    • -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
      输出形式:Application time: 0.5291524 seconds
    • -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
      输出形式:Total time for which application threads were stopped: 0.0468229 seconds
    • -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
      输出形式:
      34.702: [GC {Heap before gc invocations=7:
       def new generation   total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K,  99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
      from space 6144K,  55% used [0x221d0000, 0x22527e10, 0x227d0000)
        to   space 6144K,   0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
       tenured generation   total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
      the space 69632K,   3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
       compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
         the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
          ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
          rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
       def new generation   total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K,   0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
        from space 6144K,  55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
        to   space 6144K,   0% used [0x221d0000, 0x221d0000, 0x227d0000)
       tenured generation   total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
      the space 69632K,   4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
       compacting perm gen  total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
         the space 8192K,  35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
          ro space 8192K,  66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
          rw space 12288K,  46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      }
      , 0.0757599 secs]
    • -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。
  4. 常见配置汇总
    1. 堆设置
      • -Xms:初始堆大小
      • -Xmx:最大堆大小
      • -XX:NewSize=n:设置年轻代大小
      • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
      • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
      • -XX:MaxPermSize=n:设置持久代大小
    2. 收集器设置
      • -XX:+UseSerialGC:设置串行收集器
      • -XX:+UseParallelGC:设置并行收集器
      • -XX:+UseParalledlOldGC:设置并行年老代收集器
      • -XX:+UseConcMarkSweepGC:设置并发收集器
    3. 垃圾回收统计信息
      • -XX:+PrintGC
      • -XX:+PrintGCDetails
      • -XX:+PrintGCTimeStamps
      • -Xloggc:filename
    4. 并行收集器设置
      • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
      • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
      • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
    5. 并发收集器设置
      • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
      • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。


四、调优总结

  1. 年轻代大小选择
    • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  2. 年老代大小选择
    • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
      • 并发垃圾收集信息
      • 持久代并发收集次数
      • 传统GC信息
      • 花在年轻代和年老代回收上的时间比例
      减少年轻代和年老代花费的时间,一般会提高应用的效率
    • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  3. 较小堆引起的碎片问题
    因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
    • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
    • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

内存调优的目的:对于JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,重点是减少

Full GC的次数,因为它会对整个堆进行整理,导致Full GC一般由以下几种情况:

  ①:年老代空间不足

  ②:Permanet Generation空间不足

  ③:统计得到的GC后晋升到年老代的平均大小大于年老代剩余空间

  ④:System.gc()被显示调用

 

内存调优的手段:通过控制堆内存的各个部分的比例和GC策略来实现的。


JVM的常见配置:

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:设置年轻代大小

-XX:newRatio:设置年轻代和年老代的比例。如:为3,表示年轻代与年老代比值为3(一般

年轻代占堆内存的三分之一比较合适),此时年轻代占整个年轻代和年老代和的四分之一

-XX:SurvivorRatio=n:年轻代中有Eden区与两个Survivor区(From space和To space)的比

值,如:3,表示Eden:Survivor=3:2,因此一个Suuvivor区占整个年轻代的五分之一

-XX:MaxPermSize=n:设置持久代(用于存放方法区数据)的大小。


收集器的设置:

-XX:+UseSerialGC:设置串行收集器

-XX:+UseParallelGC:设置并行收集器

-XX:+UseParalledlOldGC:设置并行年老代收集器

-XX:+UseConcMarkSweepGC:设置并发收集器


垃圾回收统计信息:

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename


并行收集器设置:

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式:1/(N+1)


并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况

-XX:+ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时使用的CPU数。并行收集线程数


本文转载:CSDN博客