不恰当的树数据结构导致内存占用过大(六)

1、案例介绍:

有一个后台PRC服务器,使用64为虚拟机,
内存配置为:

  1. -Xms4g(设定程序启动时占用内存大小)
  2. -Xmx8g(设定程序运行期间最大可占用的内存大小)
  3. -Xmn1g(设置年轻代大小为1G)

整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小

下面这个公式没有加上持久代
xmx=xmn+老年代大小
使用ParNew+CMS的垃圾回收器组合

2、出现的问题

平时对外服务的MinorGC 时间约为30毫秒以内,完全可以接受,但是如果业务上需要每10分钟加载一个约为80MB的数据文件到内存进行数据分析,这些数据会在内存中形成超过100万个HashMap<Long,Long>Entry,这段时间里面Minor GC就会造成超过500毫秒的停顿,这时候就不能接受,
这里你可能就有疑问了,为什么会超过那么长时间呢?
首先案例中也说了,新生代是1G内存
而Eden:From Survivor:To Survivor=8:1:1,那Eden相当于800MB大小内存

再来分析一下一个Map有多大:key和value存放的两个长整型数据就有16B(2x8B),包装成Long对象后分别有8B的,8B的Klass指针,再加8B存储数据的Long值,在组成Map.Entry后,又多16B的对象头,一个8B的next字段和4B的int型的hash字段,为了对齐还要加4B的空白填充,最后还有hashmap对这个Entry的8B的引用。
最后总结出 (Long(24B)x2)+Entry(32B)+HashMap Ref(8B)=88B,而数据也就16B 差不多5倍的扩容。

对于80MB的数据来说,如果转成HashMap<Long,Long>Entry 存在内存中,那就会占500MB差不多的内存大小(只是推算),实际不清楚多大,在Eden区肯定很快Minor GC,但是往Survivor 放?

3、初步的解决方法

(1)自己可以通过设置-XX:PretenureSizeThreshold参数(这个参数只对 SerialParNew两个收集器有效)来设置当对象大于多少时直接放进老年代,减少新生代的复制算法频繁GC,因为复制算法的高效体现在"朝生夕灭"上,而这些HashMap对象肯定存活的时间长一些,

(2)除了上面的大对象直接移入老年代,还可以把Survivor移除掉(参数 -XX:SurvivorRatio=65536-XX:MaxTenuringThreshold=0(年龄阈值),或者 -XX:+AlwaysTenure),新生代的所有对象Minor GC一次后就进入老年代,

这些都是治标不治本的,最大的问题还是数据结构设计的不合理,HashMap空间利用效率太低,才18%

发布了213 篇原创文章 · 获赞 22 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_43113679/article/details/100806062