jvm

[翻译]Jvm的Xmx参数详解

Posted by zhida on October 22, 2016

转载请注明出处 来源:paraller’s blog

场景

在项目中指定了 -Xmx参数,但是在实际的监测中发现内存的使用量大于指定的值,查阅了一篇外文网站,简单翻译一下

yeamsg:
  image: docker.umiit.cn:5043/v3/yea-msg:latest
  volumes:
    - /mnt/docker-data/logstash/yea-msg:/usr/local/maven/logs
    - /etc/localtime:/etc/localtime:ro
    - /etc/timezone:/etc/timezone:ro
  environment: 
    JVM_ARGS: -Xmx2200m
  ports:
    - "0.0.0.0:8083:8083"

正文

在启动脚本中指定了 -Xmx参数,然后认为Java进程不会吃掉你设置好的内存大小,然后在午夜你发现你的程序吃掉了生产环境4G的内存,然后程序也挂掉了。

首先,有一种可能是你的源代码造成内存泄露了,但99%的可能性是因为JVM正常的表现,因为你指定的 -Xmx只是指定了你程序的堆内存大小。

除了堆内存,你的应用程序还需要其他的内存区域:称为永久代和虚拟机栈,所以你要限制程序的使用大小,还应该分别指定 -XX:MaxPermSize and -Xss

简单来说,你可以使用下面的公式来推断使用情况:

Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss] 

但是除了你的程序需要消耗内存,JVM自身也需要空间,因为以下几个原因:

  • 垃圾回收,Java是一门垃圾回收语言,为了判断哪个对象应该被回收,需要保持一个对象关系图谱,所以需要预留空间。

  • JIT 优化. JIT编译,会把加载好的类文件编译成机器码,Java虚拟机在运行时会再一次优化代码 , Java Virtual Machine optimizes the code during the runtime. Again, to know which parts to optimize it needs to keep track of the execution of certain code parts. So again, you are going to lose memory.

  • 堆外分配:如果你想使用堆外内存,比如当你直接或映射使用ByteBuffers类或者使用第三方框架,你会增加堆内存使用量但是你无法通过JVM的配置文件来控制空间。

  • JNI code. 当你使用native code 去编写两种不同类型的数据库驱动示例,相当于你把代码加载进本地的内存

  • 元空间. 如果你使用的是Java 8, 使用了元空间去替代原来储存类生命信息的的永久代 ,它是无限制大小的,并且使用的是本地的内存.

关于内存消耗的原因上面已经说了,但是我们应该怎样预测我们的需要多大内存?或者至少要明白为什么消耗了内存,这样我们才可以去优化。

经过我们的长时间痛苦的经验,我们发现其实没有什么好办法去预测使用量,JVM的不同时间的使用量的波动范围太大了。最好的办法就是好的警告和错误提示,所以你在开发的时候要保证你的开发环境和你的生产环境服务器的配置尽量一致。 在本地通过检测工具,比如Mac的活动监视器,linux的top命令,查看实际的内存消耗,减去堆内存和永久代的空间大小,就是偏差的应用的开销了。

现在如果你想要减少你的开销,你想要知道你的内存是怎么消失的,Mac OS X的vmmap工具 和 Linux 的pmap 工具 是很有用的

以下是示例,我使用下面的配置启动我的Jetty

-Xmx168m -Xms168m -XX:PermSize=32m -XX:MaxPermSize=32m -Xss1m 

我的应用程序有30个线程在跑,计算得出最多不超过230M内存的损耗,但是当我查看Mac系统的活动监视器的时候,数据有些不一样。

实际的内存消耗超过了320M,现在我们借助 vmmap 来输出相关信息去了解内存为什么消耗了,以下是示例:

下面的信息说明 rt.jar 消耗了大概2M的空间.

mapped file 00000001178b9000-0000000117a88000 [ 1852K] r--/r-x SM=ALI /Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home/jre/lib/rt.jar - 

使用了 ~6MB 加载动态库

__TEXT 0000000104573000-0000000104c00000 [ 6708K] r-x/rwx SM=COW /Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home/jre/lib/server/libjvm.dylib - See more at: http://plumbr.eu/blog/why-does-my-java-process-consume-more-memory-than-xmx?utm_source=rss&utm_medium=rss&utm_campaign=rss20130618#sthash.G8fx60eX.dpuf

然后25-30号线程被每个被分配了 1MB 用于栈空间和维护栈

Stack 000000011a5f1000-000000011a6f0000 [ 1020K] rw-/rwx SM=ZER thread 25 Stack 000000011aa8c000-000000011ab8b000 [ 1020K] rw-/rwx SM=ZER thread 27 Stack 000000011ab8f000-000000011ac8e000 [ 1020K] rw-/rwx SM=ZER thread 28 Stack 000000011ac92000-000000011ad91000 [ 1020K] rw-/rwx SM=ZER thread 29 Stack 000000011af0f000-000000011b00e000 [ 1020K] rw-/rwx SM=ZER thread 30 - See more at: http://plumbr.eu/blog/why-does-my-java-process-consume-more-memory-than-xmx?utm_source=rss&utm_medium=rss&utm_campaign=rss20130618#sthash.G8fx60eX.dpuf

STACK GUARD 000000011a5ed000-000000011a5ee000 [ 4K] ---/rwx SM=NUL stack guard for thread 25 STACK GUARD 000000011aa88000-000000011aa89000 [ 4K] ---/rwx SM=NUL stack guard for thread 27 STACK GUARD 000000011ab8b000-000000011ab8c000 [ 4K] ---/rwx SM=NUL stack guard for thread 28 STACK GUARD 000000011ac8e000-000000011ac8f000 [ 4K] ---/rwx SM=NUL stack guard for thread 29 STACK GUARD 000000011af0b000-000000011af0c000 [ 4K] ---/rwx SM=NUL stack guard for thread 30 - See more at: http://plumbr.eu/blog/why-does-my-java-process-consume-more-memory-than-xmx?utm_source=rss&utm_medium=rss&utm_campaign=rss20130618#sthash.G8fx60eX.dpuf

网站参考

Why does my Java process consume more memory than Xmx?