JAVA内存深度分析报告

本文涉及的产品
文本翻译,文本翻译 100万字符
图片翻译,图片翻译 100张
语种识别,语种识别 100万字符
简介: JAVA内存深度分析报告


最近在对云主机的内存占用优化中,又有了新的认识,网上对JAVA的native memory 的认知五花八门,对direct memory 的获取的有偏差,今天我们来好好理一理,以便我们对JAVA 内存有个更好的认知。

理论部分:

首先,JAVA应用的内存到底包含了哪些部分?下图可以完整的表达,

从上图我们可以认识到,JAVA 应用包含Heap Memory,Non-heap Memory,以及Direct Memory.

1.Heap Memory(堆内存)

这块主要是有 Eden Space(伊甸园), Survivor Space (幸存者区), Old Gen (老年代)组成。

这块数据是可以监控到的,这个后面再提。

2.Non-heap Memory(堆外内存)

包含了Metaspace(元数据区或者叫元空间),Code cache(代码缓存,JIT产生的汇编指令所占的空间), Compressed Class Space 是 Metaspace 的一部分,默认大小为 1G,这三块属于可观察部分,这个也有相对应的API可以获取到。

我们看下metaSpace的JDK默认值,

[root@VM-16-13-centos ~]# java -XX:+PrintFlagsFinal -version | grep MetaspaceSize
   size_t InitialBootClassLoaderMetaspaceSize      = 4194304                                   {product} {default}
   size_t MaxMetaspaceSize                         = 18446744073709547520                      {product} {default}
   size_t MetaspaceSize                            = 21807104                               {pd product} {default}

其中MaxMetaspace可以认为是无上限的值。

代码缓存如果 -XX:ReservedCodeCacheSize >= 240m,则代码分成三个区域:

其中 Code cache包含以下三个区域:

  • CodeHeap ‘non-nmethods’ JVM 自己用的代码
  • CodeHeap ‘non-profiled nmethods’, 完全优化的机器码
  • CodeHeap ‘profiled nmethods’ 部分优化的机器码
    如果 -XX:ReservedCodeCacheSize < 240m,则它们存在一起 不进行区分
    另一部分,Thread Stack(线程堆栈),GC(执行GC逻辑所需的空间),internal,Symbol(有关符号的分配,如同String table和常量池),Other (Direct Memory),这部分暂时还没找到用API获取的方式,但也是能通过其他方式获取到。

3.Direct Memory(直接内存)

直接内存是和java.nio.DirectByteBuffer类相关联的,常被用在第三方库,比如nio和gzip。

这里有个疑问,经过观察,上面堆外内存的Other 和Direct Memory 是一样的,那么,上面那张图,那为何要独立分出这一部分来?

实验部分:

接下去我们通过实验来相互印证一些数据和想法。

准备环境:

JDK:

openjdk version "11.0.16.1" 2022-08-16
OpenJDK Runtime Environment TencentKonaJDK (build 11.0.16.1+2)
OpenJDK 64-Bit Server VM TencentKonaJDK (build 11.0.16.1+2, mixed mode)

启动参数:

java -server  -Xms256M -Xmx256M -XX:+UseG1GC -XX:MaxMetaspaceSize=1024m -XX:NativeMemoryTracking=detail  -jar game.jar

在这里,我们分配256MB的堆大小,同时,使用G1作为我们的GC算法,并且启动了本地内存追踪XX:NativeMemoryTracking=detail ,

1.Platform MXBeans API 监控快照

max heap memory[G1 Eden Space,G1 Survivor Space,G1 Old Gen] 256 MB,used 169 MB [91, 3, 75],
max non-heap[CodeHeap 'non-nmethods',CodeHeap 'non-profiled nmethods',CodeHeap 'profiled nmethods',Compressed Class Space,Metaspace]  memory  2280 MB,used 165 MB [1, 7, 26, 14, 116], committed 172 MB,
max direct memory 256 MB,used 4 MB

通过每分钟的内存监控打印,我们可以看到,

堆的使用空间为G1 Eden Space,G1 Survivor Space,G1 Old Gen 相加,总量的调用API为:

ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();

非堆空间(可监控的) 为CodeHeap ‘non-nmethods’,CodeHeap ‘non-profiled nmethods’,CodeHeap ‘profiled nmethods’,Compressed Class Space,Metaspace相加,总量的调用API 为:ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();

2.MetaSpace 快照:

jcmd 18228 GC.heap_info
18228:
 garbage-first heap   total 262144K, used 205311K [0x00000000f0000000, 0x0000000100000000)
  region size 1024K, 126 young (129024K), 3 survivors (3072K)
 Metaspace       used 118903K, capacity 121481K, committed 121632K, reserved 1146880K
  class space    used 14438K, capacity 15451K, committed 15564K, reserved 1040384K

这里的 class Space 14438K,Metaspace 118903K 和API 的 14M,116M 基本能对应上。

3.Native Memory 快照:

jcmd 18228  VM.native_memory summary scale=MB     
18228:
Native Memory Tracking:
Total: reserved=1795MB, committed=520MB
-                 Java Heap (reserved=256MB, committed=256MB)
                            (mmap: reserved=256MB, committed=256MB) 
-                     Class (reserved=1123MB, committed=122MB)
                            (classes #22297)
                            (  instance classes #20922, array classes #1375)
                            (malloc=3MB #54279) 
                            (mmap: reserved=1120MB, committed=119MB) 
                            (  Metadata:   )
                            (    reserved=104MB, committed=104MB)
                            (    used=102MB)
                            (    free=2MB)
                            (    waste=0MB =0.00%)
                            (  Class space:)
                            (    reserved=1016MB, committed=15MB)
                            (    used=14MB)
                            (    free=1MB)
                            (    waste=0MB =0.00%)
-                    Thread (reserved=80MB, committed=8MB)
                            (thread #79)
                            (stack: reserved=79MB, committed=8MB)
-                      Code (reserved=245MB, committed=42MB)
                            (malloc=3MB #14293) 
                            (mmap: reserved=242MB, committed=39MB) 
-                        GC (reserved=48MB, committed=48MB)
                            (malloc=7MB #20188) 
                            (mmap: reserved=42MB, committed=42MB) 
-                  Internal (reserved=1MB, committed=1MB)
                            (malloc=1MB #2072) 
-                     Other (reserved=4MB, committed=4MB)
                            (malloc=4MB #22) 
-                    Symbol (reserved=26MB, committed=26MB)
                            (malloc=22MB #633541) 
                            (arena=4MB #1)
-    Native Memory Tracking (reserved=12MB, committed=12MB)
                            (tracking overhead=11MB)

让我们一段一段的分析NMT的输出。

先插入一个常识:

reserved :操作系统已经为该进程“保留”的。所谓的保留,更加接近一种记账的概念,就是操作系统承诺最多可以给你多少资源,到时候并不一定能申请的到。
used:你当前正在使用的有多少资源。
committed:进程已经申请进的内存,可能在被使用,有可能闲置着。used<=committed
3.1 分配的内存
Native Memory Tracking:
Total: reserved=1795MB, committed=520MB

reserved显示了我们应用程序能够预定的最大内存。相对地,committed内存是当前已经申请进JAVA进程的内存。

尽管分配了256 MB的堆内存,但是我们应用程序全部预定内存大约1.7 GB,远远大于堆内存。类似,使用内存大约520 MB,也大于256 MB。

3.2 堆

NMT打印我们的堆内存,确实像我们前面设置的一样:

Java Heap (reserved=256MB, committed=256MB)
(mmap: reserved=256MB, committed=256MB)

256MB的保留和使用内存,符合我们设置的情况。

3.3 元数据区(Metaspace)

下面是加载的类的类元数据的信息:

Class (reserved=1123MB, committed=122MB)
(classes #22297)
(  instance classes #20922, array classes #1375)
(malloc=3MB #54279) 
(mmap: reserved=1120MB, committed=119MB) 
(  Metadata:   )
(    reserved=104MB, committed=104MB)
(    used=102MB)
(    free=2MB)
(    waste=0MB =0.00%)
(  Class space:)
(    reserved=1016MB, committed=15MB)
(    used=14MB)
(    free=1MB)
(    waste=0MB =0.00%)

大约1123MB的预定内存和122 MB的使用空间区加载22297个类。这里的used MetaData 102MB 和used Class space 14MB 没有和API获得的完全对应上 [Metaspace,Compressed Class Space] memory 2280 MB,used 165 MB [116,14] .这是为什么?

3.4 线程空间(Thread)

下面是线程的内存分配信息:

Thread (reserved=80MB, committed=8MB)
(thread #79)
(stack: reserved=79MB, committed=8MB)

总共,8MB的内存被分配到了79个线程——大约每个栈有0.1MB的内存。

3.5 代码缓存(Code Cache)

让我们看看JIT产生的汇编指令所占的空间:

Code (reserved=245MB, committed=42MB)
(malloc=3MB #14293) 
(mmap: reserved=242MB, committed=39MB)

这个也在API 中反映, [CodeHeap ‘non-nmethods’,CodeHeap ‘non-profiled nmethods’,CodeHeap ‘profiled nmethods’] memory [1, 7, 26] 共 34MB,

当前,大约42MB的空间被会缓存了,并且能使用的空间大约在245MB。

3.6 GC

下面是NMT报告的G1GC的内存使用情况:

GC (reserved=48MB, committed=48MB)
(malloc=7MB #20188) 
(mmap: reserved=42MB, committed=42MB)

我们能看到,大约48MB的空间是G1可以保留和使用的。

3.7 其他(Other)
Other (reserved=4MB, committed=4MB)
(malloc=4MB #22)

回顾下我们API监控的日志“max direct memory 256 MB,used 4 MB”

从Other部分可以看到其值跟Direct Memory使用的值一致,改变ByteBuffer.allocateDirect的值再重新查看,可以发现Other部分跟着改变;因而初步断定Other部分应该是可以反映direct memory的使用大小

3.8 符号(Symbol)

下面是NMT打印有关符号的分配,如同String table和常量池:

Symbol (reserved=26MB, committed=26MB)
(malloc=22MB #633541) 
(arena=4MB #1)

大约26MB被分配给Symbol使用。

4.NMT 监控时间段

NMT允许我们追踪在一段时间内的内存改变情况。首先我们应该标记当前我们应用程序的状态作为基线:

$ jcmd VM.native_memory baseline

Baseline succeeded

1

2

然后,过一段时间,我们就能够比较出当前内存与基线之间的差别:

$ jcmd VM.native_memory summary.diff

1

现在,使用+和-标记,就能够告诉我们在这段时间内内存的使用情况:

总结

我们通过API ,MetaData , Native memory 快照,让我们对JAVA内存结构有了更深度的认识,以便以后在分析问题时,能有更客观的数据分析的基础。

参考文献:

Java memory management

Native Memory Tracking in JVM

目录
相关文章
|
20天前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
21天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
1月前
|
缓存 easyexcel Java
Java EasyExcel 导出报内存溢出如何解决
大家好,我是V哥。使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。以下是V哥整理的解决该问题的一些常见方法,包括分批写入、设置合适的JVM内存、减少数据对象的复杂性、关闭自动列宽设置、使用Stream导出以及选择合适的数据导出工具。此外,还介绍了使用Apache POI的SXSSFWorkbook实现百万级别数据量的导出案例,帮助大家更好地应对大数据导出的挑战。欢迎一起讨论!
164 1
|
2天前
|
Java
java内存区域
1)栈内存:保存所有的对象名称 2)堆内存:保存每个对象的具体属性 3)全局数据区:保存static类型的属性 4)全局代码区:保存所有的方法定义
8 1
|
16天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
15天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
25天前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
147 9
|
20天前
|
存储 缓存 安全
Java内存模型(JMM):深入理解并发编程的基石####
【10月更文挑战第29天】 本文作为一篇技术性文章,旨在深入探讨Java内存模型(JMM)的核心概念、工作原理及其在并发编程中的应用。我们将从JMM的基本定义出发,逐步剖析其如何通过happens-before原则、volatile关键字、synchronized关键字等机制,解决多线程环境下的数据可见性、原子性和有序性问题。不同于常规摘要的简述方式,本摘要将直接概述文章的核心内容,为读者提供一个清晰的学习路径。 ####
35 2
|
21天前
|
存储 安全 Java
什么是 Java 的内存模型?
Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)规范的一部分,它定义了一套规则,用于指导Java程序中变量的访问和内存交互方式。
49 1
|
24天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
44 2
下一篇
无影云桌面