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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

p5.js:sound(音乐)可视化,动画显示音频高低变化

本文通过4个案例介绍了使用 p5.js 进行音乐可视化的实践,包括将音频振幅转化为图形、生成波形图。 承上一篇:vite:初学 p5.js demo 画圆圈 cd p5-demo copy .\node_modules\p5\lib\p5.min.js . copy .\node_modules\p5\lib\addons\p5.soun…

PDF处理控件Aspose.PDF,如何实现企业级PDF处理

PDF处理为何成为开发者的“隐形雷区”? “手动调整200页PDF目录耗时3天,扫描件文字识别错误导致数据混乱,跨平台渲染格式崩坏引发客户投诉……” 作为开发者,你是否也在为PDF处理的复杂细节消耗大量精力?Aspose.PDF凭…

ruo-yi项目启动备忘

ruo-yi项目启动遇到问题备忘 参考文档: 若依 手把手启动 https://blog.csdn.net/qq_43804008/article/details/132950644?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-132950644-blog-137337537.235^v43^pc_blog_bottom_…

⭐LeetCode周赛 Q1. 找出最大的几近缺失整数——模拟⭐

⭐LeetCode周赛 Q1. 找出最大的几近缺失整数——模拟⭐ 示例 1: 输入:nums [3,9,2,1,7], k 3 输出:7 解释: 1 出现在两个大小为 3 的子数组中:[9, 2, 1]、[2, 1, 7] 2 出现在三个大小为 3 的子数组中:[3,…

Java 集合框架大师课:性能调优火葬场(四)

🚀 Java 集合框架大师课:性能调优火葬场(四) 🔥 战力值突破 95% 警告!调优就像吃重庆火锅——要选对食材(数据结构)还要控制火候(算法)🌶️ 第一章…

【愚公系列】《Python网络爬虫从入门到精通》045-Charles的SSL证书的安装

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主&…

蓝桥杯嵌入式组第七届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之,藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 ADC模块1.3.3 IIC模块1.3.4 UART模块1.3.5 LCD模块1.3.6 LED模块1.3.7 TIM模块 2.源码3.第七届题目 前言:STM32G431RBT6实现嵌入式组第七届题目解析源码&…

KUKA机器人:智能制造的先锋力量

在科技日新月异的今天,自动化和智能化已成为推动制造业转型升级的重要引擎。作为全球领先的智能、资源节约型自动化解决方案供应商,KUKA机器人在这一浪潮中扮演着举足轻重的角色。本文将带您深入了解KUKA机器人的发展现状,探索其在智能制造领…

Ateme在云端构建可扩展视频流播平台

Akamai Connected Cloud帮助Ateme客户向全球观众分发最高质量视频内容。 “付费电视运营商和内容提供商现在可以在Akamai Connected Cloud上通过高质量视频吸引观众,并轻松扩展。”── Ateme首席战略官Rmi Beaudouin ​ Ateme是全球领先的视频压缩和传输解决方案提…

OceanBase社区年度之星专访:张稚京与OB社区的双向奔赴

2024年年底,OceanBase社区颁发了“年度之星”奖项,旨在表彰过去一年中为 OceanBase 社区发展作出卓越贡献的个人。今天,我们有幸邀请到这一荣誉的获得者——来自融科智联的张稚京老师,并对他进行了专访。 在过去的一年中&#xf…

如何选择国产串口屏?

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

涨薪技术|Kubernetes(k8s)之Service服务

01Service简介 Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。通过 ReplicationController 能够动态地创建和销毁 Pod(例如,需要进行扩缩容,或者执行 滚动升…

Quickwit+Jaeger+Prometheus+Grafana搭建Java日志管理平台

介绍 生产服务应用可观测性在当下比较流行的方案,其中出现了大量高性能、开箱即用、易上手的的开源产品,大大丰富了在可观测性领域产品的多样性,本文讲述基于OTLP协议推送Java项目遥测数据(日志、指标、链路)到后端存储…

「mysql」Mac mysql一路畅通式安装

折腾了一上午,遇到的各种错误: 错误一:安装后,终端执行 mysql 或者执行 mysql -u root -p 时报错: ERROR 1045 (28000): Access denied for user rootlocalhost (using password: YES)错误二:为解决错误一&…

Linux原生异步IO原理与实现(Native AIO)

异步 IO:当应用程序发起一个 IO 操作后,调用者不能立刻得到结果,而是在内核完成 IO 操作后,通过信号或回调来通知调用者。 异步 IO 与同步 IO 的区别如图所示: 从上图可知,同步 IO 必须等待内核把 IO 操作处…

AI编程方法第三弹:让它改错

很多情况下,我们自己还是可以完成代码的,不过会遇到很多错误。在发生错误时,可以充分利用AI编程工具帮助我们调试错误,加快处理速度。当然,对于初学者并不建议,还是等自己掌握了基础知识,再去考…

【论文解读】MODEST 透明物体 单目深度估计和分割 ICRA 2025

MODEST是一种用于透明物体的单目深度估计和分割的方法,来自ICRA 2025。 它通过单张RGB图像作为输入,能够同时预测透明物体的深度图和分割掩码。 由深度图生成点云数据,然后采用GraspNet生成抓取位姿,开展透明物体抓取实验。 论文…

基于SpringBoot的美食信息推荐系统设计与实现(源码+SQL脚本+LW+部署讲解等)

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…

Unity--Cubism Live2D模型使用

了解LIVE2D在unity的使用--前提记录 了解各个组件的作用 Live2D Manuals & Tutorials 这些文件都是重要的控制动画参数的 Cubism Editor是编辑Live2D的工具,而导出的数据的类型,需要满足以上的条件 SDK中包含的Cubism的Importer会自动生成一个Pref…

2025人工智能AI新突破:PINN内嵌物理神经网络火了

最近在淘金的时候发现基于物理信息的神经网络(简称PINN)也是个研究热点,遂研读了几篇经典论文,深觉这也是个好发论文的方向,所以火速整理了一些个人认为很值得一读的PINN论文和同学们分享。 为了方面同学们更好地理解…