开发者社区> sid10t.> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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

简介: 本博文将结合 JVM 参数、工具等方面,进一步分析 JVM 内存结构,包括外部资料相对较少的堆外部分。 本篇博文的重点是,如何监控和诊断JVM堆内和堆外内存使用?
+关注继续查看

前言

在上一篇博文:【JAVA】JVM 内存区域的划分 中介绍了 JVM 内存区域的划分,总结了相关的一些概念,本博文将结合 JVM 参数、工具等方面,进一步分析 JVM 内存结构,包括外部资料相对较少的堆外部分。

本篇博文的重点是,如何监控和诊断JVM堆内和堆外内存使用?

概述

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

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

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

  • 也可以使用命令行工具进行运行时查询,如 jstat 和 jmap 等工具都提供了一些选项,可以查看堆、方法区等使用数据。
  • 或者,也可以使用 jmap 等提供的命令,生成堆转储(Heap Dump)文件,然后利用 jhat 或 Eclipse MAT 等堆转储分析工具进行详细分析。
  • 如果你使用的是 Tomcat、Weblogic 等 Java EE 服务器,这些服务器同样提供了内存管理相关的功能。
  • 另外,从某种程度上来说,GC 日志等输出,同样包含着丰富的信息。

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

正文

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

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

image.png

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

  1. 新生代

新生代是大部分对象创建和销毁的区域,在通常的 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 里分配一块儿。

image.png

  1. 老年代

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

  1. 永久代

这部分就是早期 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 活动内存池的截图。

image.png

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

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

-XX:NativeMemoryTracking=summary

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

-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

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

image.png

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

  • 第一部分非常明显是 Java 堆,我已经分析过使用什么参数调整,不再赘述。
  • 第二部分是 Class 内存占用,它所统计的就是 Java 类元数据所占用的空间,JVM 可以通过类似下面的参数调整其大小:
-XX:MaxMetaspaceSize=value

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

-XX:InitialBootClassLoaderMetaspaceSize=30720

接下来是 Code 统计信息,显然这是 CodeCache 相关内存,也就是 JIT compiler 存储编译热点方法等信息的地方,JVM 提供了一系列参数可以限制其初始值和最大值等,例如:

-XX:InitialCodeCacheSize=value
-XX:ReservedCodeCacheSize=value

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

后记

以上就是 如何监控和诊断JVM堆内和堆外内存使用? 的所有内容了;

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

📝 上篇精讲:【JAVA】Java 内存模型中的 happen-before
💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注;
👍 创作不易,请多多支持;
🔥 系列专栏: 面试精讲 JAVA

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
java日志(系统运行监控)
.什么是日志? 记录系统运行的过程和详情,并可以进行永久存储
0 0
zabbix配置jmx监控java应用
zabbix配置jmx监控java应用
0 0
使用JDK自带的jmap和jhat监控处于运行状态的Java进程
对于处于运行状态中的Java进程,JDK自带了很多工具,允许Java开发人员监控运行进程中的各种状态,比如该进程内部创建了多少个对象实例,消耗了多少内存,等等。
0 0
使用jvisualvm配合Visual GC插件监控Java程序详细总结
本节将会介绍一下jvisualvm的特性及作用、各个功能是如何使用的、最后会介绍jvisualvm的插件Visual GC的安装及使用
0 0
使用 JMX 监控和管理 Java 程序(二)
使用 JMX 监控和管理 Java 程序(二)
0 0
Java 中的监控与管理原理概述
Java 中的监控与管理原理概述
0 0
使用JMX Exporter监控Rainbond上的Java应用
可通过本文了解部署在 Rainbond 上的 Java 应用如何使用 JMX Exporter 暴露 JVM 监控指标。
0 0
Sigar java 服务器信息探针、监控
Sigar java 服务器信息探针、监控
0 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Jpom一款低侵入式Java运维、监控软件
立即下载
Java工程师必读手册
立即下载
Java应用提速(速度与激情)
立即下载