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

有网友留言,今天去参加面试,面试官上来就问:你能解释为什么 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。我编写了一个脚本评估有多少 RSS 属于 Java 线程堆栈。

还有其他 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 的使用量:

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

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 调试代理,也会造成内存消耗过多。

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

4. Allocator 问题

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

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

5. 总结

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

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

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

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

面试官上来就问:Java 进程中有哪些组件会占用内存?相关推荐

  1. 面试java你最擅长什么_面试官最喜欢问的10道Java面试题

    1.Java的HashMap是如何工作的? HashMap是一个针对数据结构的键值,每个键都会有相应的值,关键是识别这样的值. HashMap 基于 hashing 原理,我们通过 put ()和 g ...

  2. 面试官95%会问的接口测试知识

    接口测试最近几年被炒的火热了,越来越多的测试同行意识到接口测试的重要性.接口测试为什么会如此重要呢? 主要是平常的功能点点点,大家水平都一样,是个人都能点,面试时候如果问你平常在公司怎么测试的,你除了 ...

  3. 安卓application_阿里面试官刁钻连问:安卓 UID的分配、查看及相关知识

    面试难度还行,但是如果不好好复习,有几个点还真是答不上来. uid机制 uid:Android系统中uid用于标识一个应用程序,uid在应用安装时被分配,并且在应用存在于手机上期间,都不会改变.一个应 ...

  4. 阿里的面试官都喜欢问哪些问题?

    作者:徐刘根 | 公众号:Java之间 金九银十是招聘的旺季,小编在这里也给大家整理了一套阿里面试官最喜欢问的问题或者出场率较高的面试题,助校招或者社招路上的你一臂之力! 首先我们需要明白一个事实,招 ...

  5. 为什么面试官总喜欢问String?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 作者:tan日拱一兵 来源:公众号「日拱一兵」 关于 Java String,这是面试的基础 ...

  6. 面试官最爱问的并发问题

    转载自  面试官最爱问的并发问题 在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(J ...

  7. 面试官最爱问的Redis(三)Redis的基本知识

    面试官最爱问的redis,继续整理了Redis的学习笔记,动力节点的redis视频,13个小时搞定redis,笔记分享给大家. 视频资源:https://www.bilibili.com/video/ ...

  8. 如果我说熟悉SpringBoot 面试官会怎么问?

    SpringBoot 因简化了 Spring 框架使用难度,极大地提高了Java企业级应用开发的效率,成为企业考核人才的重要标准之一.但随着现今互联网行业快速发展.企业业务不断深入,相应地对 Spri ...

  9. 面试官:你在xx项目中有哪些亮点或是贡献亦或是小技巧?

    前言 面试官:你在xx项目中有哪些亮点或是贡献亦或是小技巧? 我:阿巴阿巴 卡!停一下,你是不是也有相同或者类似的经历?实际大部分同学们多数情况下都是在使用vue或react去实现业务代码,跟业务代码 ...

最新文章

  1. redis实现对账(集合比较)功能
  2. CF359D:Pair of Numbers(数论)
  3. java四种访问权限
  4. cdr 表格自动填充文字_做平面广告设计,AI和CDR如何选择?
  5. shell学习笔记二则:统计空间
  6. (二)预处理时间序列数据用于AI预测和异常检测
  7. java正则表达式匹配`\`
  8. diskcheck.sh
  9. 牛逼!国产开源的远程桌面火了,只有 9MB,支持自建中继器!
  10. dubbo kryo序列化_Java后端精选技术:序列化框架的选型和比对
  11. 软考 系统架构设计师 2009-2018年英语翻译及重点词汇
  12. (四轴)无人机工作原理及组成
  13. mkv文件用什么软件打开_什么是MKV文件,以及如何播放它们?
  14. Found existing installation:xxxx
  15. Linux系统之磁盘管理
  16. 视觉SLAM十四讲CH9代码解析及课后习题详解
  17. Redis Cluster集群原理+三主三从交叉复制实战+故障切换(十)
  18. web前端开发工程师的三种级别,技术决定你能拿5K,还是15K,还是25K
  19. 实验二十三——RPL协议仿真实验
  20. 用html写除法竖式代码,除法的竖式写法

热门文章

  1. 信息系统项目管理师-组织级项目管理与大型项目管理知识点
  2. centos 7配置firewall防火墙的地址伪装和端口转发实例
  3. Kotlin on Android 开发环境介绍
  4. 关于金蝶k3 wise供应生门户登陆界面屏蔽业务账套多余功能模块设置方法
  5. Linux学习笔记6——文本处理相关命令
  6. Script:GridControl Repository Health Check
  7. 【点击模型学习笔记】Predicting Clicks_Estimating the Click-Through Rate for New Ads_MS_www2007...
  8. Q学习(Q learning) 强化学习
  9. MOPSO 多目标粒子群优化算法
  10. python 转成摩尔斯电码_一篇文章教会你摩尔斯码