JVM调优整理

Stella981
• 阅读 651

友情链接

查看JVM加载的类及对应类加载器的方法

Java内存模型

​ 在进行调优之间,必须得对Java内存模型有一个较深的认识才行,否则会一脸懵逼。

JVM调优整理

​ 图中的持久代(也叫永久代)在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之前配置**:

JVM调优整理

​ **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之间,所以选择的上面的地址

JVM调优整理

然后将所有可用插件全部都勾上,并安装,最后出来的界面大概是这样:

JVM调优整理

监视:可以发起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文件生成配置

  1. 生成error 文件的路径:你可以通过参数设置-XX:ErrorFile=/path/hs_error%p.log, 默认是在java运行的当前目录 [default: ./hs_err_pid%p.log]

  2. 参数-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

  1. -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后,对年老代进行压缩

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这