Java 的线程到底占用了多少内存?

您所在的位置:网站首页 查看java程序内存占用多少G Java 的线程到底占用了多少内存?

Java 的线程到底占用了多少内存?

2024-07-15 13:02| 来源: 网络整理| 查看: 265

原文排版比较好.      原文链接:  https://mp.weixin.qq.com/s/wA3pUemz5oWJX6Zp9HFIGA

 

 

若是有人问你正在运行的 Java 程序的堆占用了多少内存, 你一个命令就给出了答案; 若是有人问你正在运行的 Java 程序的线程栈使用了多少内存, 该怎么得到答案呢? 

 

故事背景

    有人的 Java 程序遇到了 OOM, 程序崩溃之前, 只给出了这么一句关键遗言: "java.lang.OutOfMemoryError: unable to create new native thread".  从这一句关键的遗言中, 我们并不能完全推导出它崩溃之前到底发生了什么事情. Google 给出的答案里, 有的说是遇到的操作系统的 limits 限制, 有的说内存真的被用光了.

     本文并不想去探讨这个 OOM 的具体原因, 而是去追问其中的一个分支问题:  Java 的线程到底占用了多少物理内存? 

 

有关 Xss 和  ThreadStackSize

     首先, 根据用途, Java 的内存使用可以分为: 堆区 (年轻代, 老年代, 元数据区) , 栈区, 编译后的代码区, 编译器代码区, GC 管理程序区, JVM 自身的代码区 和 符号区等. 一般情况下, 占大头的是堆区. 栈区根据线程数可能大小不一. 

      在 JVM 的 flags 里面, 有2个参数是与栈大小相关的. 分别是 -Xss 和 -XX:ThreadStackSize. 我们可以认为2个 flags 其实代表一个意思, 只是一个是简写, 一个是全量写法.  根据官方文档, 它设置的是一个线程 Stack 的大小. 若是不设置, 根据操作系统的不同, 有不同的默认值. 如 64位的 Linux 下, 默认是1MB. 

 

线程栈的大小

      是不是根据Xss 的值乘以线程数, 就得到了所有线程栈占用的物理内存大小呢?  于是, 我找到一个基于 JDK 8, 正在运行, 并且线程数目巨大(其实是有线程泄漏的 bug)的程序. 使用 NMT 得到了下面的结果:

 

图片

 

       这代表什么呢? 当时的活跃线程数大概是13991 个, 栈所声明要使用的内存数14454495KB, 实际提交 (committed) 的 内存约 13.71G. 这是一个声明了使用8核16G 内存的 container 进程, 而这个 Java 进程的栈却告诉我们: 栈使用了13.71G 内存, 加上堆占用的8个多G, 该进程已经提交了24G 多的内存占用 (没开 SWAP). 这明显已经矛盾. 

      

       同时, 我们通过 ps 命令可以看到, 该进程占用大约13G 的 RSS 内存. Container 设置的内存最大值是16G, 当前还有500M 的空余. (该 Container 是一个 fat Container, 里面还有其它辅助进程).  这是一个合理的情形. 

 

图片

 

      数据对不上, 至少有一个人在说谎. 

 

NMT 的 bug

       NMT 作为 JVM 提供的一个追踪原生内存使用量的工具, 最早主要用来追查内存泄漏的. 主要的内存泄漏大都集中在堆区. 对于栈区, 早期的 NMT 的计算方式主要以线程数乘以每个线程可以使用的最大内存量( Xss)得到的.  所以, 直到2018年, 有人报了这个问题, 才有了这个修复: https://bugs.openjdk.java.net/browse/JDK-8191369.  

      但是这个修复主要修了 Linux 和 Windows 版本. 所以即便我在 MAC 上下载了 JDK 15的 release 版本, 依旧有这个数据问题. 

 

修复后的结果

     我找了一个 Linux 上基于 JDK 11 的程序, 使用 NMT 之后, 终于看到了想要的结果: 

 

图片

 

      这里大约有310个运行中线程, 使用了大概38M, 平均使用100K 多一些. 这才是真正的结果. 

 

为什么保留1M, 却只使用了100K?

      这其实就是虚拟内存和真实物理内存差异的原因.  若 Xss 要求是1M, 那么每个线程会申请1M的虚拟内存, 可是大部分线程并不会使用这么多, 也就没必要占用这么多物理内存, 使用多少个页(匿名 page), 就提交多少个页. 若按照每页4K 计算, 也就是平均25个页左右, 就满足了大部分线程的内存需求. 

 

     另外, 如果我们查看 IBM JDK 或 Eclipse OpenJ9的文档, 我们可能看到另外2个启动的 flags, 分别是: -Xiss 和 -Xssi. 分别代表栈的 Initial Stack Size (初始值) 和 Stack Size Increment (渐增值). 所以我们之前讨论的 Xss 代表最大值. 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3