JVM~内存调优配置~GC调优选择

Java堆从 GC的角度还可以细分为:新生代(Eden区、From Survivor区和 To Survivor区)和老年代

JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。(注意:从Java 8开始,HotSpot虚拟机中删除了“持久代”)

新生代: 用来存放新生的对象。一般占据堆的1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾 回收。新生代又分为Eden 区、ServivorFrom、 ServivorTo 3个区。

老年代:主要存放应用程序中生命周期长的内存对象。

1.堆内存设置

堆内存初始与最大

-Xms512m和 -Xmx512m

年轻代eden与FromSurvivor  ToSurvivor 大小设置

-XX:NewSize和-XX:MaxNewSize

      用于设置新生代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大

-server -Xms512m -Xmx512m -XX:NewSize=128m -XX:MaxNewSize=128m

-Xmn2M  新生代大小为2M

-XX:SurvivorRatio 默认8  用于设置Eden和其中一个Survivor的比值

-XX:NewRatio  老年代:新生代 默认2

例:edge:from:to 6:1:1,新生代:老年代 1:2

java -server -Xms256m -Xmx256m -XX:NewSize=128m -XX:MaxNewSize=128m -jar jvm-0.1.jarjava -server -Xms256m -Xmx256m  -XX:NewRatio=2 -XX:SurvivorRatio=6 -jar jvm-0.1.jar

2.永久代(不会被GC回收)

指内存的永久保存区域,主要存放Class 和Meta (元数据)的信息,Class在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class 的增多而胀满,最终抛出OOM异常。

-XX:PermSize:表示非堆区初始内存分配大小(方法区,Java8中已经移除)

-XX:MaxPermSize:表示对非堆区分配的内存的最大上限(方法区,Java8中已经移除)

java -server -Xms256m -Xmx256m -XX:PermSize=64m -XX:MaxPermSize=128m -jar jvm-0.1.jar

3.直接内存设置

 -XX:MaxDirectMemorySize 本地最大的直接内存,Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC。默认初始为64M, 最大为堆的最大值-Xmx,最大为sun.misc.VM.maxDirectMemory()

使用NMT查看directBuffer使用情况:

jcmd PID VM.native_memory scale=MB

 java -server -Xms256m -Xmx256m -XX:MaxDirectMemorySize=1G -jar jvm-0.1.jar

4.线程栈内存设置

栈内存为线程私有的空间,每个线程都会创建私有的栈内存。栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

栈内存256K

-Xss:256K

5.元数据空间(1.8)

在 JDK 1.8 之前,所有加载的类信息都放在永久代中。但在 JDK1.8 之时,永久代被移除,取而代之的是元空间(Metaspace)。在元空间这块内存中,有两个参数很相似,它们是: -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize。

元空间的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

java -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=50m -XX:+PrintGCDetails -jar jvm-0.1.jar

上面的命令中,我们设置 MetaspaceSize 为 10M,MaxMetaspaceSize 为 50M。但其实它们并不是设置初始大小和最大大小的。

从上面的执行结果可以看到,Metaspace 空间的大小为 2.6M 左右,并不是我们设置的 10M。那是因为 MetaspaceSize 设置的是元空间发生 GC 的初始阈值。当达到这个值时,元空间发生 GC 操作,这个值默认是 20.8M。而 MaxMetaspaceSize 则是设置元空间的最大大小,默认基本是机器的物理内存大小。虽然可以不设置,但还是建议设置一下,因为如果一直不断膨胀,那么 JVM 进程可能会被 OS kill 掉。

6.年轻代到老年代晋升

      年轻代晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1

TenuringThreshold must be between 0 and 15

 -XX:InitialTenuringThreshold和-XX:MaxTenuringThreshold

java -server -Xms256m -Xmx256m -XX:InitialTenuringThreshold=10  -XX:MaxTenuringThreshold=15 -jar jvm-0.1.jar

如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。

7.运行时堆栈信息打印

JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

-XX:+PrintTenuringDistribution   用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。

-XX:+PrintGC

-XX:+PrintGCDetails  打印GC 的详细信息

-XX:+PrintGCTimeStamps  打印CG发生的时间戳

–[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

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

-XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间。可与上面混合使用。

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

-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。

-XX:+HeapDumpOnOutOfMemoryError导出内存溢出的堆信息(hprof文件)

java -Xms128M -Xmx128M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\gc.hprof -XX:+PrintGCDetails -Xloggc:D:\gc.log gc.GcTest -jar jvm-0.1.jar

Allocation Failure:表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16413636k(10930260k free), swap 33190852k(26877424k free)
CommandLine flags: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\gc.hprof -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=134217728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.444: [GC (Allocation Failure) [PSYoungGen: 33280K->3138K(38400K)] 33280K->3218K(125952K), 0.0050832 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.925: [GC (Metadata GC Threshold) [PSYoungGen: 22410K->5090K(35328K)] 22602K->5602K(122880K), 0.0036870 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
0.929: [Full GC (Metadata GC Threshold) [PSYoungGen: 5090K->0K(35328K)] [ParOldGen: 512K->5431K(87552K)] 5602K->5431K(122880K), [Metaspace: 20503K->20501K(1067008K)], 0.0228211 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 

 

 

Full GC

8.Minor GC和Major GC

Minor GC和Major GC区别

Minor GC:简单理解就是发生在年轻代的GC。三步(复制--清空--互换)

Minor GC的触发条件

当产生一个新对象,新对象优先在Eden区分配。如果Eden区放不下这个对象,虚拟机会使用复制算法发生一次Minor GC,清除掉无用对象,同时将存活对象移动到Survivor的其中一个区(fromspace区或者tospace区)。

虚拟机会给每个对象定义一个对象年龄(Age)计数器,对象在Survivor区中每“熬过”一次GC,年龄就会+1。待到年龄到达一定岁数(默认是15岁),虚拟机就会将对象移动到年老代。

如果新生对象在Eden区无法分配空间时,此时发生Minor GC。发生MinorGC,对象会从Eden区进入Survivor区,如果Survivor区放不下从Eden区过来的对象时,此时会使用分配担保机制将对象直接移动到年老代。

1.第一次Yong GC(Minor GC)后,Eden区还存活的对象复制到Surviver区的“To”区,“From”区还存活的对象也复制到“To”区,

2.再清空Eden区和From区,这样就等于“From”区完全是空的了,而“To”区也不会有内存碎片产生,

3.等到第二次Yong GC时,“From”区和“To”区角色互换,很好的解决了内存碎片的问题。

Major GC的触发条件

Major GC又称为Full GC。当年老代空间不够用的时候,虚拟机会使用“标记—清除”或者“标记—整理”算法清理出连续的内存空间,分配对象使用。

  老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC 前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。

  当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。

  MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM (Out of Memory)异常。

 

9.GC选择

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收集同时使用。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片。

10.收集器设置

收集器设置

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

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

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

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

并行收集器设置

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

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

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

并发收集器设置

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

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

11.调优总结

 年轻代

  响应时间优先:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

  吞吐量优先:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。

 

 老年代

  响应时间优先:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

并发垃圾收集信息

持久代并发收集次数

传统GC信息

花在年轻代和年老代回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率

  吞吐量优先:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

较小堆引起的碎片问题

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值