jvm内存配置、参数,实例解析,四种内存溢出种类及原因分析

jvm配置参数查看

ps -ef | grep tomcat 结果:
work     31591     1  4 May16 ?        02:00:01 /data/j2sdk/bin/java -Djava.util.logging.config.file=/data/bi/tomcat/conf/logging.properties -*********

jvm参数配置方法

服务器上:修改 tomcat/bin/catalina.sh,重新启动tomcat即可,实例如下:

默认JAVA_OPTS="$JAVA_OPTS -Djava.protocol.handler.pkgs=org.apache.catalina.webresources",修改为(这个是线上服务验证过的实例,可供参考)
JAVA_OPTS="$JAVA_OPTS -Xms3072M -Xmx3072M -Xmn1024M -XX:SurvivorRatio=3  -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=55 -XX:+ScavengeBeforeFullGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseParNewGC -XX:-OmitStackTraceInFastThrow -Xdebug  -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Djava.security.egd=file:/dev/./urandom -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/data/bi/tomcat/bin/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/bi/tomcat/bin/dump.hprof -Djava.protocol.handler.pkgs=org.apache.catalina.webresources"
eclipse上,eclipse根目录下,找到eclipse.ini直接修改参数,"

eclipse :直接再eclipse的安装路径下修改eclipse.ini文件,重启eclipse即可

jvm内存配置参数详解

整体13部分内容包括:6区3器3日志1GC,即:

6区:jdk8开始的metaspace、直接内存、codeCache、堆、栈、持久代
3器:JIT编译模式,垃圾回收器,并发收集器配置 
3日志:gc日志、快照dump、NMT日志
1GC:代码禁用GC 

1. 堆设置
o -Xms:初始堆大小,如4096M
o -Xmx:最大堆大小,如4096M,如果xms>xmx,   那默认空余堆内存小于40%会触发调整到xmx,但是MinheapFreeRation可以调整
o -XX:NewSize=n:设置年轻代大小,如1024
o -Xmn1024M 设置年轻代大小为1024M
o -XX:NewRatio=n:设置年老代和年轻代的比值。建议值为3至5,如4,表示年老代:年轻代=4:1,年轻代占整个年轻代年老代和的1/5
o -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。建议值3-5。注意Survivor区有两个。如8,表示2*Survivor:Eden=2:8,一个Survivor区占整个年轻代的1/10

注意:如果eden和survior太小,new出来的对象直接进入old,那么除非发生fullGC,否则对象不可能被回收。

2. 持久代大小(jdk1.8开始已消除该区域,由metaspace替代)
o -XX:MaxPermSize=n:设置持久代最大内存
o -XX:PermSize=512m:持久代初始内存
o -XX:+CMSClassUnloadingEnabled
如果你启用了CMSClassUnloadingEnabled ,垃圾回收会清理持久代,移除不再使用的classes。这个参数只有在 UseConcMarkSweepGC  也启用的情况下才有用。
持久代和老生代、新生代的关系:
持久代本质就是方法区的一部分,存放class信息,静态代码,类信息等。
新生代和老生代都是属于堆,所以总结下就是持久代和新生代老生代没有关系。

3. 单个线程堆栈大小设置
-Xss768k 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

4. 收集器设置
o -XX:+UseSerialGC:设置串行收集器
o -XX:+UseParallelGC:设置并行收集器
o -XX:+UseParalledlOldGC:设置并行年老代收集器
o -XX:+UseConcMarkSweepGC:设置并发收集器

5. 行收集器参数设置
 -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
 -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
 -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
 -XX:+CMSParallelInitialMarkEnabled:开启初始标记过程中的并行化,进一步提升初始化标记效率;
 -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
 -XX:+CMSScavengeBeforeRemark 在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销--一般CMS的GC耗时 80%都在remark阶段
 -XX:CMSInitiatingOccupancyFraction=70 和-XX:+UseCMSInitiatingOccupancyOnly
    这两个设置一般配合使用,一般用于『降低CMS GC频率或者增加频率、减少GC时长』的需求
    -XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC);
    -XX:+UseCMSInitiatingOccupancyOnly 只使用设定的回收阈值(上面的-XX:CMSInitiatingOccupancyFraction),如果不指定,JVM仅在第一次使用设定值,后续则自动调整。然而,请记住大多数情况下,JVM比我们自己能作出更好的垃圾收集决策。因此,只有当我们充足的理由(比如测试)并且对应用程序产生的对象的生命周期有深刻的认知时,才应该使用该标志。
-XX:+UseCMSCompactAtFullCollection   在FULL GC的时候,使用并发收集器时,开启对年老代的压缩.CMS是不会移动内存的,因此,非常容易产生碎片,导致内存不够用.增加这个参数是个好习惯。 
-XX:CMSFullGCsBeforeCompaction=5 上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩.建议开启该参数
-XX:MaxTenuringThreshold=10    垃圾最大年龄,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率.

6. 垃圾回收统计信息
 -XX:+PrintGC
 -XX:+PrintGCDetails
 -XX:+PrintGCTimeStamps
 -Xloggc:filename

7.禁止显式调用GC和快异常处理
-XX:+DisableExplicitGC禁止System.gc(),免得程序员误调用gc方法影响性能;

-XX:-OmitStackTraceInFastThrow 
这是HotSpot VM专门针对异常做的一个优化,称为fast throw,当一些异常在代码里某个特定位置被抛出很多次的话,HotSpot Server Compiler(C2)会用fast throw来优化这个抛出异常的地方,直接抛出一个事先分配好的、类型匹配的对象,这个对象的message和stack trace都被清空。
可以明确:抛出这个异常非常快,不用额外分配内存,也不用爬栈。
副作用:正好是需要知道哪里出问题的时候看不到stack trace了,不利于排查问题。
如果遇到没有stack trace的问题,可以考虑通过 -XX:-OmitStackTraceInFastThrow 禁用该默认的优化

8.metaspace的参数设置(jdk1.8开始)
-XX:MetaspaceSize=*m:初始化的Metaspace大小
-XX:MaxMetaspaceSize=*m:限制Metaspace增长的上限,本机上该参数的默认值为4294967295B(大约4096MB)。
-XX:MinMetaspaceFreeRatio=40:当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。
-XX:MaxMetasaceFreeRatio=70:当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。
-XX:MaxMetaspaceExpansion=**m:Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB。
-XX:MinMetaspaceExpansion=**m:Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB)。

9.oom时是否dump快照

-XX:+HeapDumpOnOutOfMemoryError 发生OutOfMemoryError错误时,进行快照存储
-XX:HeapDumpPath=/data/zhipin-bi/tomcat/bin/dump.hprof 设置快照存储的位置

10. 直接内存:类似direce buffer直接分配的内存

-XX:MaxDirectMemorySize=512M
直接内存的知识可参考:https://blog.csdn.net/h2604396739/article/details/101361702

11.依赖NMT特性对jvm
开启NMT并选择summary模式:-XX:NativeMemoryTracking=summary
应用退出时打印NMT信息:-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

12.JIT编译配置
-XX:+TieredCompilation 开启分层编译(默认开启)
-XX:-TieredCompilation 关闭分层编译;

13.codeCatch相关配置
-XX:ReservedCodeCacheSize=256M 设置codeCache的大小
-XX:+PrintCodeCache 在JVM停止的时候打印出codeCache的使用情况
-XX:+UseCodeCacheFlushing 启动codeCache回收

官网相关参数路径:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

jvm内存配置参数实例与解释

公司线上服务器jvm内存的情况,一直没有一个统一的标准来给到各个应用服务的owner
推荐的jvm模版:
jdk版本 机器配置 建议jvm参数 备注
jdk1.7 6V8G -server  -Xms3072M -Xmx3072M -Xmn1024M -XX:SurvivorRatio=3 -Xss768k -XX:PermSize=512m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+ScavengeBeforeFullGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5  -XX:+UseParNewGC -XX:+CMSClassUnloadingEnabled -XX:+DisableExplicitGC  -XX:CMSInitiatingOccupancyFraction=68 -XX:+UseCMSInitiatingOccupancyOnly -verbose:gc -XX:+PrintGCDetails -Xloggc:{CATALINA_BASE}/logs/gc.log -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={CATALINA_BASE}/logs 

上面配置参数解释如下:
-Xms3072M -Xm3072M
针对JVM堆的设置,通过-Xms -Xmx限定其最小、最大值,这里设置的相同,防止因扩容影响性能。
-Xmn1024m设置年轻代大小为1024m

-XX:SurvivorRatio=3 2*survior:eden=2:3 survior占年轻代1/5空间,即205M左右,eden区600M左右

-Xss768k 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

-XX:PermSize=512m -XX:MaxPermSize=512m   --------jdk1.8已经不需要该参数
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4

-XX:+UseConcMarkSweepGC
CMS收集器也被称为短暂停顿并发收集器。它是对年老代进行垃圾收集的。CMS收集器通过多线程并发进行垃圾回收,尽量减少垃圾收集造成的停顿。CMS收集器对年轻代进行垃圾回收使用的算法和Parallel收集器一样。这个垃圾收集器适用于不能忍受长时间停顿要求快速响应的应用。

-XX:+ScavengeBeforeFullGC:在full GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段

-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5:因为cmsGc采用的是标记清除算法,所以会产生空间碎片,这里设置开启内存碎片整理,并且每回收5次进行一次磁盘整理。

-XX:+UseParNewGC 对年轻代采用多线程并行回收,这样收得快;

-XX:+CMSClassUnloadingEnabled
如果你启用了CMSClassUnloadingEnabled ,垃圾回收会清理持久代,移除不再使用的classes。这个参数只有在UseConcMarkSweepGC  也启用的情况下才有用。

-XX:+DisableExplicitGC禁止System.gc(),免得程序员误调用gc方法影响性能;

-XX:CMSInitiatingOccupancyFraction=68
默认CMS是在tenured generation(年老代)占满68%的时候开始进行CMS收集,如果你的年老代增长不是那么快,并且希望降低CMS次数的话,可以适当调高此值;

-XX:+UseCMSInitiatingOccupancyOnly 只使用设定的回收阈值(上面的-XX:CMSInitiatingOccupancyFraction),如果不指定,JVM仅在第一次使用设定值,后续则自动调整

-verbose:gc -XX:+PrintGCDateStamps -Xloggc:{CATALINA_BASE}/logs/gc.log  gc.log打印内容和路径

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={CATALINA_BASE}/logs :设置oom时的dump文件路径。

调整jvm参数的节奏:
由于怕影响线上应用,所以调整的步骤分三步:
第一步:部分影响少量机器试点,对比未调整的机器,观察调整后的结果;
第二步:调整部分应用的参数,进行压测,观察高并发压测之后的效果;
第三步:调整部分核心应用的jvm参数,通过818大促来实际检验效果;

遇到的几个问题:
问题一:初始化标记阶段耗时过长:
一般的建议是cms阶段两次STW的时间不超过200ms,如果是CMS Initial mark阶段导致的时间过长:
在初始化标记阶段(CMS Initial mark),为了最大限度地减少STW的时间开销,我们可以使用:
-XX:+CMSParallelInitialMarkEnabled
开启初始标记过程中的并行化,进一步提升初始化标记效率;

jvm参数设置经验&&规则

年轻代大小选择
响应时间吐纳兔粮优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,年轻代收集发生的频率也是最小的.同时,减少到达年老代的对象.
避免设置过小.当新生代设置过小时会导致:1.YGC次数更加频繁 2.可能导致YGC对象直接进入旧生代,如果此时旧生代满了,会触发FGC.
新生代大小建议 512-1024M,surviorRation建议值3-5,

年老代大小选择
响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象.

较小堆引起的碎片问题
因为年老代的并发收集器使用标记,清除算法,所以不会对堆进行压缩.当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象.但是,当堆空间较小时,运行一段时间以后,就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收.如果出现"碎片",可能需要进行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩.
-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力
使用CMS的好处是用尽量少的新生代,经验值是128M-256M, 然后老生代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率。 实际上cms的收集停顿时间非常的短,2G的内存, 大约20-80ms的应用程序停顿时间
仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。
采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿

上面借鉴与:https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
官网参数介绍连接:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

下面是一个坑的实例
新生代大小256M,survior区10M左右,老生代2516M
然后线上运行就会发现,老生代的使用内存在一致增多,2天的时间没有发生任何的fgc。然后就到了下面的情况:
eden中使用了200M,有50M大小的对象,survior使用了8M左右,老生代使用2500M,注意因为默认的fgc是老生代的连续空间小于survior中对象大小才会触发,上面场景是不会触发fgc,所以导致老生代有很多该回收的对象一直占用内存。然后代码中有缓存近50M数据的操作,这个时候eden中那些大于50M对象发现survior区放不下,只能直接到old区,old区又发现也放不下,所以报了OOM错误。

解决方案
1 增大年轻代为512,设置survior的代大小为128M左右
2 设置fgc的开始gc值, -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=60 是指设定CMS在对内存占用率达到70%的时候开始GC

内存溢出种类及原因分析

1)Out Of Memory:Pergen space
内存中需要加载的class、jar、静态代码太多,解决这类问题有以下两种办法:
一些单独的堆空间,被称为永久保存区域(permanent generation),它们不属于Java堆的一部分,用来存放类和类的描述。
a增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大 小,XX:MaxPermSize是最大永久保存区域大小。
b清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到 tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。
2)Out Of Memory:java heap space
原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:
检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。 我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了 Java heap space的内存溢出问题,后来通过修改程序得到了解决。
增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024
3)OutOfMemoryError:unable to create new native thread
因为JVM已经被系统分配了大量的内存(比如1.5G),并且它至少要占用可用内存的一半。有人发现,在线程个数很多的情况下, 你分配给JVM的内存越多,那么,上述错误发生的可能性就越大。
那么是什么原因造成这种问题呢?
每一个32位的进程最多可以使用2G的可用内存,因为另外2G被操作系统保留。这里假设使用1.5G给JVM,那么还余下500M可用内存。这 500M内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400M,现在关键的地方出现了:当你使用Java创建一个线程,在JVM的内 存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程(参考JVM规范),操作系统会在余下的400兆内存里创建这个物理 线程,而不是在JVM的1500M的内存堆里创建。在jdk1.4里头,默认的栈大小是256KB,但是在jdk1.5里头,默认的栈大小为1M每线程, 因此,在余下400M的可用内存里边我们最多也只能创建400个可用线程。

这样结论就出来了,要想创建更多的线程,你必须减少分配给JVM的最大内存。

4)GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded 触发场景:执行垃圾收集的时间比例太大, 有效的运算量太小. 默认情况下, 如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%这种情况发生的原因是, 程序基本上耗尽了所有的可用内存, GC也清理不了。
如:bi中的场景就是大量的缓存,把大量的查询结果直接缓存起来,再次查询的时候直接获取。。老生代内存不够的时候会进行fgc,但是发现几乎没有效果,此时就会报Gc overhead limit exceeded

5)StackOverflowError
线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,此时应该是对单个线程设置的内存太小(增大-Xss768k即可),或者是有递归太深了(查询代码用到递归的地方)
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
这里需要注意当栈的大小越大可分配的线程数就越少。

你可能感兴趣的:(jvm,深入理解java虚拟机,完整项目调优oom,jvm,连接池,tomcat)