首先还是看下内存溢出的类型和初步应对分析:
1. 堆内存溢出
java.lang.OutOfMemoryError: ......java heap space.....
对于堆内存溢出,很显然是需要考虑调整堆内存大小,对于JVM启动堆内存,在非64位下只能够设置到4G,但是在64位下可以设置到8G内存或者更大都没有问题。但是建议最多设置到8G,如果对内存设置太大,进行Full GC的时候本身也相对缓慢。
如果设置了一个比较大的值,还出现堆内存溢出,那么就是前面一篇讲流控文章谈到的,即出现了大数据量并且长耗时的调用,直接导致内存一直被占用,同时在短时间无法释放,也无法进行垃圾回收。在这种情况下就必须要对这种业务场景和调用方法进行控制。减少每次调用的数据量或缩短优化处理时间。
在堆内存这个溢出之前,可能系统会提前先报错关键字为:
java.lang.OutOfMemoryError:GC over head limit exceeded
这种情况是当系统处于高频的GC状态,而且回收的效果依然不佳的情况,就会开始报这个错误,这种情况一般是产生了很多不可以被释放的对象,有可能是引用使用不当导致,或申请大对象导致,但是java heap space的内存溢出有可能提前不会报这个错误,也就是可能内存就直接不够导致,而不是高频GC.
2. PermGen的溢出,或者PermGen 满了的提示
java.lang.OutOfMemoryError: PermGen space
原因:系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法,导致常量池的膨胀,虽然JDK 1.5以后可以通过设置对永久带进行回收,但是我们希望的是这个地方是不做GC的,它够用就行,所以一般情况下今年少做类似的操作。
注意在JDK1.9已经没有PermSize设置参数,而是被 XX:MetaspaceSize替代。 主要如果出现PermGen溢出,一般情况都是代码本身有问题需要优化。
3. 在使用ByteBuffer中的allocateDirect()的时候会出现内存溢出
java.lang.OutOfMemoryError: Direct buffer memory
如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,如果常规的应用程序你要将一个文件的内容输出到客户端需要通过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),然后再输出到直接内存由操作系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存可以直接由应用程序自己控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存。
如果经常有类似的操作,可以考虑设置参数:-XX:MaxDirectMemorySize
4.第四类内存溢出错误,堆栈内存溢出:
java.lang.StackOverflowError
这个参数直接说明一个内容,就是-Xss太小了,我们申请很多局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4以前默认是256K,1.5以后是1M,如果报这个错,只能说明-Xss设置得太小。
5.线程分配内存溢出错误:
java.lang.OutOfMemoryError: unable to create new native thread
上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap以外的内存区域,也就是这个错误说明除了heap以外的区域,无法为线程分配一块内存区域了,这个要么是内存本身就不够,要么heap的空间设置得太大了,导致了剩余的内存已经不多了,而由于线程本身要占用内存,所以就不够用了。
6.地址空间不够导致内存溢出
java.lang.OutOfMemoryError: request {} byte for {}out of swap
这类错误一般是由于地址空间不够而导致。
六大类常见溢出已经说明JVM中99%的溢出情况,要逃出这些溢出情况非常困难,除非一些很怪异的故障问题会发生,比如由于物理内存的硬件问题,导致了code cache的错误(在由byte code转换为native code的过程中出现,但是概率极低),这种情况内存 会被直接crash掉,类似还有swap的频繁交互在部分系统中会导致系统直接被crash掉,OS地址空间不够的话,系统根本无法启动。
另外GC本身是需要内存空间的,因为在运算和中间数据转换过程中都需要有内存,所以你要保证GC的时候有足够的内存哦,如果没有的话GC的过程将会非常的缓慢。
下面看下JVM启动常用参数配置:
-Xmx 为Heap区域的最大值
-Xms 为Heap区域的初始值,线上环境需要与-Xmx设置为一致,否则capacity的值会来回飘动
-Xmn : 设置年轻代的大小(以字节为单位),一般为堆内存的1/2到1/4即可。
-Xss(或-ss) Java线程堆栈大小,这个其实也是可以默认的,JDK1.5后默认值为1M。
-XX:PermSize和-XX:MaxPermSize :在JDK 1.8版本已经废弃。被 -XX:MetaspaceSize取代
该值为永久代的内存分配大小,如果超过该值则会触发Full GC垃圾回收。
Oracle将在新一代JVM中将这个区域彻底删掉,也就是对用户透明,G1的如果真正稳定起来,以后JVM的启动参数将会非常简单,而且理论上管理再大的内存也是没有问题的。
JDK8已经废弃使用的JVM启动参数
-Xincgc : 启用增量垃圾收集。此选项在JDK 8中已被弃用,无需更换。
-Xrun libname : 加载指定的调试/分析库。该选项被选项取代-agentlib。
-XX:CMSIncrementalDutyCycle = percent
设置允许并发收集器运行的次要集合之间的时间百分比(0到100)。此选项在JDK 8中已被弃用,无需替换,因为该选项被弃用-XX:+CMSIncrementalMode。
-XX:CMSIncrementalDutyCycleMin = percent
设置作为-XX:+CMSIncrementalPacing启用占空比下限的次要集合之间的时间百分比(0到100)。此选项在JDK 8中已被弃用,无需替换,因为该选项被弃用-XX:+CMSIncrementalMode。
-XX:+ CMSIncrementalMode
启用CMS收集器的增量模式。此选项在JDK 8中已被弃用,无需替换,以及以其他选项开始CMSIncremental。
-XX:CMSIncrementalOffset = percent
设置在次要集合之间的时间段内增量模式占空比向右移动的时间百分比(0到100)。此选项在JDK 8中已被弃用,无需替换,因为该选项被弃用-XX:+CMSIncrementalMode。
-XX:+ CMSIncrementalPacing
根据JVM运行时收集的统计信息,可以自动调整增量模式占空比。此选项在JDK 8中已被弃用,无需替换,因为该选项被弃用-XX:+CMSIncrementalMode。
-XX:CMSIncrementalSafetyFactor = percent
设置计算占空比时用于添加保守性的时间百分比(0到100)。此选项在JDK 8中已被弃用,无需替换,因为该选项被弃用-XX:+CMSIncrementalMode。
-XX:CMSInitiatingPermOccupancyFraction = percent
设置启动GC的永久发电占用率(0到100)的百分比。此选项在JDK 8中已被弃用,无需更换。
-XX:MaxPermSize = size
设置最大的永久代大小。此选项已在JDK 8中弃用,并被该-XX:MaxMetaspaceSize选项取代。
-XX:PermSize = size
设置初始分配给的永久代的大小(以字节为单位),如果超过垃圾回收则触发垃圾回收。此选项已被取消JDK 8,并被该-XX:MetaspaceSize选项取代。
-XX:+ UseSplitVerifier
默认情况下,此选项在以前的版本中启用,验证分为两个阶段:类型引用(由编译器执行)和类型检查(由JVM运行时执行)。此选项在JDK 8中已被弃用,默认情况下,验证已被拆分,无法禁用它。
-XX:+ UseStringCache
启用缓存通常分配的字符串。此选项已从JDK 8中删除,无需更换。
其它高级参数设置:
-XX:+UseAdaptiveSizePolicy
使用自适应生成大小调整。默认情况下启用此选项。要禁用自适应生成大小,请指定-XX:-UseAdaptiveSizePolicy并明确设置内存分配池的大小(请参阅-XX:SurvivorRatio选项)。
-XX:+UseCMSInitiatingOccupancyOnly
允许使用占用值作为启动CMS收集器的唯一标准。默认情况下,此选项被禁用,并且可以使用其他标准。
-XX:+ UseConcMarkSweepGC
启用CMS旧垃圾收集器的使用。Oracle建议您在吞吐量(-XX:+UseParallelGC)垃圾收集器无法满足应用程序延迟要求时,使用CMS垃圾收集器。G1垃圾收集器(-XX:+UseG1GC)是另一个选择。
默认情况下,禁用此选项,并根据机器的配置和JVM的类型自动选择收集器。启用此选项后,该-XX:+UseParNewGC选项将自动设置,您不应禁用该选项,因为在JDK 8中不推荐使用以下组合的选项-XX:+UseConcMarkSweepGC -XX:-UseParNewGC。
-XX:+ UseG1GC
启用垃圾回收(G1)垃圾回收器。它是一种服务器式垃圾回收器,面向具有大量RAM的多处理器机器。它可以高概率地满足GC暂停时间目标,同时保持良好的吞吐量。推荐使用G1收集器,用于需要大堆(大小约6 GB或更大)的应用,具有有限的GC等待时间要求(稳定且可预测的暂停时间低于0.5秒)。
默认情况下,禁用此选项,并根据机器的配置和JVM的类型自动选择收集器。
-XX:+ UseGCOverheadLimit
启用在OutOfMemoryError引发异常之前限制JVM在GC上花费的时间比例的策略。
默认情况下启用此选项,并且并行GC将抛出OutOfMemoryError总共时间的98%用于垃圾回收,并且小于2%的堆将被恢复。堆小时,此功能可用于防止应用程序长时间运行,几乎没有进展。要禁用此选项,请指定-XX:-UseGCOverheadLimit。
-XX:+ UseNUMA
通过增加应用程序使用较低延迟的内存,实现具有不均匀内存架构(NUMA)的机器上的应用程序的性能优化。默认情况下,禁用此选项,并且不会对NUMA进行优化。该选项仅在使用并行垃圾收集器时可用(-XX:+UseParallelGC)。
-XX:+ UseParallelGC
使用并行扫描垃圾收集器(也称为吞吐量收集器)通过利用多个处理器来提高应用程序的性能。
默认情况下,禁用此选项,并根据机器的配置和JVM的类型自动选择收集器。如果启用,则该-XX:+UseParallelOldGC选项将自动启用,除非您明确禁用该选项。
-XX:+ UseParallelOldGC
使用并行垃圾收集器来完成GC。默认情况下,此选项被禁用。启用它将自动启用该-XX:+UseParallelGC选项。
-XX:+ UseParNewGC
该选项设置为允许在年轻一代中使用并行线程进行收集。默认情况下,此选项被禁用。设置-XX:+UseConcMarkSweepGC选项时会自动启用。在JDK 8中-XX:+UseParNewGC不推荐使用不带该-XX:+UseConcMarkSweepGC选项的选项。
-XX:+ UseSerialGC
启用串行垃圾收集器的使用。这通常是小而简单应用程序的最佳选择,不需要垃圾收集的任何特殊功能。默认情况下,禁用此选项,并根据机器的配置和JVM的类型自动选择收集器。
-XX:+ UseSHM
在Linux上,使JVM可以使用共享内存来设置大页面。
-XX:+ UseStringDeduplication
启用字符串重复数据删除。默认情况下,此选项被禁用。要使用此选项,必须启用垃圾回收(G1)垃圾收集器。看到-XX:+UseG1GC选项。字符串重复数据删除String通过利用许多String对象相同的事实来减少Java堆上对象的内存占用。而不是每个String对象指向自己的字符数组,相同的String对象可以指向并共享相同的字符数组。
-XX:+ UseTLAB
允许在年轻一代的空间中使用线程本地分配块(TLAB)。默认情况下启用此选项。要禁用TLAB的使用,请指定-XX:-UseTLAB。
-XX:+ AggressiveOpts
可以使用积极的性能优化功能,预计在即将发布的版本中将会成为默认的性能优化功能。默认情况下,此选项被禁用,并且不使用实验性能功能。
-XX:+ UseLargePages
启用大页面内存的使用。默认情况下,禁用此选项,不使用大页面内存。
-XX:MaxGCPauseMillis =time
设置最大GC暂停时间的目标(以毫秒为单位)。这是一个软性目标,JVM将尽力实现。默认情况下,没有最大暂停时间值。
性能调优示例
示例1 - 调整更高的吞吐量
java -d64 -server -XX:+ AggressiveOpts -XX:+ UseLargePages -Xmn10g -Xms26g -Xmx26g
示例2 - 调整响应时间较短
java -d64 -XX:+ UseG1GC
-Xms26g Xmx26g -XX:MaxGCPauseMillis = 500 -XX:+ PrintGCTimeStamp
本文参考如文章整理,具体更多的性能调优和JVM垃圾回收机制分析参考:
https://www.cnblogs.com/woshimrf/p/jvm-garbage.html
https://blog.csdn.net/huangrunqing/article/details/9986293
https://www.cnblogs.com/benwu/articles/6921047.html