当前位置: 首页 > article >正文

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

问题

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 参数。

相关文章:

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

问题 Java应用启动或者运行过程中报“内存不足!”,我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言,需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构,来保证JVM应用的成功启动以及未来平…...

【鸿蒙开发】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;// 不…...

【学习方法一】

学习方法一 一、通用高效学习法二、学科专项方法三、工具与技术辅助四、习惯与心理策略五、避免常见误区总结六、进阶学习策略七、解决学习痛点八、场景化学习法九、资源与工具推荐十、个性化学习调整十一、长期学习心态十二、常见问题QA十三、应对特殊挑战的学习法十四、健康与…...

k8s启动时calico-kube-controllers与coredns组件一直是pending状态

症状&#xff1a; k8s执行kubectl get po -n kube-system时&#xff0c;以下组件一直>是pending状态&#xff1a; calico-kube-controllerscoredns 当执行 kubectl get po -n kube-system 发现 calico-kube-controllers 和 coredns 一直处于 Pending 状态时&#xff0c;通常…...

ngx_openssl_create_conf

ngx_openssl_create_conf 声明在 src\event\ngx_event_openssl.c static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); 定义在 src\event\ngx_event_openssl.c static void * ngx_openssl_create_conf(ngx_cycle_t *cycle) {ngx_openssl_conf_t *oscf;oscf ngx_…...

如何选择国产串口屏?

目录 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…...

EB-Cable许可管理系统的功能和特点

在数字化时代&#xff0c;软件许可管理已成为企业日常运营中不可或缺的一部分。EB-Cable许可管理系统作为一款专为电缆管理而设计的软件解决方案&#xff0c;为企业提供了全面、高效且灵活的许可管理功能。本文将详细介绍EB-Cable许可管理系统的功能和特点&#xff0c;帮助您快…...

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…...

Mac如何查看 IDEA 的日志文件

在 macOS 上&#xff0c;IntelliJ IDEA 的日志文件通常存储在用户目录下的 .IntelliJIdea<版本号> 文件夹中。以下是查看日志文件的具体步骤&#xff1a; 1. 找到日志文件的位置 日志文件通常位于以下路径&#xff1a; ~/Library/Logs/IntelliJIdea<版本号> 其…...

linux 软件安装(下)

七、ElasticSearch安装 官网地址&#xff1a;Elasticsearch&#xff1a;官方分布式搜索和分析引擎 | Elastic ​ 官网下载地址&#xff1a;Past Releases of Elastic Stack Software | Elastic 7.1、linux安装 1、上传安装包 altp # 打开sftp窗口 # 上传es安装包 put e:/sof…...

MongoDB 自动化部署

部署在容器中&#xff0c;并且自动创建所需用户和权限等 # 启动 mongoDBsudo docker run -dit --name china_fish_mongo \ -p 27017:27017 \ -v /data/project1/db/mongo/config/mongod.conf:/etc/mongod.conf \ -v /data/project1/db/mongo/data:/data/db \ -v /data/project1…...

程序化广告知识入门与Python基础数据处理实践

程序化广告知识入门与Python基础数据处理实践 大家好&#xff01;我写这一系列博客的初衷是想和大家一起学习进步。在技术飞速发展的今天&#xff0c;数据处理能力愈发重要&#xff0c;Python作为强大的数据处理工具&#xff0c;掌握它能为我们的职业发展和技术提升带来极大帮…...

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

二叉搜索树&#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 …...

Go 语言封装 HTTP 请求的 Curl 工具包

文章目录 Go 语言封装 HTTP 请求的 Curl 工具包&#x1f3d7;️ 工具包结构简介核心结构体定义初始化函数 &#x1f31f; 功能实现1. 设置请求头2. 构建请求3. 发送请求4. 发送 GET 请求5. 发送 POST 请求6. 发送 PUT 请求7. 发送 DELETE 请求8. 读取响应体 &#x1f4a1; 实现…...

【C++】函数重载与nullptr

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

全球首款 5G-A 人形机器人发布

全球首款 5G-A 人形机器人于世界移动通信大会&#xff08;MWC2025&#xff09;上由中国移动、华为、乐聚联合发布。以下是关于这款机器人的详细介绍&#xff1a; 名称与背景 名称9&#xff1a;这款人形机器人名为 “夸父”&#xff0c;是中国移动、华为与乐聚机器人在 GTI 平台…...

第5节:交换技术与VLAN技术

交换机工作原理与VLAN技术实战 在现代网络中,交换机是构建局域网(LAN)的核心设备,而VLAN(虚拟局域网)技术则是优化网络管理和性能的关键工具。本文将以华为三层交换机为例,深入探讨交换机的工作原理、VLAN的配置与Trunk技术,以及STP(生成树协议)的应用,帮助读者全面…...

程序化广告行业(2/89):从程序化广告深挖数据处理技巧

程序化广告行业&#xff08;2/89&#xff09;&#xff1a;从程序化广告深挖数据处理技巧 大家好&#xff01;我一直希望能和大家在技术学习的道路上携手共进&#xff0c;这也是我写这一系列博客的初衷。上次我们一起学习了Python基础的数据处理知识&#xff0c;这次咱们借助一…...

【10】单片机时间和速度的起源:指令周期与晶振频率

【10】单片机时间和速度的起源&#xff1a;指令周期与晶振频率 &#x1f31f; 核心概念 单片机的运算速度与时间控制&#xff0c;本质上由 指令周期 和 晶振频率 共同决定。理解这两者的关系&#xff0c;是掌握单片机底层控制的关键。 &#x1f4cc; 1. 节拍与指令周期 &…...

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

一、案例场景 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后会…...

动态规划中固定倒数第二个数与倒数第一个数的区别与应用场景分析 —— 从最长等差数列问题到统计等差数列个数的填表策略对比

目录 1. 问题目标的区别 &#xff08;1&#xff09;找到最长的等差数列 &#xff08;2&#xff09;统计等差数列的个数 2. 填表顺序的区别 &#xff08;1&#xff09;固定倒数第二个数&#xff08;i&#xff09; &#xff08;2&#xff09;固定倒数第一个数&#xff08;j&…...

【C语言】数组篇

目录 引言一维数组数组的定义数组的初始化完全初始化部分初始化省略数组长度 数组元素的访问 多维数组二维数组的定义二维数组的初始化完全初始化部分初始化省略第一维长度 二维数组元素的访问 遍历数组元素遍历一维数组遍历二维数组 数组作为函数参数一维数组作为函数参数二维…...

【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博客专家、…...