iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >如何监控和诊断JVM堆内和堆外内存使用?
  • 536
分享到

如何监控和诊断JVM堆内和堆外内存使用?

jvmjava开发语言 2023-09-24 17:09:07 536人浏览 安东尼
摘要

典型回答 了解 JVM 内存的方法有很多,具体能力范围也有区别,简单总结如下: 可以使用综合性的图形化工具,如 JConsole、VisualVM(注意,从 oracle jdk 9 开始,VisualVM 已经不再包含在 JDK 安装包中

典型回答

了解 JVM 内存的方法有很多,具体能力范围也有区别,简单总结如下:

  • 可以使用综合性的图形化工具,如 JConsole、VisualVM(注意,从 oracle jdk 9 开始,VisualVM 已经不再包含在 JDK 安装包中)等。这些工具具体使用起来相对比较直观,直接连接到 Java 进程,然后就可以在图形化界面里掌握内存使用情况。

以 JConsole 为例,其内存页面可以显示常见的堆内存各种堆外部分使用状态。

  • 也可以使用命令行工具进行运行时查询,如 jstat 和 jmap 等工具都提供了一些选项,可以查看堆、方法区等使用数据。

  • 或者,也可以使用 jmap 等提供的命令,生成堆转储(Heap Dump)文件,然后利用 jhat 或 Eclipse MAT 等堆转储分析工具进行详细分析。

  • 如果你使用的是 TomcatWEBlogic 等 Java EE 服务器,这些服务器同样提供了内存管理相关的功能。

  • 另外,从某种程度上来说,GC 日志等输出,同样包含着丰富的信息。

这里有一个相对特殊的部分,就是是堆外内存中的直接内存,前面的工具基本不适用,可以使用 JDK 自带的 Native Memory Tracking(NMT)特性,它会从 JVM 本地内存分配的角度进行解读。

考点分析

今天选取的问题是 Java 内存管理相关的基础实践,对于普通的内存问题,掌握上面我给出的典型工具和方法就足够了。这个问题也可以理解为考察两个基本方面能力,第一,你是否真的理解了 JVM 的内部结构;第二,具体到特定内存区域,应该使用什么工具或者特性去定位,可以用什么参数调整。

对于 JConsole 等工具的使用细节,我在专栏里不再赘述,如果你还没有接触过,你可以参考JConsole 官方教程。我这里特别推荐Java Mission Control(JMC),这是一个非常强大的工具,不仅仅能够使用JMX进行普通的管理、监控任务,还可以配合Java Flight Recorder(JFR)技术,以非常低的开销,收集和分析 JVM 底层的 Profiling 和事件等信息。目前, Oracle 已经将其开源,如果你有兴趣请可以查看 OpenJDK 的Mission Control项目

关于内存监控与诊断,我会在知识扩展部分结合 JVM 参数和特性,尽量从庞杂的概念和 JVM 参数选项中,梳理出相对清晰的框架

  • 细化对各部分内存区域的理解,堆内结构是怎样的?如何通过参数调整?

  • 堆外内存到底包括哪些部分?具体大小受哪些因素影响?

知识扩展

今天的分析,我会结合相关 JVM 参数和工具,进行对比以加深你对内存区域更细粒度的理解。

首先,堆内部是什么结构?

对于堆内存,我在上一讲介绍了最常见的新生代和老年代的划分,其内部结构随着 JVM 的发展和新 GC 方式的引入,可以有不同角度的理解,下图就是年代视角的堆结构示意图。


你可以看到,按照通常的 GC 年代方式划分,Java 堆内分为:

新生代

新生代是大部分对象创建和销毁的区域,在通常的 Java 应用中,绝大部分对象生命周期都是很短暂的。其内部又分为 Eden 区域,作为对象初始分配的区域;两个 Survivor,有时候也叫 from、to 区域,被用来放置从 Minor GC 中保留下来的对象。

  • JVM 会随意选取一个 Survivor 区域作为“to”,然后会在 GC 过程中进行区域间拷贝,也就是将 Eden 中存活下来的对象和 from 区域的对象,拷贝到这个“to”区域。这种设计主要是为了防止内存的碎片化,并进一步清理无用对象。

  • 从内存模型而不是垃圾收集的角度,对 Eden 区域继续进行划分,Hotspot JVM 还有一个概念叫做 Thread Local Allocation Buffer(TLAB),据我所知所有 OpenJDK 衍生出来的 JVM 都提供了 TLAB 的设计。这是 JVM 为每个线程分配的一个私有缓存区域,否则,多线程同时分配内存时,为避免操作同一地址,可能需要使用加等机制,进而影响分配速度,你可以参考下面的示意图。从图中可以看出,TLAB 仍然在堆上,它是分配在 Eden 区域内的。其内部结构比较直观易懂,start、end 就是起始地址,top(指针)则表示已经分配到哪里了。所以我们分配新对象,JVM 就会移动 top,当 top 和 end 相遇时,即表示该缓存已满,JVM 会试图再从 Eden 里分配一块儿。


    2. 老年代

放置长生命周期的对象,通常都是从 Survivor 区域拷贝过来的对象。当然,也有特殊情况,我们知道普通的对象会被分配在 TLAB 上;如果对象较大,JVM 会试图直接分配在 Eden 其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM 就会直接分配到老年代。

永久代

这部分就是早期 Hotspot JVM 的方法区实现方式了,储存 Java 类元数据、常量池、Intern 字符串缓存,在 JDK 8 之后就不存在永久代这块儿了。

那么,我们如何利用 JVM 参数,直接影响堆和内部区域的大小呢?我来简单总结一下:

  • 最大堆体积
-Xmx value
  • 初始的最小堆体积
-Xms value
  • 老年代和新生代的比例
-XX:NewRatio=value

默认情况下,这个数值是 2,意味着老年代是新生代的 2 倍大;换句话说,新生代是堆大小的 1/3。

  • 当然,也可以不用比例的方式调整新生代的大小,直接指定下面的参数,设定具体的内存大小数值。
-XX:NewSize=value
  • Eden 和 Survivor 的大小是按照比例设置的,如果 SurvivorRatio 是 8,那么 Survivor 区域就是 Eden 的 1/8 大小,也就是新生代的 1/10,因为 YoungGen=Eden + 2*Survivor,JVM 参数格式是
-XX:SurvivorRatio=value
  • TLAB 当然也可以调整,JVM 实现了复杂的适应策略,如果你有兴趣可以参考这篇说明

不知道你有没有注意到,我在年代视角的堆结构示意图也就是第一张图中,还标记出了 Virtual 区域,这是块儿什么区域呢?

在 JVM 内部,如果 Xms 小于 Xmx,堆的大小并不会直接扩展到其上限,也就是说保留的空间(reserved)大于实际能够使用的空间(committed)。当内存需求不断增长的时候,JVM 会逐渐扩展新生代等区域的大小,所以 Virtual 区域代表的就是暂时不可用(uncommitted)的空间。

第二,分析完堆内空间,我们一起来看看 JVM 堆外内存到底包括什么?

在 JMC 或 JConsole 的内存管理界面,会统计部分非堆内存,但提供的信息相对有限,下图就是 JMC 活动内存池的截图。


接下来我会依赖 NMT 特性对 JVM 进行分析,它所提供的详细分类信息,非常有助于理解 JVM 内部实现。

首先来做些准备工作,开启 NMT 并选择 summary 模式,

-XX:NativeMemoryTracking=summary

为了方便获取和对比 NMT 输出,选择在应用退出时打印 NMT 统计信息

-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

然后,执行一个简单的在标准输出打印 HelloWorld 的程序,就可以得到下面的输出


我来仔细分析一下,NMT 所表征的 JVM 本地内存使用:

  • 第一部分非常明显是 Java 堆,我已经分析过使用什么参数调整,不再赘述。

  • 第二部分是 Class 内存占用,它所统计的就是 Java 类元数据所占用的空间,JVM 可以通过类似下面的参数调整其大小:

-XX:MaxMetaspaceSize=value

对于本例,因为 HelloWorld 没有什么用户类库,所以其内存占用主要是启动类加载器(Bootstrap)加载的核心类库。你可以使用下面的小技巧,调整启动类加载器元数据区,这主要是为了对比以加深理解,也许只有在 hack JDK 时才有实际意义。

-XX:InitialBootClassLoaderMetaspaceSize=30720
  • 下面是 Thread,这里既包括 Java 线程,如程序主线程、Cleaner 线程等,也包括 GC 等本地线程。你有没有注意到,即使是一个 HelloWorld 程序,这个线程数量竟然还有 25。似乎有很多浪费,设想我们要用 Java 作为 serverless 运行时,每个 function 是非常短暂的,如何降低线程数量呢?
    如果你充分理解了专栏讲解的内容,对 JVM 内部有了充分理解,思路就很清晰了:
    JDK 9 的默认 GC 是 G1,虽然它在较大堆场景表现良好,但本身就会比传统的 Parallel GC 或者 Serial GC 之类复杂太多,所以要么降低其并行线程数目,要么直接切换 GC 类型;
    JIT 编译默认是开启了 TieredCompilation 的,将其关闭,那么 JIT 也会变得简单,相应本地线程也会减少。
    我们来对比一下,这是默认参数情况的输出:


    下面是替换了默认 GC,并关闭 TieredCompilation 的命令行

    得到的统计信息如下,线程数目从 25 降到了 17,消耗的内存也下降了大概 1/3。
  • 接下来是 Code 统计信息,显然这是 CodeCache 相关内存,也就是 JIT compiler 存储编译热点方法等信息的地方,JVM 提供了一系列参数可以限制其初始值和最大值等,例如:
-XX:InitialCodeCacheSize=value
-XX:ReservedCodeCacheSize=value

你可以设置下列 JVM 参数,也可以只设置其中一个,进一步判断不同参数对 CodeCache 大小的影响。



很明显,CodeCache 空间下降非常大,这是因为我们关闭了复杂的 TieredCompilation,而且还限制了其初始大小。

  • 下面就是 GC 部分了,就像我前面介绍的,G1 等垃圾收集器其本身的设施和数据结构就非常复杂和庞大,例如 Remembered Set 通常都会占用 20%~30% 的堆空间。如果我把 GC 明确修改为相对简单的 Serial GC,会有什么效果呢?

使用命令:

-XX:+UseSerialGC


可见,不仅总线程数大大降低(25 → 13),而且 GC 设施本身的内存开销就少了非常多。据我所知,AWS Lambda 中 Java 运行时就是使用的 Serial GC,可以大大降低单个 function 的启动和运行开销。

  • Compiler 部分,就是 JIT 的开销,显然关闭 TieredCompilation 会降低内存使用。

  • 其他一些部分占比都非常低,通常也不会出现内存使用问题,请参考官方文档。唯一的例外就是 Internal(JDK 11 以后在 Other 部分)部分,其统计信息包含着 Direct Buffer 的直接内存,这其实是堆外内存中比较敏感的部分,很多堆外内存 OOM 就发生在这里,请参考专栏第 12 讲的处理步骤。原则上 Direct Buffer 是不推荐频繁创建或销毁的,如果你怀疑直接内存区域有问题,通常可以通过类似 instrument 构造函数等手段,排查可能的问题。

JVM 内部结构就介绍到这里,主要目的是为了加深理解,很多方面只有在定制或调优 JVM 运行时才能真正涉及,随着微服务和 Serverless 等技术的兴起,JDK 确实存在着为新特征的工作负载进行定制的需求。

今天我结合 JVM 参数和特性,系统地分析了 JVM 堆内和堆外内存结构,相信你一定对 JVM 内存结构有了比较深入的了解,在定制 Java 运行时或者处理 OOM 等问题的时候,思路也会更加清晰。JVM 问题千奇百怪,如果你能快速将问题缩小,大致就能清楚问题可能出在哪里,例如如果定位到问题可能是堆内存泄漏,往往就已经有非常清晰的思路和工具可以去解决了。

来源地址:https://blog.csdn.net/qq_37050329/article/details/130405375

--结束END--

本文标题: 如何监控和诊断JVM堆内和堆外内存使用?

本文链接: https://www.lsjlt.com/news/417006.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何监控和诊断JVM堆内和堆外内存使用?
    典型回答 了解 JVM 内存的方法有很多,具体能力范围也有区别,简单总结如下: 可以使用综合性的图形化工具,如 JConsole、VisualVM(注意,从 Oracle JDK 9 开始,VisualVM 已经不再包含在 JDK 安装包中...
    99+
    2023-09-24
    jvm java 开发语言
  • jvm如何设置堆内存参数
    JVM的堆内存参数可以通过设置JAVA_OPTS环境变量来进行配置。具体设置堆内存参数的步骤如下: 打开命令行窗口,并进入JVM的...
    99+
    2024-02-29
    jvm
  • jvm堆内存溢出如何解决
    JVM堆内存溢出(OutOfMemoryError)的解决方法通常有以下几种:1. 增加堆内存:通过修改JVM启动参数来增加堆内存的...
    99+
    2023-09-27
    jvm
  • 如何设置jvm堆内存大小
    要设置JVM堆内存大小,可以使用以下方法之一: 通过命令行参数设置:可以通过在启动Java应用程序时使用-Xms和-Xmx参数来设...
    99+
    2024-02-29
    jvm
  • JVM分配和回收堆外内存的方式与注意点
    目录JVM内存模型如何分配堆外内存第一种方式:ByteBuffer#allocateDirect第二种方式:Unsafe#allocateMemory如何回收堆外内存第一种方式:Un...
    99+
    2024-04-02
  • JVM中怎么使用jstat查看堆内存
    在JVM中使用jstat命令可以查看堆内存的使用情况。具体步骤如下: 打开命令行工具,进入到JVM的bin目录下,通常位于JDK...
    99+
    2024-03-05
    JVM
  • win11内存诊断如何使用
    这篇文章主要介绍“win11内存诊断如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“win11内存诊断如何使用”文章能帮助大家解决问题。win11内存诊断使用的方法:第一步,点击底部“开始菜单...
    99+
    2023-06-30
  • java堆外内存泄漏如何排查
    在Java中,堆外内存泄漏通常是由于未正确释放使用了堆外内存的资源所导致的。下面是一些排查堆外内存泄漏的常见方法:1. 使用JVM工...
    99+
    2023-08-24
    java
  • 如何实现JVM内存监控工具用法
    本篇文章为大家展示了如何实现JVM内存监控工具用法,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。你对JVM内存监控工具的使用是否熟悉,这里向大家简单介绍一下,VisualVM囊括的命令行工具包括jp...
    99+
    2023-06-17
  • 教你如何通过JConsoler监控Tomcat的JVM内存
    目录1.监控Tomcat的方式2.Java自带的监控命令 3.Tomcat故障案例4.配置Tomcat JMX监控5.使用Jsconsole连接JMX查看监控数据通过JConsole...
    99+
    2024-04-02
  • 服务器内存故障排除:如何诊断和修复服务器内存问题
    1. 症状识别: 服务器内存故障通常表现为以下症状: 系统崩溃或死机 蓝屏或其他错误信息 程序运行缓慢或崩溃 数据丢失或损坏 内存使用率异常 2. 诊断步骤: 2.1 观察内存使用情况: 使用命令“top”或“free”查看内存使用...
    99+
    2024-02-12
    服务器内存 故障排除 诊断 修复 内存错误
  • Android adb shell 查看App内存(java堆内存/vss虚拟内存/详细的内存状况/内存快照hprof)和系统可用内存
    1.adb shell 获取app 进程的pid adb shell "ps|grep com.xxx包名" 根据某个渠道包,去查询对应的pid,如下所示: 2.通过adb shell 查看设备的j...
    99+
    2023-09-03
    android adb java
  • Redis的内存使用情况如何监控
    使用Redis自带的INFO命令来查看Redis实例的内存使用情况。可以通过命令INFO memory来查看内存相关的信息,如当...
    99+
    2024-04-02
  • 一文教会你使用jmap和MAT进行堆内存溢出分析
    jmap:Java内存映像工具 jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为heapdump或dump文件)。另外,还有几种方式获取dump文件...
    99+
    2024-04-02
  • 阿里云服务器内存使用率如何监控和优化
    阿里云服务器内存使用率是一个重要的性能指标,它直接关系到服务器的运行效率和稳定性。本文将详细说明如何监控阿里云服务器内存使用率,以及如何通过优化来提高服务器的性能。 一、阿里云服务器内存使用率的监控阿里云服务器提供了多种方式来监控内存使用率...
    99+
    2023-11-15
    阿里 使用率 内存
  • Node学习之如何最小化堆分配和防止内存泄漏
    以上就是Node学习之如何最小化堆分配和防止内存泄漏的详细内容,更多请关注编程网其它相关文章!...
    99+
    2023-05-14
    后端 Node.js JavaScript
  • 如何在Redis中监控内存使用情况
    可以通过Redis自带的INFO命令来查看Redis实例的内存使用情况。具体步骤如下: 连接到Redis服务器,可以使用redi...
    99+
    2024-04-09
    Redis
  • 如何用shell脚本监控系统负载、CPU和内存使用情况
    这篇文章主要讲解了“如何用shell脚本监控系统负载、CPU和内存使用情况”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何用shell脚本监控系统负载、CPU和内存使用情况”吧!一、安装l...
    99+
    2023-06-09
  • 如何在MySQL中进行数据库性能监控和诊断
    在MySQL中进行数据库性能监控和诊断可以通过以下几种方式: 使用MySQL自带的性能监控工具:MySQL自带了一些性能监控工具...
    99+
    2024-03-06
    MySQL
  • 监控 Python 内存使用情况和代码执行时间
    我的代码的哪些部分运行时间最长、内存最多?我怎样才能找到需要改进的地方?在开发过程中,我很确定我们大多数人都会想知道这一点,在本文中总结了一些方法来监控 Python 代码的时间和内存使用情况。本文将介绍4种方法,前3种方法提供时间信息,第...
    99+
    2023-05-14
    开发 Python 内存
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作