项目的jvm内存分析


项目的jvm内存分析

我们的项目是一个 java 写的高重删多副本二级存储,放在2U4节点服务器上跑,使用万兆交换机相连,写的速度能达到300M/s,但是读的速度只能达到30~40MB/s.分析,读速度之所以这么慢.主要是因为:为了实现重删,我们采用了数据变长分块,生成hash,最后利用全局hash库过滤重复数据,最后数据保存到当前正在使用的map文件.当多个文件同时写入的时候这个过程,把连续的数据变得离散. 使得读取数据的时候,硬盘每一次都是随机读.读性能相当差.为了提高读性能,我们对数据模型做了修改,对入口做了分组,把分组连续的数据做成定长1M块存储.

重构后,读性能确实上升了一倍,但是程序的稳定性不行.连续写入数据几个小时后,程序性能就开始慢慢往下降,最后直接内存溢出.

直接内存

使用htop看程序内存占用,发现占用的内存确实比实际配置的高很多.造成这种原因,因为我们使用了java nio 提供的直接内存接口,这部分内存都不是从堆中分配的:

  1. 显式分配
    ByteBuffer.allocateDirect(int size)
    

    程序里一些循环里面的临时ByteBuffer是使用直接内存生成的.这一部分,都改过来:

    ByteBuffer.allocate
    
  2. 使用了nio内存映射:
        FileChannel.map(MapMode mode,long position, long size)
    

    我们的hash索引是保存在文件里,当布隆过滤器没有命中,使用内存映射来真实读取.因为设计上的原因,基本上不会释放.

  3. jvm参数配置有问题

    -XX:+DisableExplicitGC 
    

    我们使用了DisableExplicitGC,因为大量Direct ByteBuffer 所以最后导致OOM.看了很多文章都建议不要开启.

DirectMemory 默认的大小是等同于JVM最大堆.所以最终看起来程序内存比我们配置的-Xmx 高很多.

堆内内存

  1. 开启** G1 gc log**后,查看日志发现有大量的mix gc,感觉这部分影响了系统性能.
    -XX:+UseG1GC -XX:+PrintGCDetails-verbose:gc -Xloggc:gc.log
    
  2. 使用jmx内存监控
        jstatd -J-Djava.security.policy=jstatd.all.policy
    

    监测程序内存情况,发现写入数据的时候old eden会不断往上涨,然后引发mix gc,不断重复这个过程.而Survivor eden不怎么变.就觉得非常奇怪.为什么直接就在old eden分配呢?

    对比老版本,看看有没有相同的情况,老版本在相同的jvm 参数下,跑的时候内存比较稳定.都是在servivor eden 上分配的.这下更苦逼,基本可以说明是:我们改出来的.

  3. 使用jmap 把整个程序的堆内存dump下来,看了看占了堆内存90%的是long[]byte[]再仔细细分,前者主要是用在了布隆过滤器上,后者也多是数据传递时候的一些数组.看不出什么问题.

  4. 偶然,把老版本的堆内存参数-Xmx配置低一些,发现老版本也出现相识的问题.这下好了,原来问题一直有,只是我们现在分配的数据多了,大了,更容易暴露出来.
    重新观察新程序gc,发现刚开始的时候,其实新版本也是在servivor eden上分配的,但是分配的量太多,G1后面就直接在old eden上分配了.

    最后解决方案:把jvm堆内存配置大一倍

    再观察,果然,程序的内存变化正常多了都是在servivor eden上分配,old eden基本能保存不动.

  5. 后面做复制的时候,想提高性能,使用到一些3M的数组.程序有复制任务的时候servivor edenold eden都会涨.
    于是在jvm 启动参数里增加了两个参数,内存回收看起有比较稳定:

    -XX:G1HeapReginSize=16m -XX:G1HeapWastePercent=10
    

    jvm堆中的servivor edenold eden会划分成很多个regin,G1HeapReginSize可以配置每个regin的大小.值必须是2的幂次方,所以只能是2m,4m,8m,16m才会起效.G1HeapWastePercent则是允许一定的堆内存浪费,可回收的内存超过堆得10%时,G1才开始进行GC.

打开文件数量过多.

程序持续跑一段时间,系统还会出现打开文件数量过多
使用命令来看

ll /proc/pid/fd/ |wc -l

打开使用channel打开的属性文件map,没有及时进行关闭.最终超过了系统允许打开的文件数量.


Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: