问题的提出,分析,请参考JNI——小心,内存怪兽出没
(简单的说起来,就是java进程占用了4G内存,但是折腾来折腾去,整个JVM的堆才100M上下,其余的内存凭空消失?刨根问底之后,原来是native heap占用了内存)
看完上面的问题,再来看解决方案:
目前看来,通过调整 JVM 参数来加大 GC 触发的频率是比较现实的一种方式。下面是一些分析过程:
一. 判断 Memonry 内存所属的区
通过 jconsole 可以看到 gc 信息,其中新生代的 scavenge 回收比较频繁,如果 Memonry 在这个区应该不存在长期得不到 gc 的情况
。由此推测 Memory 应该是在 Tenured/old 区 ,而这个区的内存回收次数恰好非常少。 当然参数优化的目标,就是加大 old 区的 gc 频率。
二. 观察 JVM 的堆参数配置
1. 默认配置下,堆的分配起始值为 126M ,最大 1.8G 。
2. 观察 gc 前后 java 整个堆 的使用情况, gc 前大概占 130M, 远未达到默认设置下的内存容量
3. 进一步查看 old 区的内存 ,发现在 gc 前稳定在 90M 。
而默认参数初始分配 85M ,最大 1.3G ,显然很难触发到 GC 。
三. 解决问题
1. 首要的问题就是控制 JVM 对整个 heap 的大小分配:
-Xmx300M 指定 jvm 的最大 heap 大小 ,
-Xms40M 指定 jvm 的最小 heap 大小 ,
2. 在减小了整个堆的前提下,优化 NewRatio 这个参数 ( 指定 jvm 中 Old Generation heap size 与 New Generation 的比例 )
将默认的 NewRatio=2 更改为更符合业务实际内存使用比例的 -XX:NewRatio=1
减小了 old 区的比例,更容易触发 gc 。
3. 另外一个猜测的可行方案:(有待验证)
启用 jvm 中的 gc 参数 -XX:MaxGCPauseMillis=<nnn>
这个参数的大概含义就是,让每次 gc 的时间不超过参数 nnn 。那么 nn 减少的时候,必然会增加到 gc 的次数,来换取每次 gc 的速度。
通过下图的数据可以求出,每次 old 区的 gc 平均耗时 34.7ms , 那么可以 -XX:MaxGCPauseMillis=28 ,也许会增加到 gc 频率。
上述 4 个标红参数已经添加 ,观察几天得到的结论是,比较好的解决了这个问题。
====================================================================
分割线:下面是调优后的观察:
可以看到整个内存呈波动结构,Java的堆从20M--》60M就触发一次old区的gc。
下面看看GC的情况: MarkSweep是old区的gc策略,大概2个小时会触发一次,每次耗时
3194/112=28.5178ms,不会对应用产生明显的停摆,并且也验证了MaxGCPauseMillis参数的作用