JVM内存设置

JVM内存设置,第1张

一个新上线java服务, 内存的设置该怎么设置呢?设置成多大比较合适,既不浪费内存,又不影响性能呢?

分析:

依据的原则是根据Java Performance里面的推荐公式来进行设置。

具体来讲:

Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍

永久代 PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍。【永久区并不是老年代的1.2到1.5倍,而是FullGC后永久区的1.2到1.5倍 1.2x to 1.5x permanent generation space】

年轻代Xmn的设置为老年代存活对象的1-1.5倍。

老年代的内存大小设置为老年代存活对象的2-3倍。

BTW:

1、Sun官方建议年轻代的大小为整个堆的3/8左右, 所以按照上述设置的方式,基本符合Sun的建议。

2、堆大小=年轻代大小+年老代大小, 即xmx=xmn+老年代大小 。 Permsize不影响堆大小。

3、为什么要按照上面的来进行设置呢? 没有具体的说明,但应该是根据多种调优之后得出的一个结论。

如何确认老年代存活对象大小?

方式1 (推荐/比较稳妥):

JVM参数中添加GC日志,GC日志中会记录每次FullGC之后各代的内存大小,观察老年代GC之后的空间大小。可观察一段时间内(比如2天)的FullGC之后的内存情况,根据多次的FullGC之后的老年代的空间大小数据来预估FullGC之后老年代的存活对象大小(可根据多次FullGC之后的内存大小取平均值)

方式2: (强制触发FullGC, 会影响线上服务,慎用)

方式1的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用CMS回收器的时候,有可能不能触发FullGC(只发生CMS GC),所以日志中并没有记录FullGC的日志。在分析的时候就比较难处理。

BTW:使用jstat -gcutil工具来看FullGC的时候, CMS GC是会造成2次的FullGC次数增加。 具体可参见之前写的一篇关于jstat使用的文章

所以,有时候需要强制触发一次FullGC,来观察FullGC之后的老年代存活对象大小。

注:强制触发FullGC,会造成线上服务停顿(STW),要谨慎,建议的 *** 作方式为,在强制FullGC前先把服务节点摘除,FullGC之后再将服务挂回可用节点,对外提供服务

在不同时间段触发FullGC,根据多次FullGC之后的老年代内存情况来预估FullGC之后的老年代存活对象大小

如何触发FullGC ?

使用jmap工具可触发FullGC

jmap -dump:live,format=b,file=heap.bin <pid>将当前的存活对象dump到文件,此时会触发FullGC

jmap -histo:live <pid>打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量. 此时会触发FullGC

具体 *** 作实例:

以我司的一个RPC服务为例。

BTW:刚上线的新服务,不知道该设置多大的内存的时候,可以先多设置一点内存,然后根据GC之后的情况来进行分析。

初始JVM内存参数设置为: Xmx=2G Xms=2G xmn=1G

使用jstat 查看当前的GC情况。如下图:

YGC平均耗时: 173.825s/15799=11ms

FGC平均耗时:0.817s/41=19.9ms

平均大约10-20s会产生一次YGC

看起来似乎不错,YGC触发的频率不高,FGC的耗时也不高,但这样的内存设置是不是有些浪费呢?

为了快速看数据,我们使用了方式2,产生了几次FullGC,FullGC之后,使用的jmap -heap 来看的当前的堆内存情况(也可以根据GC日志来看)

heap情况如下图:(命令 : jmap -heap <pid>)

上图中的concurrent mark-sweep generation即为老年代的内存描述。

老年代的内存占用为100M左右。 按照整个堆大小是老年代(FullGC)之后的3-4倍计算的话,设置各代的内存情况如下:

Xmx=512m Xms=512m Xmn=128m PermSize=128m 老年代的大小为 (512-128=384m)为老年代存活对象大小的3倍左右

调整之后的,heap情况

GC情况如下:

YGC 差不多在10s左右触发一次。每次YGC平均耗时大约9.41ms。可接受。

FGC平均耗时:0.016s/2=8ms

整体的GC耗时减少。但GC频率比之前的2G时的要多了一些。

注: 看上述GC的时候,发现YGC的次数突然会增多很多个,比如 从1359次到了1364次。具体原因是?

总结:

在内存相对紧张的情况下,可以按照上述的方式来进行内存的调优, 找到一个在GC频率和GC耗时上都可接受的一个内存设置,可以用较小的内存满足当前的服务需要

但当内存相对宽裕的时候,可以相对给服务多增加一点内存,可以减少GC的频率,GC的耗时相应会增加一些。 一般要求低延时的可以考虑多设置一点内存, 对延时要求不高的,可以按照上述方式设置较小内存。

补充:

永久代(方法区)并不在堆内,所以之前有看过一篇文章中描述的 整个堆大小=年轻代+年老代+永久代的描述是不正确的。

jmap -heap pid 结果参数详解

jar包启动时指定对应参数,比如我的工程启动命令就是这样的

启动命令,打码部分为工程名

常见参数如下

1.-Xms:初始堆大小。只要启动,就占用的堆大小。

2.-Xmx:最大堆大小。java.lang.OutOfMemoryError:Java heap这个错误可以通过配置-Xms和-Xmx参数来设置。

3.-Xss:栈大小分配。栈是每个线程私有的区域,通常只有几百K大小,决定了函数调用的深度,而局部变量、参数都分配到栈上。

当出现大量局部变量,递归时,会发生栈空间OOM(java.lang.StackOverflowError)之类的错误。

4.XX:NewSize:设置新生代大小的绝对值。

5.-XX:NewRatio:设置年轻代和年老代的比值。比如设置为3,则新生代:老年代=1:3,新生代占总heap的1/4。

6.-XX:MaxPermSize:设置持久代大小。

java.lang.OutOfMemoryError:PermGenspace这个OOM错误需要合理调大PermSize和MaxPermSize大小。

7.-XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。注意,Survivor区有form和to两个。比如设置为8时,那么eden:form:to=8:1:1。

8.-XX:HeapDumpOnOutOfMemoryError:发生OOM时转储堆到文件,这是一个非常好的诊断方法。

9.-XX:HeapDumpPath:导出堆的转储文件路径。

10.-XX:OnOutOfMemoryError:OOM时,执行一个脚本,比如发送邮件报警,重启程序。后面跟着一个脚本的路径。

说明:

1、一般初始堆和最大堆设置一样,因为:现在内存不是什么稀缺的资源,但是如果不一样,从初始堆到最大堆的过程会有一定的性能开销,所以一般设置为初始堆和最大堆一样。64位系统理论上可以设置为无限大,但是一般设置为 4G ,因为如果再大,JVM进行垃圾回收出现的暂停时间会比较长,这样全GC过长,影响JVM对外提供服务,所以不能太大。一般设置为4G。

2、-XX:NewRaio和-XX:SurvivorRatio这两个参数,都是设置年轻代和年老代的大小的,设置一个即可,第一是设置年轻代的大小,第二个是设置比值,理论上设置一个既可以满足需求

打印GC回收的过程日志信息

以下配置主要针对分代收集回收算法而言

年轻代的设置很关键

JVM中最大堆大小有三方面限制:相关 *** 作系统的数据模型(32bit还是64bit)限制:系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G-2G64位 *** 作系统对内存没有限制。在Windows Server 2003系统,3.5G物理内存,JDK5.0下测试,最大设置为1478m。

典型设置:

JVM给了三种选择:串行收集器,并行收集器,并发收集器,但是串行收集器只适用于小数据量的情况,一般不考虑使用了,所以这里只针对并行收集器和并发收集器。默认情况下,JDK5.0以前是使用的串行收集器,如果想使用其他收集器需要在启动时加入相应的参数, JDK5.0以后,JVM会根据系统当前的配置进行判断

吞吐量优先的并行收集器

并行收集器主要以到达一定的吞吐量为目标,适用于后台处理

响应时间优先的并发收集器

并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

6.1年轻代大小选择

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

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

6.2年老代大小选择

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

1、并发垃圾收集信息

2、持久代并发收集次数

3、传统GC信息

4、花在年轻代和年老代回收上的时间比例减少年轻代和年老代花费的时间,一般会提高应用的效率

6.3吞吐量优先的应用

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

6.4较小堆引起的碎片问题

因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:

Jconsole,jProfile,VisualVM

Jconsole:jdk自带, 功能简单,但是可以再系统有一定负荷的情况下使用,对垃圾回收算法有很详细的跟踪。

JProfiler:商业软件,需要付费,但是功能强大

VisualVM:JDK自带,功能强大,与Jprofiler类似,推荐

观察内存释放情况、集合类检查,对象树

上面这些调优工具都提供了强大的功能,但是总的来说一般分为以下几类功能:

一般就是根据垃圾回收前后情况对比,同时根据对象引用情况( 常见的集合对象引用 )分析,基本都可以找到泄漏点。

持久代沾满处理:

1、-XX:MaxPermSize=16m

2、换JDK比如:JRocket

系统内存被沾满:

一般是因为没有足够的资源产生线程造成的,系统创建线程时,除了要在Java堆中分配内存外, *** 作系统本身也需要分配资源来创建线程。因此,当线程数量大的一定程度以后,堆中或许还有空间,但是 *** 作系统分配不出资源来了,出现异常。

分配给Java虚拟机的内存越多,系统剩余的资源就越少,因此,当系统内存固定时,分配给Java虚拟机的内存越多,那么,系统总共能够产生的线程也就越少,两者成反比。同事,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共生产的线程数。

java程序内存问题的诊断方法:

查看jmap的命令参数,帮助查看堆信息


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/11980843.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-20
下一篇 2023-05-20

发表评论

登录后才能评论

评论列表(0条)

保存