java进程内存限制

我司所有java服务都是跑在容器里的,但是这些都是受cgroup限制的。但是经常发现有几个服务一直会被重启。虽然一般分配的内存都是heap的1.3到1.5倍。但是还是架不住经常重启。

而观察了一些,发现都是不是heap的gc的引起的。那问题就明显指向到了堆外。

经过参考几位大佬(主要是 qu dongfang)的文章。因为他把堆外内存包含了哪些都说清楚了,但是最后发现还是有漏的。于是把参数进行了如下调整,这个时候整个容器的内存大小改为了1536MB

1
2

-Xms1024m -Xmx1024m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -XX:CompressedClassSpaceSize=32m -XX:MaxDirectMemorySize=370m -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

可结果还是超,直接就被oom了。

qudongfang这位大佬在日志里是这样写的

1
2
3
4
5
6
7
8
9
10

广义堆内内存 = 狭义堆内内存 + 永久代(Perm)
狭义对内内存 = 新生代(New) + 老年代(Old) # Xmx Xms
新生代(New) = S0 + S1 + Eden # NewSize NewRatio SurvivorRatio

广义堆外内存 = 狭义堆外内存(directbytebuffer) # MaxDirectMemorySize,netty/mina等高性能网络通信常用,具体不是很了解
java栈 # 需关注,线程数 * ThreadStackSize(Xss)
native栈 # 不大,线程数 * VMThreadStackSize + CICompilerCount * CompilerThreadStackSize
pc寄存器 # 可忽略
jni(如带用c/c++ malloc)# 这个不可控,一般忽略就好

最后发现还有地方是在堆外里的

  • Unsafe.allocateMemory()是一个围绕os::malloc的包装, 这个是直接调用操作系统来申请的内存,根本不受vm里任何内存限制。 那这种问题就无解了。
  • spring boot 2.0.5.RELEASE 之前版本的原因。Spring
    Boot依赖于finalize机制去释放了堆外内存;但是glibc为了性能考虑,并没有真正把内存归返到操作系统。这个就是为什么我们看到明明有GC,但是你观察进程的RSS使用居然只会网上涨,而从来不会减少的原因。

而以上2点恰恰是这个服务的特征,不过还没有使用大佬的方法去进行验证下。

美团那篇文章居然还有老外翻译了一下,居然还有人在后面评论。然后好多中文的资料都是错误的。

参考:

https://icoding.live/2018/08/25/jvm-gc-intern-swap/
http://www.concurrent.work/docker/java/jvm/gc/pitfalls-about-running-java-inside-container/
https://www.jianshu.com/p/e1503204a059
https://www.jianshu.com/p/b6dedd95e9d9
http://cn.voidcc.com/question/p-fitcryus-hr.html
https://www.cayun.me/java/Java%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E4%B8%8E%E9%87%8A%E6%94%BE%E5%8E%9F%E7%90%86/
https://tech.meituan.com/2019/01/03/spring-boot-native-memory-leak.html