Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

堆外内存场景、分析及解决方法

问题:某环境发现给定JVM内存为200GB,实际使用量为250GB

...

最新情况:堆外内存增长到36GB之后就不继续增长了,可能有内部释放机制


堆外内存分析

0.文档结构

1-6节列举了一些分析过程和工具,第7节总结了排查方法,第8节是相关参考资料

 

1.使用堆外内存的原因:

避免与操作系统交互时jvm gc移动某一段buffer的位置,导致io操作使用不正确的地址[1]

推论:需要buffer的系统调用均需要堆外内存,要么是显式声明在java代码里(DirectBytebuffer),要么是隐含在c代码中(BufferedOutputStream

 

2.使用google-perf工具分析c语言调用与堆栈,确定问题[2][3]

1)安装流程(具体步骤在参考资料中,这里列出主要步骤)

  • 安装lib-uwind
  • 安装google-ppfor工具
  • 增加环境变量,改变jvm使用的malloc
  • 运行iotdb一段时间
  • 使用google-ppfor生成分析文件

28存储组,50设备,每个设备100个传感器下内存正常,堆外内存使用不多,在合理范围内,需要能够复现堆外内存的场景

 Image Added

注:图中红框就是申请堆外内存的部分

3)增加压力后出现JVM crash,是GC执行过程中的NULL指针,怀疑工具库实现有bug,这也意味着生产环境不能贸然使用该工具

Image Added

 

 

3.每一个IO thread都会cache一部分的堆外buffer,不会释放,所以解决堆外内存多有两个方法[4]

1)减少IO线程

2jdk版本大于1.8后可以使用-Djdk.nio.maxCachedBufferSize去限制cache buffer的使用

如果不限制,则每次申请direct buffer就会cache,除非使用了cache中的buffer,也就是说堆外内存只增不降

由此做一组实验,其他参数相同的情况下,增加thrift线程数(也就是client数),看堆外内存使用情况:

线程数

堆外内存使用

50

270MB

100

300MB

200

320MB

300

350MB

可以看出thrift线程数对堆外内存有影响,但是不是那么大,别的IO线程可能占了更大的部分,需要进一步分析。

0.11版本thrift线程池很大小,田原已经限制了WAL总的堆外内存池,很大程度上解决了问题

 

4.堆外内存的主要来源[5]

  • 线程与sockets
  • direct ByteBuffers的使用
  • 第三方库使用native内存(可以从google-ppfor中看出来)

 

5.内存分析神器——jxray[6]

虽然是收费软件,但是功能很强大,可以试用14天,可以分析出很多内存问题,

举个例子:iotdb中有大量重复的string

 Image Added


6.排查方法总结

1)使用jxray(见5)分析dump的堆,查看第18项和22项(Off-heap memory used by java.nio.DirectByteBuffers and Thread stacks),排查IO使用的buffer和线程栈空间

2)使用google-pprof(见2)生成本地方法内存使用图,查看本地方法(比如压缩,编码等)使用的内存空间


 

7.参考资料

[1]https://stackoverflow.com/questions/5670862/bytebuffer-allocate-vs-bytebuffer-allocatedirect (based on book of java ino)

[2] https://blog.csdn.net/21aspnet/article/details/88032700

[3] https://blog.csdn.net/woaiwojia6699/article/details/111224528

[4] https://dzone.com/articles/troubleshooting-problems-with-native-off-heap-memo

[5]https://stackoverflow.com/questions/39329455/java-non-heap-memory-analyzes