JVM常用概念之本地内存跟踪

news2025/3/11 9:10:57

问题

Java应用启动或者运行过程中报“内存不足!”,我们该怎么办?

基础知识

对于一个在本地机器运行的JVM应用而言,需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构,来保证JVM应用的成功启动以及未来平稳的运行,然而JVM在运行期间会面临各种不同情况下的动态处理,比如动态加载、热编译等会产生大量的类,从而在运行时会产生足够的生成代码,这种情况是JVM默认该应用程序会长期运行的,而对于只是短期运行的JVM应用程序而言是不需要这样处理的。

OpenJDK8以及之后的版本提供了一个叫做本地内存跟踪(NMT)的工具,该工具可以知道JVM内部的内存分配,对分析JVM内存相关问题是否有帮助。

我们可以使用 -XX:NativeMemoryTracking=summary 启用 NMT。您可以让 jcmd 转储当前 NMT 数据,或者可以使用-XX:+PrintNMTStatistics在 JVM 终止时请求数据转储。输入 -XX:NativeMemoryTracking=detail 将获取 mmaps 的内存映射和 mallocs 的调用堆栈。

实验

测试用例源码

public class Hello {
  public static void main(String... args) {
    System.out.println("Hello");
  }
}

执行结果

JVM参数

-Xmx16m -Xms16m

NMT结果

Native Memory Tracking:

Total: reserved=1373921KB, committed=74953KB
-                 Java Heap (reserved=16384KB, committed=16384KB)
                            (mmap: reserved=16384KB, committed=16384KB)

-                     Class (reserved=1066093KB, committed=14189KB)
                            (classes #391)
                            (malloc=9325KB #148)
                            (mmap: reserved=1056768KB, committed=4864KB)

-                    Thread (reserved=19614KB, committed=19614KB)
                            (thread #19)
                            (stack: reserved=19532KB, committed=19532KB)
                            (malloc=59KB #105)
                            (arena=22KB #38)

-                      Code (reserved=249632KB, committed=2568KB)
                            (malloc=32KB #297)
                            (mmap: reserved=249600KB, committed=2536KB)

-                        GC (reserved=10991KB, committed=10991KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=608KB, committed=608KB)

-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=2KB #23)
                            (arena=131KB #3)

-                  Internal (reserved=9444KB, committed=9444KB)
                            (malloc=9412KB #1373)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=1356KB, committed=1356KB)
                            (malloc=900KB #65)
                            (arena=456KB #1)

-    Native Memory Tracking (reserved=38KB, committed=38KB)
                            (malloc=3KB #41)
                            (tracking overhead=35KB)

-               Arena Chunk (reserved=237KB, committed=237KB)
                            (malloc=237KB)

由上述执行结果可以看出,分配的16m的堆,但是NMT中确显示占用了75m的内存,这是为什么呢?

原因分析

GC部分
GC (reserved=10991KB, committed=10991KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=608KB, committed=608KB)

由上图可知, GC malloc 分配了大约 10 MB,mmap 分配了大约 0.6 MB。如果这些结构描述了有关堆的某些内容(例如,标记位图、卡表、记忆集等),则应该可以预期它会随着堆大小的增加而增长。事实上确实如此:

# Xms/Xmx = 512 MB
-                        GC (reserved=29543KB, committed=29543KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=19160KB, committed=19160KB)

# Xms/Xmx = 4 GB
-                        GC (reserved=163627KB, committed=163627KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=153244KB, committed=153244KB)

# Xms/Xmx = 16 GB
-                        GC (reserved=623339KB, committed=623339KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=612956KB, committed=612956KB)

有上述运行结果可知,很可能 malloc 分配的部分是并行 GC 任务队列的 C 堆分配,mmap 分配的区域是位图。毫不奇怪,它们会随着堆大小而增长,并从配置的堆大小中占用约 3-4%。这引发了部署问题,就像原始问题一样:将堆大小配置为占用所有可用物理内存将超出内存限制,可能会触发OOM内存溢出异常。

但该开销还取决于所使用的 GC,因为不同的 GC 选择以不同的方式表示 Java 堆。例如,切换回 OpenJDK 中最轻量的垃圾回收器,例如:-XX:+UseSerialGC ,在我们的测试用例中会产生以下显著变化:

-Total: reserved=1374184KB, committed=75216KB
+Total: reserved=1336541KB, committed=37573KB

--                     Class (reserved=1066093KB, committed=14189KB)
+-                     Class (reserved=1056877KB, committed=4973KB)
                             (classes #391)
-                            (malloc=9325KB #148)
+                            (malloc=109KB #127)
                             (mmap: reserved=1056768KB, committed=4864KB)

--                    Thread (reserved=19614KB, committed=19614KB)
-                            (thread #19)
-                            (stack: reserved=19532KB, committed=19532KB)
-                            (malloc=59KB #105)
-                            (arena=22KB #38)
+-                    Thread (reserved=11357KB, committed=11357KB)
+                            (thread #11)
+                            (stack: reserved=11308KB, committed=11308KB)
+                            (malloc=36KB #57)
+                            (arena=13KB #22)

--                        GC (reserved=10991KB, committed=10991KB)
-                            (malloc=10383KB #129)
-                            (mmap: reserved=608KB, committed=608KB)
+-                        GC (reserved=67KB, committed=67KB)
+                            (malloc=7KB #79)
+                            (mmap: reserved=60KB, committed=60KB)

--                  Internal (reserved=9444KB, committed=9444KB)
-                            (malloc=9412KB #1373)
+-                  Internal (reserved=204KB, committed=204KB)
+                            (malloc=172KB #1229)
                             (mmap: reserved=32KB, committed=32KB)

请注意,这改进了“GC”部分,因为分配的元数据更少,也改进了“线程”部分,因为从并行(默认)切换到串行 GC 时需要的 GC 线程更少。这意味着我们可以通过调低并行、G1、CMS、Shenandoah 等的 GC 线程数来获得部分改进。我们稍后会看到线程堆栈。请注意,更改 GC 或 GC 线程数将对性能产生影响— 通过更改这一点,您将选择时依据时间复杂度和空间复杂度进权衡。

“类”部分仍然有改进的空间,因为元数据表示略有不同。我们能从“类”这个角度进一步缩减内存的占用吗?让我们尝试使用-Xshare:on启用的类数据共享 (CDS) :

-Total: reserved=1336279KB, committed=37311KB
+Total: reserved=1372715KB, committed=36763KB

--                    Symbol (reserved=1356KB, committed=1356KB)
-                            (malloc=900KB #65)
-                            (arena=456KB #1)
-
+-                    Symbol (reserved=503KB, committed=503KB)
+                            (malloc=502KB #12)
+                            (arena=1KB #1)

从上述结果来看,还有有效果的。

线程部分
-                    Thread (reserved=11357KB, committed=11357KB)
                            (thread #11)
                            (stack: reserved=11308KB, committed=11308KB)
                            (malloc=36KB #57)
                            (arena=13KB #22)

从上述线程相关的内容可以看出,线程占用的大部分空间都是线程堆栈。您可以尝试使用-Xss将堆栈大小从默认值(本例中为 1M)缩减为更小的值。请注意,这会导致出现StackOverflowException 的异常的风险更大,因此如果您确实更改了此选项,请务必测试软件的所有可能配置,以防出现不良影响。大胆使用-Xss256k将其设置为 256 KB 可得到以下结果:

-Total: reserved=1372715KB, committed=36763KB
+Total: reserved=1368842KB, committed=32890KB

--                    Thread (reserved=11357KB, committed=11357KB)
+-                    Thread (reserved=7517KB, committed=7517KB)
                             (thread #11)
-                            (stack: reserved=11308KB, committed=11308KB)
+                            (stack: reserved=7468KB, committed=7468KB)
                             (malloc=36KB #57)
                             (arena=13KB #22)

从上述结果来看,效果还不错,在有大量线程的场景下,这种优化配置后的内存使用优化效率会更加明显,同时线程也使继Java堆后的第二大内存消耗者。

那线程是否还有优化空间呢?JIT 编译器本身也有线程。这部分解释了为什么我们将堆栈大小设置为 256 KB,但上面的数据表明平均堆栈大小仍然是7517 / 11 = 683 KB 。使用-XX:CICompilerCount=1减少编译器线程数,并设置-XX:-TieredCompilation以仅启用最新的编译层,结果如下:

-Total: reserved=1368612KB, committed=32660KB
+Total: reserved=1165843KB, committed=29571KB

--                    Thread (reserved=7517KB, committed=7517KB)
-                            (thread #11)
-                            (stack: reserved=7468KB, committed=7468KB)
-                            (malloc=36KB #57)
-                            (arena=13KB #22)
+-                    Thread (reserved=4419KB, committed=4419KB)
+                            (thread #8)
+                            (stack: reserved=4384KB, committed=4384KB)
+                            (malloc=26KB #42)
+                            (arena=9KB #16)

这是预想的一样,是有效果的,内存的利用得到了进一步的优化,但是这样操作会导致编译器线程越少,预热速度越慢,从而影响应用的启动时间和线程的执行性能

减少 Java 堆大小、选择合适的 GC、减少 VM 线程数、减少 Java 堆栈线程大小和线程数是减少内存受限场景中 VM 占用空间的常用方法。

虚拟机线程栈大小

减少 VM 线程的堆栈大小是危险的,但是也值得尝试,尝试使用-XX:VMThreadStackSize=256,结果如下:

-Total: reserved=1165843KB, committed=29571KB
+Total: reserved=1163539KB, committed=27267KB

--                    Thread (reserved=4419KB, committed=4419KB)
+-                    Thread (reserved=2115KB, committed=2115KB)
                             (thread #8)
-                            (stack: reserved=4384KB, committed=4384KB)
+                            (stack: reserved=2080KB, committed=2080KB)
                             (malloc=26KB #42)
                             (arena=9KB #16)

2m的编译器和 GC 线程堆栈一起消失了,内存的占用得到了进一步的减小!

初始代码缓存大小(即生成代码的区域大小)

可以通过减小初始代码缓存大小(即生成代码的区域大小)可以减少内存占用吗?输入-XX:InitialCodeCacheSize=4096 (字节!),执行结果如下:

-Total: reserved=1163539KB, committed=27267KB
+Total: reserved=1163506KB, committed=25226KB

--                      Code (reserved=49941KB, committed=2557KB)
+-                      Code (reserved=49941KB, committed=549KB)
                             (malloc=21KB #257)
-                            (mmap: reserved=49920KB, committed=2536KB)
+                            (mmap: reserved=49920KB, committed=528KB)

 -                        GC (reserved=67KB, committed=67KB)
                             (malloc=7KB #78)

内存的占用得到了进一步的缩减!

初始元数据存储大小

尝试设置较小的初始元数据存储大小,用 -XX:InitialBootClassLoaderMetaspaceSize=4096 (字节)将其缩减,执行结果如下:

-Total: reserved=1163506KB, committed=25226KB
+Total: reserved=1157404KB, committed=21172KB

--                     Class (reserved=1056890KB, committed=4986KB)
+-                     Class (reserved=1050754KB, committed=898KB)
                             (classes #4)
-                            (malloc=122KB #83)
-                            (mmap: reserved=1056768KB, committed=4864KB)
+                            (malloc=122KB #84)
+                            (mmap: reserved=1050632KB, committed=776KB)

 -                    Thread (reserved=2115KB, committed=2115KB)
                             (thread #8)

内存的占用得到了进一步的缩减!

其它

在应用程序的数据结构设计和算法的优化上仍然有优化的空间。

综合分析

在这里插入图片描述

总结

使用 NMT 发现 VM 在哪里使用内存通常是一项很有启发性的练习。它几乎可以立即让你了解从哪里可以改善特定应用程序的内存占用。将在线 NMT 监视器连接到性能管理系统将有助于在运行实际生产应用程序时调整 JVM 参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【鸿蒙开发】Hi3861学习笔记- 软件定时器示例

00. 目录 文章目录 00. 目录01. 定时器概述02. 定时器API03. 定时器常用API3.1 osTimerNew3.2 osTimerDelete3.3 osTimerStart3.4 osTimerStop 04. 程序示例05. 附录 01. 定时器概述 软件定时器,是基于系统Tick时钟中断且由软件来模拟的定时器,当经过设…

在Html5中仿Matlab自定义色带生成实践

目录 前言 一、RGB的相关知识 1、RGB的基本原理 2、RGB的数值表示 3、应用场景 二、ColorMap生成实战 1、外部库介绍 2、相关API 3、实例生成 三、总结 前言 在现代网页开发与数据可视化领域,色彩的表现力对于信息传达和视觉体验起着至关重要的作用。色带&…

贪心算法--

1.柠檬水找零 link:860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; code class Solution { public:bool lemonadeChange(vector<int>& bills) {// 贪心算法&#xff0c; 优先花出大面额bill&#xff0c; 尽可能保护小面额billint five 0, ten 0;// 不…

如何选择国产串口屏?

目录 1、迪文 2、淘晶驰 3、广州大彩 4、金玺智控 5、欣瑞达 6、富莱新 7、冠显 8、有彩 串口屏&#xff0c;顾名思义&#xff0c;就是通过串口通信接口&#xff08;如RS232、RS485、TTL UART等&#xff09;与主控设备进行通信的显示屏。其核心功能是显示信息和接收输入…

matlab慕课学习3.1

3.1顺序结构程序 于20250306 3.1.1程序和程序设计 程序是用某种计算机能够理解并且能够执行的语言来描述的解决问题的方法和步骤。 3.1.2程序的三种基本结构 1.顺序结构 2.选择结构 3.循环结构 3.1.3脚本文件和函数文件 脚本文件是可在命令行窗口直接执行的文件&#xff0…

cesium地图设置3d,2d,2.5d动态切换

通过修改cesium实例vw的scene的显示模式&#xff0c;来切换最终的显示模式。 Cesium.SceneMode总共有四个变量值&#xff0c;分别如下&#xff1a;NameTypeDescriptionMORPHINGnumber在3d与2d之间切换变体 between mode, e.g., 3D to 2D.COLUMBUS_VIEWnumber2.5d模式&#xff0…

【数据结构】二叉搜索树、平衡搜索树、红黑树

二叉搜索树&#xff08;Binary Search Tree&#xff09; 二叉搜索树是一种特殊的二叉树&#xff0c;它用来快速搜索某个值&#xff0c;对于每个节点都应该满足以下条件&#xff1a; 若该节点有左子树&#xff0c;那么左子树中所有节点的值都应该小于该节点的值。若该节点有右…

密码学(终极版)

加密 & 解密 备注&#xff1a;密码学领域不存在完全不能破解的密码&#xff0c;但是如果一个密码需要很久很久&#xff0c;例如一万年才能破解&#xff0c;就认为这个密码是安全的了。 对称加密 非对称加密 公钥加密、私钥解密 私钥签名、公钥认证 非对称的底层原理是…

经销商管理系统选型解析:8款产品详评

本文主要介绍了以下8款经销商管理系统&#xff1a;1.纷享销客&#xff1b; 2.用友T6经销商管理系统&#xff1b; 3.金蝶经销商管理系统&#xff1b; 4.鼎捷经销商管理系统&#xff1b; 5.浪潮经销商管理系统&#xff1b; 6.销售易&#xff1b; 7.SAP Business One Distributor …

【C++】函数重载与nullptr

1、函数重载 C支持在同一个作用域中出现同名函数&#xff0c;但是要求这些同名函数的形参不同&#xff0c;可以是形参个数不同或者类型不同。这样C函数调用就表现出了多态行为&#xff0c;使用更灵活。C语言是不支持同一作用域中出现同名函数的。 代码&#xff1a; 形参类型不…

处理动态分页:自动翻页与增量数据抓取策略-数据议事厅

一、案例场景 Lily&#xff08;挥舞着数据报表&#xff09;&#xff1a;“用户反馈我们的股票舆情分析总是缺失最新跟帖&#xff01;这些动态分页像狡猾的狐狸&#xff0c;每次抓取都漏掉关键数据&#xff01;” 小王&#xff08;调试着爬虫代码&#xff09;&#xff1a;“传…

用android studio模拟器,模拟安卓手机访问网页,使用Chrome 开发者工具查看控制台信息

web 网页项目在安卓手机打开时出现问题&#xff0c;想要查看控制台调试信息。记录一下使用android studio 模拟器访问的方式。 步骤如下&#xff1a; 1.安装android studio&#xff0c;新增虚拟设备&#xff08;VDM- virtual device manager) 点击Virtual Device Manager后会…

【Linux内核系列】:深入理解缓冲区

&#x1f525; 本文专栏&#xff1a;Linux &#x1f338;作者主页&#xff1a;努力努力再努力wz ★★★ 本文前置知识&#xff1a; 文件系统以及相关系统调用接口 输入以及输出重定向 那么在此前的学习中&#xff0c;我们了解了文件的概念以及相关的系统调用接口&#xff0c;并…

【互联网性能指标】QPS/TPS/PV/UV/IP/GMV/DAU/MAU/RPS

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…

【微知】如何根据内核模块ko查看所依赖其他哪些模块?(modinfo rdma_ucm |grep depends)

背景 有些情况下查看某个模块被哪些模块依赖可以用lsmod看到后面的列表&#xff0c;但是反向查看就要麻烦一些&#xff0c;比如某个模块依赖哪些其他模块&#xff1f;通过modinfo xxx.ko获取里面的depends相关信息 方法 modinfo rdma_ucm |grep depends实操 实操前先看依赖…

Linux安装ComfyUI

Linux安装ComfyUI 1. ComfyUI2. 放置模型文件3. 创建python虚拟环境3.1 删除 Conda 虚拟环境 4. python虚拟环境&#xff0c;安装PyTorch5. 安装依赖6. 运行7. 打开8. 下载模型 移动到路径 1. ComfyUI # cat /etc/issue Ubuntu 20.04.6 LTS \n \lmkdir comfyUI cd comfyUI/git…

订阅指南:用关键指标驱动业务增长

分析订阅业务远非看似简单。仅仅增加订阅数可能并不比维持一批忠实用户更有利可图。深入分析订阅数据及其背后的运作机制&#xff0c;将帮助您优化产品决策、预测收入并促进增长。本文将为您解读关键订阅指标的实际意义&#xff0c;并展示如何通过订阅宝这一专业工具&#xff0…

【开发学习】如何使用deepseek创建记录事件时间的PC应用程序

本文记录了尝试使用deepseek创建应用程序的过程&#xff0c;实现记录事件&时间的PC应用程序&#xff0c;包括创建代码、测试及调整。 目的&#xff1a;创建一个应用&#xff0c;用户输入文本提交&#xff0c;应用记录下时间和文本&#xff0c;数据留存在excel和应用程序中。…

OSPF-单区域的配置

一、单区域概念&#xff1a; 单区域OSPF中&#xff0c;整个网络被视为一个区域&#xff0c;区域ID通常为0&#xff08;骨干区域&#xff09;。所有的路由器都在这个区域内交换链路状态信息。 补充知识点&#xff1a; OSPF为何需要loopback接口&#xff1a; 1.Loopback接口的…

【2025力扣打卡系列】0-1背包 完全背包

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为python3&#xff0c;Day5 0-1背包【目标和】 有n个物品&#xff0c;第i个物品的体积为w[i], 价值为v[i]。每个物品至多选一个&#xff0c;求体积和不超过capacity时的最大价值和常见变形 至多装capacity&#x…