转载

[译] Java 进程中有哪些组件会占用内存?

(给 ImportNew 加星标,提高Java技能)

编译:唐尤华

本文来自 StackOverflow 的一个问答: Java using much more memory than heap size (or size correctly Docker memory limit)  

题主发现 Java 进程占用内存远超过堆内存设置的大小 ,于是提出了下面的问题:

有谁能解释为什么 Java 进程占用内存远超过堆内存大小? 如何正确计算 Docker 内存限制? 有没有办法减少 Java 进程的堆外内存(off-heap memeory)占用?

"下面是热心网友的答复"

Java 进程使用的虚拟内存远远超过 Java 堆大小。 要知道 JVM 包括许多子系统,垃圾回收器、类装载器、JIT 编译器等等。 所有这些子系统运行都需要占用内存。

JVM 不是内存唯一的消费者,Java Class Library 在内的所有 Native Library 也会占用内存。 对于内存跟踪工具来说这些开销甚至无法跟踪。 Java 应用程序本身还可以通过直接 `ByteBuffers` 使用堆外内存。

1. 究竟 Java 进程中有哪些组件会占用内存?

通过 Native Memory Tracking 可以观察到有以下 JVM 组件。

1.1 Java 堆

最显而易见的就是 Java 堆,它是 Java 对象存在的地方。 它会占用 `-Xmx` 参数指定大小的内存。

1.2 垃圾回收器

GC 需要额外的内存进行堆管理,主要用于 GC 自身的结构与算法。 这些结构包括 Mark Bitmap、Mark Stack(遍历对象关系图)、Remembered Set(记录 region 之间引用)等等。 其中一些可以直接调优,例如 `-XX: MarkStackSizeMax` 选项,另一些依赖于堆布局。 其中 G1 region (`-XX:G1HeapRegionSize`)占用内存较大,Remembered Set 占用内存较小。

GC 的内存开销因算法而异,其中 `-XX:+UseSerialGC` 与 `-XX:+UseShenandoahGC` 的开销最小,而 G1 或 CMS 则会轻松占用大约10%的堆内存。

1.3 代码缓存

代码缓存包含动态生成的代码,JIT 编译生成的方法、解释器以及运行时 stub 代码。 代码大小受 `-XX:ReservedCodeCacheSize` 选项限制(默认为240M)。 关闭 `-XX:-TieredCompilation` 可以减少已编译代码的数量,从而减小代码缓存。

1.4 编译器

JIT 编译器本身工作时也需要内存。 可以通过关闭 Tiered Compilation 或者 `-XX:CICompilerCount` 减少编译使用的线程数。

1.5 类加载

类的元数据存储在 Metaspace 堆外区域中,包括方法字节码、符号、常量池、注解等。 加载的类越多,使用的元数据就越多。 可以通过 `-XX:MaxMetaspaceSize`(默认无上限)和 `-XX:CompressedClassSpaceSize`(默认1G)选项控制元数据总大小。

1.6 符号表

JVM 有两个主要的 hashtable: 符号表包含名称、签名、标识符等,String 表包含对 interned String 引用。 如果 Native Memory Tracking 显示 String 表使用了大量内存,这可能意味着应用程序调用 String.intern 过于频繁。

1.7 线程

线程堆栈也会申请内存。 堆栈大小由 `-Xss` 选项指定,默认每个线程1M,幸运的是情况并非那么糟糕。 操作系统会以延迟分配的方式分配内存页面,比如在第一次使用时分配,因此实际使用的内存要低得多,通常每个线程堆栈占用80至200KB。 我编写了一个[脚本][1]评估有多少 RSS 属于 Java 线程堆栈。

[1]:https://github.com/apangin/jstackmem

还有其他 JVM 部件会占用本地内存,但它们在总内存消耗中通常比例不大。

2. Direct Buffer

应用程序可以通过 ByteBuffer.allocateDirect 调用直接请求非堆内存。 默认的非堆内存大小限制由 `-Xmx` 选项指定,但也可以使用 `-XX:MaxDirectMemorySize` 覆盖配置。 Direct ByteBuffer 包含在 Native Memory Tracking 输出的 Other 区域,在 JDK 11 之前包含在 Internal 区域。

通过 JMX 可以在 JConsole 或 Java Mission Control 中直接看到 Direct Memory 的使用量:

[译] Java 进程中有哪些组件会占用内存?

除了 Direct ByteBuffer,还有 `MappedByteBuffer` 映射到进程虚拟内存中的文件。 虽然 Native Memory Tracking 不对它跟踪,但是 `MappedByteBuffer` 也会占用物理内存,而且没有一种简单的方法限制它申请的内存大小。 可以通过查看进程内存映射了解实际的内存使用情况: `pmap-x <pid>`。

``shell
Address           Kbytes    RSS    Dirty Mode  Mapping
...
00007f2b3e557000   39592   32956       0 r--s- some-file-17405-Index.db
00007f2b40c01000   39600   33092       0 r--s- some-file-17404-Index.db
                           ^^^^^               ^^^^^^^^^^^^^^^^^^^^^^^^
```

3. Native Library

`System.Loadlibrary` 加载的 JNI 代码可以不受 JVM 控制分配堆外内存,标准 Java Class Library 也是如此。 尤其是未关闭的 Java 资源可能造成本地内存泄漏。 典型的例子是 `ZipInputStream` 和 `DirectoryStream`。

JVMTI 代理,尤其是 jdwp 调试代理,也会造成内存消耗过多。

[这个回答][2]描述了如何使用 [async-profiler][3] 分析本地内存分配。

[2]:https://stackoverflow.com/a/53598622/3448419

[3]:https://github.com/jvm-profiling-tools/async-profiler/

4. Allocator 问题

进程通常通过 mmap 系统调用直接从操作系统分配内存,或者使用标准的 libc allocator —— malloc 分配本机内存。 反过来,malloc 会调用 mmap 向操作系统申请大块内存,然后根据自己的分配算法管理内存块。 问题在于这种算法会造成碎片化以及[过度使用虚拟内存][4]。

[4]:https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

[jemalloc][5] 是 libc malloc 的一个更智能的替代选项,使用 jemalloc 占用内存会变得更小。

[5]:http://jemalloc.net/

5. 总结

因为有太多的因素需要考虑,没有一种可靠的方法可以用来评估一个 Java 进程所有的内存使用量。

``
总内存 = 堆 + 代码缓存 + Metaspace + 符号表 +
        其他 JVM 结构 + 线程堆栈 +
        Direct Buffer + 映射文件 +
        Native Library + Malloc 开销 + ...
```

虽然可以通过设置 JVM 参数缩小或限制类似代码缓存这样的区域,但是其他许多区域根本不受 JVM 控制。

设置 Docker 限制的一种可能的方法是观察进程“正常”状态下的实际内存使用情况。 有一些工具和技术可以用来研究 Java 内存消耗问题,[Native Memory Tracking][6]、[pmap][7]、[jemalloc][5]、[async-profiler][3]。

[6]:https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html

[7]:http://man7.org/linux/man-pages/man1/pmap.1.html

推荐阅读

(点击标题可跳转阅读)

Java 多线程基础:进程和线程之由来

从 Java 进程里 dump 出类的 class 文件的小工具 – dumpclass

内存不足:杀死进程还是牺牲子进程

看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

[译] Java 进程中有哪些组件会占用内存?

好文章,我 在看 :heart:

原文  https://mp.weixin.qq.com/s/dC1jK0wLKFbVO24PzNg1Bg
正文到此结束
Loading...