友情链接
查看JVM加载的类及对应类加载器的方法
Java内存模型
在进行调优之间,必须得对Java内存模型有一个较深的认识才行,否则会一脸懵逼。
图中的持久代(也叫永久代)在JDK1.8就没有了,取而代之的时候MetaSpace
关于JVM栈,分享一个博客:https://blog.csdn.net/zq602316498/article/details/38926607
我想上图已经表达得很清楚,只对年轻代和老年代再做一下详细的描述,并提一下一个Java对象申请时的过程
年轻代有Eden+from+to三部分组成,其中Eden是对象第一次申请时存放的地方,而from和to(他们是survivor区)是存放经过小于等于N次垃圾回收后仍然存活的对象,老年代则是存放的经过大于N次GC依然存活的对象。
垃圾收集
算法
标记-清除:将失效的引用标记为可回收,然后在内存中清楚,会产生大量的内存碎片
复制:将内存分为两部分,将存活的对象复制到另外一块内存中,避免内存碎片产生,但如果复制的对象过多会导致性能下降
标记-整理(压缩):将存活的对象移动到内存一端连续空间中
分代收集:将内存划分为年轻代、老年代、永久代,不同代使用不同的收集算法。如:Eden不足,复制到survivor,surviror不足复制到老年代,如果老年代不足则触发full fc或者cms,如果还不足就会出现oom,如果新开辟的空间eden都不能满足,则直接晋升到老年代
垃圾收集器
因为年轻代里面的对象通常是生命周期很短的,只有少部分还处于存活状态,所以使用标记-复制更有效率,而老年代则相反,如果使用复制返回空间消耗很大,效率更低,所以它常用标记-整理算法。这里提到的不是绝对的哈。
串行
会出现STW,亦即垃圾收集器线程工作时,应用程序线程是暂停的
serial:年轻代串行垃圾收集器,适合单核CPU,使用复制算法
serial old:老年代串行垃圾收集器,适合单核CPU,使用标记-整理算法
并行
会出现STW,亦即垃圾收集器线程工作时,应用程序线程是暂停的
parnew:年轻代,适合多核CPU,使用复制算法
parallel old:老年代,适合多核CPU,使用标记-整理算法
吞吐量优先收集器
parallel scanvenge
会出现STW,亦即垃圾收集器线程工作时,应用程序线程是暂停的
在并行收集器起基础上演变而来的,允许设置STW的时间,提高应用程序的响应,提高吞吐量。比如:
#收集时间上线
-XX:MaxGCPauseMillis
#GC时间占总时间的比例,用来控制吞吐量
-XX:GCTimeRatio
#自动分代大小调接策略
-XX:UseAdaptiveSizePolicy
#屏蔽System.gc()
-XX:DisableExplicitGC
堆类型
在JVM参数中指定了垃圾收集器类型,就决定过来堆的类型。JVM会按照如下顺序选择堆的类型:
1.UseParallelGC-ParallelScanvengeHeap
2.UseG1GC-G1CollectedHeap,这样的配置,收集策略只能是G1CollectorPolicy_BestRegionsFirst
3.如果上面两个选项都么有设置,则将选择堆类型为GenCollectedHeap,而对于收集策略,还可以进行细分:
默认配置ConcurrentMarkSweepPolicy,老年代的收集使用标记-清除算法
若配置UseSerialGC,将选择MarkSweepPolicy
若配置UseConsMarkSweepGC,将选择ConcurrentMarkSweepPolicy,若开启了自适应策略选项UseAdaptiveSizePolicy,则选择ASConcurrentMarkSweepPolicy作为收集策略
收集策略
JVM选择都是分代收集,而分代收集又有很多种策略,一种策略针对每个分代指定其收集算法。
常用的收集策略有两种
收集策略
新生代
老年代
永久代
MarkSweepPolicy
多核:ParNew,否则DefNew
MarkSweepCompact
MarkSweepCompact
ConcurrentMarkSweepPolicy
DefNew
ConcurrentMarkSweep
ConcurrentMarkSweep
ConcurrentMarkSweepPolicy
ASParNew/ParNew
ASConcurrentMarkSweep
ConcurrentMarkSweep
内存和垃圾收集的学习,可以开始探索如何根据日志和可视化工具对JVM进行调优
性能分析
开启GC日志
开启GC日志方式,是在启动程序是加上命令参数“-verbose:gc”
GC日志相关参数
选项
默认值
输出
-XX:+PrintGC
false
等同于“-verbose:gc”
-XX:+PrintGCDetails
false
GC时处处更多细节信息
-XX:+PrintGCDateStamps
false
GC操作的日期戳信息,相对于时间戳,这个是GST时间
-XX:+PrintGCTimeStamps
false
GC操作的时间戳信息
-XX:+PrintGCTaskTimeStamps
false
GC工作线程的时间戳信息
-Xloggc:
GC日志输出文件,一定要保证目录存在,否则无法生成文件
在sts(spring官方自己整合的eclipse版本)的sts.ini中配置,如:
-XX:MaxPermSize=512M
-Xms40m
-Xmx512m
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:D:/gc/gc.log
我故意做了堆的设置,让GC尽快发生,-Xms40m:初始堆内存为40兆
启动sts,可以看到在d:/gc目录下生成了gc.log
分析GC日志
从gc.log中拿了几行具有代表性的日志输出做讲解
第一行:JVM的版本信息
Java HotSpot(TM) 64-Bit Server VM (25.91-b14) for windows-amd64 JRE (1.8.0_91-b14), built on Apr 1 2016 00:58:32 by "java_re" with MS VC++ 10.0 (VS2010)
第二行:内存总体概况
Memory: 4k page, physical 16654448k(11836556k free), swap 19144816k(13972420k free)
# 内存: 内存分页大小4K, 物理内存 总大小(可用大小),交换区(windows叫虚拟内存)总大小(可用大小)
第三行:java启动命令行的参数
CommandLine flags: -XX:InitialHeapSize=41943040 -XX:MaxHeapSize=536870912 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
# 命令参数:初始堆大小40m,最大堆大小512m,打印gc日志,打印gc日期戳,打印gc详情,打印gc时间戳,开启压缩类指针,开启压缩普通对象指针,关闭内存大页面分配,使用并行收集器
第四行:真正的GC日志开始了
2018-06-04T15:32:53.442+0800: 0.690: [GC (Allocation Failure) [PSYoungGen: 10240K->1527K(11776K)] 10240K->2494K(39424K), 0.0039626 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
#GC发生的时间戳:从JVM启动到第一次GC的时间:[GC-年轻代GC(内存分配失败触发GC) [PS(Parallel Scanvenge)年轻代收集:收集前大小-收集后大小(总大小)] 堆收集前大小-堆收集后大小(对总大小),GC耗时],[耗时:GC应用程序耗时,GC系统耗时,GC实际耗时]
第五行
2018-06-04T15:32:54.375+0800: 1.623: [Full GC (Ergonomics) [PSYoungGen: 1536K->0K(22016K)] [ParOldGen: 25538K->21620K(47616K)] 27074K->21620K(69632K), [Metaspace: 8384K->8384K(1056768K)], 0.0952506 secs] [Times: user=0.38 sys=0.00, real=0.10 secs]
#格式都一样,只是内容变了一些,这次是Full GC,不仅收集了年轻代,还收集了年老代和元数据空间(如果JDK小于1.8,则是PermGen-持久代)
第六行
2018-06-04T15:32:56.655+0800: 3.902: [GC (Metadata GC Threshold) [PSYoungGen: 27823K->10909K(57856K)] 49531K->32625K(105472K), 0.0159518 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
#因为达到了元数据空间达到触发GC的阈值,触发了年轻代GC
第七行
2018-06-04T15:32:56.671+0800: 3.918: [Full GC (Metadata GC Threshold) [PSYoungGen: 10909K->0K(57856K)] [ParOldGen: 21716K->27310K(61952K)] 32625K->27310K(119808K), [Metaspace: 19192K->19192K(1069056K)], 0.1041471 secs] [Times: user=0.34 sys=0.00, real=0.10 secs]
#因为达到了元数据空间达到触发GC的阈值,触发了Full GC
上面的内容有错误的话,请大神们指出
通过看上面的消息,可以想到,既然GC是JVM调优的重点,为了避免精彩发生GC,影响应用程序的响应速度,首先可能会想到调整各个内存区域的大小,选择不同的垃圾回收器,调整内存页大小,压缩对象指针等等
打印safepoint(JVM设置一个安全点,所有应用线程都会去探测,如果有安全点,则应用线程会暂停,直到所有应用线程暂停下来,GC开始工作)信息。
为什么需要安全点,因为有的GC不是并发的,必须要求GC时,被标记的已失效对象的状态不能被改变。
**jdk1.8之前配置**:
**jdk1.8之后配置**:
-XX:+ShowSafepointMsgs更改成了-XX:+PrintSafepointStatistics,其他的好像没变
可以看到类似下面的输出结果
[Full GC (Metadata GC Threshold) [PSYoungGen: 10741K->0K(163840K)] [ParOldGen: 42768K->39251K(223232K)] 53510K->39251K(387072K), [Metaspace: 31956K->31956K(1079296K)], 0.1767548 secs] [Times: user=0.64 sys=0.03, real=0.18 secs]
Total time for which application threads were stopped: 0.2012747 seconds, Stopping threads took: 0.0000271 seconds
GC监控信息
为了减少GC次数,我将-Xms40m改成-Xms256m
通过GC日志可以看到每次GC的详情,但是没有一个统计信息,jstat工具提供了统计功能
类加载统计
PS D:\gc> jstat -class 13852
Loaded Bytes Unloaded Bytes Time
15886 32896.8 2 1.8 16.19
#类加载数量 加载的类占用内存大小 未加载的类数量 未加载的类大小 加载耗时
编译统计
PS D:\gc> jstat -compiler 13852
Compiled Failed Invalid Time FailedType FailedMethod
10106 4 0 42.39 1 org/eclipse/jdt/internal/core/JarPackageFragmentRoot initRawPackageInfo
#编译数量 失败数量 无效数量 编译耗时 失败类型 失败的方法
垃圾回收统计
PS D:\gc> jstat -gc 13852
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
43008.0 43008.0 15067.9 0.0 88576.0 61467.9 349696.0 77796.0 106260.0 94882.6 14612.0 11617.5 10 0.310 4 1.030 1.340
#S0C:第一个幸存区的大小
#S1C:第二个幸存区的大小
#S0U:第一个幸存区的使用大小
#S1U:第二个幸存区的使用大小
#EC:伊甸园区的大小
#EU:伊甸园区的使用大小
#OC:老年代大小
#OU:老年代使用大小
#MC:方法区大小
#MU:方法区使用大小
#CCSC:压缩类空间大小
#CCSU:压缩类空间使用大小
#YGC:年轻代垃圾回收次数
#YGCT:年轻代垃圾回收消耗时间
#FGC:老年代垃圾回收次数
#FGCT:老年代垃圾回收消耗时间
#GCT:垃圾回收消耗总时间
关于内存总大小、已使用大小是执行命令时的JVM状态,而次数和消耗总时间是历史累计的
扩展:
在64位平台上, 指向类元数据的指针可以由32位偏移量 (带有 UseCompressedOops) 表示。这由命令行标志 UseCompressedClassPointers (默认启用) 控制。一旦设置了CompressedClassSpaceSize,则内存区域大小启动后就不会变了。如果 UseCompressedClassPointers 所需的空间超过 CompressedClassSpaceSize, 将抛出一个具有Compressed class space字样的 java.lang.OutOfMemoryError异常。
操作: -XX:CompressedClassSpaceSize=40m
注意: CompressedClassSpaceSize 的可接受大小有界限,必须介于1048576和3221225472之间。例如, -XX:CompressedClassSpaceSize=4g, 超出可接受的界限将导致消息“CompressedClassSpaceSize=4294967296无效”。
注意: 有多种类元数据 -klass 元数据和其他元数据。只有 klass 元数据存储在由 CompressedClassSpaceSize 限定的空间中。其他元数据存储在 Metaspace 中
堆内存统计
PS D:\gc> jstat -gccapacity 13852
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
174592.0 174592.0 174592.0 43008.0 43008.0 88576.0 349696.0 349696.0 349696.0 349696.0 0.0 122880.0 106260.0 0.0 30720.0 14612.0 10 4
#NGCMN:新生代最小容量
#NGCMX:新生代最大容量
#NGC:当前新生代容量
#S0C:第一个幸存区大小
#S1C:第二个幸存区的大小
#EC:伊甸园区的大小
#OGCMN:老年代最小容量
#OGCMX:老年代最大容量
#OGC:当前老年代大小
#OC:当前老年代大小
#MCMN:最小元数据容量
#MCMX:最大元数据容量
#MC:当前元数据空间大小
#CCSMN:最小压缩类空间大小
#CCSMX:最大压缩类空间大小
#CCSC:当前压缩类空间大小
#YGC:年轻代gc次数
#FGC:老年代GC次数
扩展:
JDK源码中:
OGC = sum(all OC)
在jdk/src/share/classes/sun/tools/jstat/resources/jstat_options中:
OGC = sun.gc.generation.1.capacity
OC = sun.gc.generation.1.space.0.capacity
从两者的查看来看,我猜可能OGC是所有OC的和,而JDK默认只有一个OC,所以OC和OGC的值相等
然后PGC和PC的关系也应该是一样的
新生代垃圾回收统计
PS D:\gc> jstat -gcnew 13852
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
36864.0 38912.0 0.0 6064.0 15 15 36864.0 94720.0 81999.3 21 0.427
#S0C:第一个幸存区大小
#S1C:第二个幸存区的大小
#S0U:第一个幸存区的使用大小
#S1U:第二个幸存区的使用大小
#TT:对象在新生代存活的次数
#MTT:对象在新生代存活的最大次数
#DSS:期望的幸存区大小
#EC:伊甸园区的大小
#EU:伊甸园区的使用大小
#YGC:年轻代垃圾回收次数
#YGCT:年轻代垃圾回收消耗时间
新生代内存统计
PS D:\gc> jstat -gcnewcapacity 13852
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
174592.0 174592.0 174592.0 57856.0 32768.0 57856.0 30720.0 173568.0 111104.0 24 7
#NGCMN:新生代最小容量
#NGCMX:新生代最大容量
#NGC:当前新生代容量
#S0CMX:最大幸存1区大小
#S0C:当前幸存1区大小
#S1CMX:最大幸存2区大小
#S1C:当前幸存2区大小
#ECMX:最大伊甸园区大小
#EC:当前伊甸园区大小
#YGC:年轻代垃圾回收次数
#FGC:老年代回收次数
老年代垃圾回收统计
PS D:\gc> jstat -gcold 13852
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
109460.0 97872.2 14996.0 11925.0 349696.0 71047.4 24 7 2.221 2.691
#MC:方法区大小
#MU:方法区使用大小
#CCSC:压缩类空间大小
#CCSU:压缩类空间使用大小
#OC:老年代大小
#OU:老年代使用大小
#YGC:年轻代垃圾回收次数
#FGC:老年代垃圾回收次数
#FGCT:老年代垃圾回收消耗时间
#GCT:垃圾回收消耗总时间
老年代内存统计
PS D:\gc> jstat -gcoldcapacity 13852
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
349696.0 349696.0 349696.0 349696.0 24 7 2.221 2.691
#OGCMN:老年代最小容量
#OGCMX:老年代最大容量
#OGC:当前老年代大小
#OC:老年代大小
#YGC:年轻代垃圾回收次数
#FGC:老年代垃圾回收次数
#FGCT:老年代垃圾回收消耗时间
#GCT:垃圾回收消耗总时间
元数据空间统计
PS D:\gc> jstat -gcmetacapacity 13852
MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC FGCT GCT
0.0 126976.0 109460.0 0.0 30720.0 14996.0 24 7 2.221 2.691
#MCMN:最小元数据容量
#MCMX:最大元数据容量
#MC:当前元数据空间大小
#CCSMN:最小压缩类空间大小
#CCSMX:最大压缩类空间大小
#CCSC:当前压缩类空间大小
#YGC:年轻代垃圾回收次数
#FGC:老年代垃圾回收次数
#FGCT:老年代垃圾回收消耗时间
#GCT:垃圾回收消耗总时间
总结垃圾回收统计
PS D:\gc> jstat -gcutil 13852
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
26.18 0.00 75.02 20.32 89.41 79.52 24 0.470 7 2.221 2.691
#S0:幸存1区当前使用比例
#S1:幸存2区当前使用比例
#E:伊甸园区使用比例
#O:老年代使用比例
#M:元数据区使用比例
#CCS:压缩使用比例
#YGC:年轻代垃圾回收次数
#FGC:老年代垃圾回收次数
#FGCT:老年代垃圾回收消耗时间
#GCT:垃圾回收消耗总时间
JVM编译方法统计
PS D:\gc> jstat -printcompilation 13852
Compiled Size Type Method
10802 68 1 java/util/Spliterators$ArraySpliterator forEachRemaining
#Compiled:最近编译方法的数量
#Size:最近编译方法的字节码数量
#Type:最近编译方法的编译类型。
#Method:方法名标识
总结
上面这么多统计信息,应该如何看喃,先看堆的,再看某一类的回收统计和内存统计
内存分析工具
jmap
使用jmap命令生成DUMP文件
jmap -dump:format=b,file=filename.hprof
filename:指定dump文件名
pid:进程号ID
jstack
使用jstack命令生成Thread dump文件
jstack pid> 1.txt
jvisualVM
jvisualVM是JDK自带的内存分析工具,可以实时跟踪和查看内存使用、GC、获取堆空间映像(DUMP)、查看。
再使用jvisualVM工具开始分析之前,先要安装一些插件才能更好的完成工作
插件安装:
在cmd窗口输入命令:
jvisualVM
在打开的软件界面中按照如下顺序选择:
工具—>插件—>设置—>编辑
输入https://visualvm.github.io/archive/uc/8u40/updates.xml.gz,这个地址需要参考jdk版本设置,具体可以去https://visualvm.github.io/pluginscenters.html找对应插件版本
我自己的JDK小版本号是91,介于40-121之间,所以选择的上面的地址
然后将所有可用插件全部都勾上,并安装,最后出来的界面大概是这样:
监视:可以发起GC,可以生成当前堆的DUMP文件,DUMP之后,会在左边列表中看到生成的DUMP文件,双击打开DUMP文件可以看到很多信息,还可以对两个DUMP文件做对比,发现正常和异常时的数据差异
线程:看到所选择虚拟机的所有线程,然后可以生成选中线程的DUMP文件,便于看到线程的工作状态,比如执行上面方法,卡在什么地方等
Profiler:分为内存和CPU的分析,内存可以看到JVM中所有对象占比情况,发现耗内存的元凶;CPU可以看到JVM中所有方法占比情况,发现耗CPU的元凶
Visual GC:可以看到JVM内存各个区域使用情况和GC概况
MAT(Memory Analyzer Tool)
还没来得及研究这款产品,但HotSpot实战作者推荐,而且有公司面试的时候就出了MAT做内存分析的题
这个帖子讲得非常清楚(推荐):https://blog.csdn.net/wanghuiqi2008/article/details/50724676
JVM Crash文件
jvm crash日志文件往往成为我们定位问题的重要线索,linux 内核在发生OOM的时候会强制kill一些进程, 可以在/var/logs/messages中查找。
jvm crash文件生成配置:
生成error 文件的路径:你可以通过参数设置-XX:ErrorFile=/path/hs_error%p.log, 默认是在java运行的当前目录 [default: ./hs_err_pid%p.log]
参数-XX:OnError 可以在crash退出的时候执行命令,格式是-XX:OnError=“string”,
可以是命令的集合,用分号做分隔符, 可以用"%p"来取到当前进程的ID.
例如:
-XX:OnError="pmap %p" // show memory map
-XX:OnError="gcore %p; dbx - %p" // dump core and launch debugger
-XX:+ShowMessageBoxOnError 参数,当jvm crash的时候在linux里会启动gdb 去分析和调式,适合在测试环境中使用。
写了一段代码模拟OOM,JVM挂了,没有生成crash文件,具体原因我也不知道,希望有朋友可以告知
package com.jv.jvm;
public class JvmCrash { public static void main(String[] args) { Object[] o = null; while (true) { o = new Object[] { o }; } } }
jvm oom dump文件
启动Java程序可以通过增加启动参数,设置出现oom时dump出当前jvm的信息
增加-XX:+HeapDumpOnOutOfMemoryError,当抛出内存溢出异常时,会自动将当前JVM所使用的内存数据dump到当前目录下,输出的文件名格式:java_pid.hprof。
还可以配置dump文件的输出路径,增加一个配置项:-XX:HeapDumpPath=file_path
常用调优
JVM选项分类及语法
HotspotJVM提供以下三大类选项:
标准选项
这类选项的功能是很稳定的,在后续版本中也不太会发生变化。
运行java或者 java -help 可以看到所有的标准选项。
语法:所有的标准选项都是以 - 开头,比如-version,-server等。
X选项
这类选项的功能还是很稳定,但官方的说法是它们的行为可能会在后续版本中改变,也有可能不在后续版本中提供了.
运行 java-X 命令可以看到所有的X选项。
语法:这类选项都是以 -X 开头,比如-Xms。
XX选项
这类选项是属于实验性,主要是给JVM开发者用于开发和调试JVM的,在后续的版本中行为有可能会变化。
语法:
如果是布尔类型的选项,它的格式为-XX:+flag或者-XX:-flag,分别表示开启和关闭该选项。
针对非布尔类型的选项,它的格式为-XX:flag=value
堆大小设置
参数大小不能胡乱设置,要参考系统的物理内存和虚拟内存大小进行,如:
典型设置:
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:设置垃圾最大年龄(经历过GC的次数)。如果设置为0的话,则年轻代对象不经过Survivor区,直接晋升到年老代。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概论。
如果应用的对象很多都是需要长期存在,则可以调大年老代内存大小,调小年龄让其尽快进入到年老代。
如果应用的对象都是临时存在,则可以将年龄调小,让其尽早被回收,年老代的内存可以调小一些。
JDK1.8取消了PermGen,取而代之的是Metaspace,所以PermSize和MaxPermSize参数失效,取而代之的是-XX:MetaspaceSize=64m和-XX:MaxMetaspaceSize=128m
-XX:+UseCompressedOops:指针在64位机器上,长度是32位机器上的1.5倍,从JDK 1.6 update14开始,64 bit JVM支持 -XX:+UseCompressedOops 选项压缩指针,起到节约内存。经测试在64位服务器上设置-Xmx32g时,-XX:+UseCompressedOops 和 -XX:+UseCompressedClassPointers 无效,因此不要超过32G
常用配置汇总
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:与上面几个配合使用,把相关日志信息记录到文件以便分析。
常见配置汇总
堆设置
-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:设置持久代大小
收集器设置
-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/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
-XX:+TraceClassUnloading 跟踪类卸载
-XX:+TraceClassLoading 跟踪类加载
回收器选择
JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。 吞吐量优先的并行收集器 如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。 典型配置:
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区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。 响应时间优先的并发收集器 如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。 典型配置:
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 -X:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。 -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片辅助信息
调优总结
年轻代大小选择
响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得: 并发垃圾收集信息 持久代并发收集次数 传统GC信息 花在年轻代和年老代回收上的时间比例 减少年轻代和年老代花费的时间,一般会提高应用的效率 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。 较小堆引起的碎片问题,因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置: -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。 -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩