首页 > 系统相关 >本地内存跟踪NMT详解

本地内存跟踪NMT详解

时间:2023-08-09 13:55:56浏览次数:29  
标签:NMT reserved 占用 XX 详解 内存 JVM committed

\

本地内存跟踪NMT详解​

1. Overview
为什么java程序消耗的内存,远超-Xms、-Xmx的限制?因为各种原因,或是为了进行某些优化,JVM会额外分配内存。这些额外的分配,会导致java程序占用的内存,超出-Xmx的限制。

本文档列举了通常情况下,JVM会分配哪几部分内存,以及各部分调整大小的方法。然后,了解如何使用Native Memory Tracking工具进行监控。

2. Native Allocations
通常,heap是java程序占用内存中的最大的一部分,但也有特例。除了heap,JVM会分配很大一块内存,用于存储metadata、application code、the code generated by JIT、internal data structures等等。如下章节,对各部分进行详述。

2.1. Metaspace
JVM使用专用的non-heap区域,存储已加载类的元数据。java 8版本以前,此区域称为PermGen or Permanent Generation。此区域存储的是类的元数据,而不是类的实例,实例是存储在heap内存中的。

对heap内存的限制,是无法影响Metaspace的。如果要调整Metaspace,要使用如下标志:

-XX:MetaspaceSize,最小值

-XX:MaxMetaspaceSize,最大值

Java 8版本以前,使用-XX:PermSize、-XX:MaxPermSize,含义是一样的。

2.2. Threads
另一个消耗内存较大的部分是stack。创建线程时,同时创建stack。stack存储局部变量和中间结果,在方法调用中扮演重要角色。

线程stack的默认大小与平台相关,但是,对于现在大多的64位操作系统来说,大约是1MB。此值可以通过-Xss调整。

创建的线程越多,此部分占用的内存越多。

另外需要注意的是,JVM本身也需要一些线程,用于执行内部操作,如:GC、JIT编译。

2.3. Code Cache
为了在不同平台运行JVM字节码,需要将其转换成机器指令。程序运行时,JIT编译器负责这个编译工作。当JVM将字节码编译为汇编指令时,它将这些指令存储在一个特殊non-heap区域:Code Cache。Code Cache可以像JVM的其他数据区域一样被管理。控制此区域的大小,使用如下两个指令:

-XX:InitialCodeCacheSize,初始值

-XX:ReservedCodeCacheSize,最大值

2.4. Garbage Collection
JVM附带了一些GC算法,每个算法都适用于不同的用例。所有这些GC算法都有一个共同的特征:他们需要一些堆外数据结构来执行任务。这些内部数据结构,会消耗一些内存。

2.5. Symbols
让我们从Strings开始,这是最常用的数据类型之一。因为其使用频率很高,Strings通常会占用较大一部分heap内存。如果大量的Strings包含相同的内容,那么会造成heap内存的浪费。

为了节省内存,对于每一个String可以仅存一个副本,然后其他的指向该副本。这个过程称为字符串驻留(String Interning)。JVM仅可以驻留编译时的字符串常量(Compile Time String Constants),我们可以对strings手动调用intern()方法以实现驻留。

JVM将驻留的strings存储在一个专用的固定大小的hashtable中,称为String Table,也称为String Pool。可以通过如下标志调节其大小:-XX:StringTableSize。

除了String Table,还有一个内存区域称为运行时常量池(Runtime Constant Pool),JVM使用这个池来存储一些必须在运行时解析的常量,如编译时数值常量或方法和字段引用。

2.6. Native Byte Buffers
JVM通常是大量内存占用的可疑对象,但有时开发人员也可以直接分配内存。最常见的方式是:

malloc call by JNI;
NIO's direct ByteBuffers;
2.7. Additional Tuning Flags
本章节使用了一些JVM调节标志。使用如下命令,可以找到几乎所有的、关于特定概念的调节标志。

java -XX:+PrintFlagsFinal -version | grep <concept>
1.


PrintFlagsFinal会打印出JVM中所有的-XX标志。例如,找出所有关于Metaspace的标志:

java -XX:+PrintFlagsFinal -version | grep Metaspace
// truncated
uintx MaxMetaspaceSize                      = 18446744073709547520                {product}
uintx MetaspaceSize                         = 21807104                            {pd product}
// truncated
1.
2.
3.
4.
5.


3. Native Memory Tracking (NMT)
我们知道了JVM中消耗内存的几个源头,现在就来看看如何监视它们。首先,启用NMT,在启动命令中加入如下标志即可:

-XX:NativeMemoryTracking=off|sumary|detail
1.


NMT默认是关闭的。

假设,我们想要跟踪一个典型的SpringBoot应用程序:

java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC -jar app.jar
1.


3.1. Instant Snapshots
开启NMT后,可以使用如下命令,随时获取本地内存占用信息。其中表示java进程的id。

jcmd <pid> VM.native_memory
1.


下面详解NMT命令的输出内容。

3.2. Total Allocations
NMT显示总的预留内存、已提交内存:

Native Memory Tracking:
Total: reserved=1731124KB, committed=448152KB
1.
2.


预留内存表示我们的应用程序可能使用的内存总量。已提交内存表示应用程序当前使用的内存。

尽管仅为应用程序分配了300MB内存,但总的预留内存近1.7GB。类似的,已提交内存近440M。这两个数据都比300MB多了很多。

除了整体的内存占用信息外,NMT还报告了各个源头占用内存的情况。下面章节详述。

3.3. Heap
heap内存占用情况显示如下:

Java Heap (reserved=307200KB, committed=307200KB)
          (mmap: reserved=307200KB, committed=307200KB)
1.
2.


预留内存、已提交内存均为300MB,符合我们对heap内存的设置。

3.4. Metaspace
已加载类的元数据内存占用信息如下:

Class (reserved=1091407KB, committed=45815KB)
      (classes #6566)
      (malloc=10063KB #8519) 
      (mmap: reserved=1081344KB, committed=35752KB)
1.
2.
3.
4.


加载了6566个class,预留内存差不多1G,提交内存45M。

3.5. Thread
线程内存分配情况如下:

Thread (reserved=37018KB, committed=37018KB)
       (thread #37)
       (stack: reserved=36864KB, committed=36864KB)
       (malloc=112KB #190) 
       (arena=42KB #72)
1.
2.
3.
4.
5.


37个线程,stack内存共计36M,差不多每个线程的stack占用1M。JVM在创建线程时,同时分配stack内存,所以预留内存和提交内存是一样的。

3.6. Code Cache
JIT生成并缓存的汇编指令的内存占用情况:

Code (reserved=251549KB, committed=14169KB)
     (malloc=1949KB #3424) 
     (mmap: reserved=249600KB, committed=12220KB)
1.
2.
3.


当前,大概13M的内存占用,可能会增加到大约245M(预留内存)。

3.7. GC
G1 GC内存占用情况如下:

GC (reserved=61771KB, committed=61771KB)
   (malloc=17603KB #4501) 
   (mmap: reserved=44168KB, committed=44168KB)
1.
2.
3.


预留内存大概60M。

Serial GC是一个简单的多的方法,当使用此方法时,配置方法如下:

java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseSerialGC -jar app.jar
1.


内存占用情况如下,仅仅用了1M:

GC (reserved=1034KB, committed=1034KB)
   (malloc=26KB #158) 
   (mmap: reserved=1008KB, committed=1008KB)
1.
2.
3.


当然,我们不能仅根据内存消耗来决定选择什么GC算法,因为Serial GC的“stop-the-world”特性,可能会导致性能下降。

3.8. Symbol
symbol内存占用情况如下,如string table和constant pool:

Symbol (reserved=10148KB, committed=10148KB)
       (malloc=7295KB #66194) 
       (arena=2853KB #1)
1.
2.
3.


大概占用10M。

3.9. NMT over Time
NMT使得我们可以跟踪内存占用情况。首先,要记录应用程序当前的内存占用情况,做为基线。命令如下:

$ jcmd <pid> VM.native_memory baseline
Baseline succeeded
1.
2.


然后,过一段时间,可以将当前内存占用与基线做比较:

$ jcmd <pid> VM.native_memory summary.diff

NMT通过+ -符号,表示这段时间内,内存占用的变化情况:
Total: reserved=1771487KB +3373KB, committed=491491KB +6873KB
-             Java Heap (reserved=307200KB, committed=307200KB)
                        (mmap: reserved=307200KB, committed=307200KB)

-             Class (reserved=1084300KB +2103KB, committed=39356KB +2871KB)
// Truncated
1.
2.
3.
4.
5.
6.
7.
8.
9.


预留内存、提交内存分别增长了3M、6M。内存分配中的其他波动也可以很容易地发现。

3.10. Detailed NMT
NMT可以提供关于整个内存空间占用情况的非常详细的信息。要显示详细信息,要使用如下标志:

-XX:NativeMemoryTracking=detail
1.


4. Conclusion
我们列举了JVM中内存占用的不同类别。然后,我们学习了如何监控一个正在运行的应用程序的内存占用情况。有了这些,我们可以更有效地调整运行时环境的大小。

关于JIT

对于Java语言:

一、你可以说它是编译型的:因为所有的Java代码都是要编译的,.java不经过编译就什么用都没有。

二、你可以说它是解释型的:因为java代码编译后不能直接运行,它是解释运行在JVM上的,所以它是解释运行的,那也就算是解释的了。

三、但是,现在的JVM为了效率,都有一些JIT优化。它又会把.class的二进制代码编译为本地的代码(汇编)直接运行,所以,又是编译的。

像C、C++ 他们经过一次编译之后直接可以编译成操作系统了解的类型,可以直接执行的,所以他们是编译型的语言。没有经过第二次的处理。

而Java不一样,他首先由编译器编译成.class类型的文件,这个是java自己类型的文件 然后再通过虚拟机(JVM)从.class文件中读一行解释执行一行,所以他是解释型的语言,而由于java对于多种不同的操作系统有不同的JVM,所以,Java实现了真正意义上的跨平台!

JIT:Just In Time Compiler,一般翻译为即时编译器,这是是针对解释型语言而言的,而且并非虚拟机必须,是一种优化手段,Java的商用虚拟机HotSpot就有这种技术手段,Java虚拟机标准对JIT的存在没有作出任何规范,所以这是虚拟机实现的自定义优化技术。

HotSpot虚拟机的执行引擎在执行Java代码是可以采用【解释执行】和【编译执行】两种方式的,如果采用的是编译执行方式,那么就会使用到JIT,而解释执行就不会使用到JIT,所以,早期说Java是解释型语言,是没有任何问题的,而在拥有JIT的Java虚拟机环境下,说Java是解释型语言严格意义上已经不正确了。

HotSpot中的编译器是javac,他的工作是将源代码编译成字节码,这部分工作是完全独立的,完全不需要运行时参与,所以Java程序的编译是半独立的实现。有了字节码,就有解释器来进行解释执行,这是早期虚拟机的工作流程,后来,虚拟机会将执行频率高的方法或语句块通过JIT编译成本地机器码,提高了代码执行的效率。

--结束--

 

标签:NMT,reserved,占用,XX,详解,内存,JVM,committed
From: https://www.cnblogs.com/iancloud/p/17616682.html

相关文章

  • Apache HttpComponents Client详解
     ApacheHttpComponentsClient(也称为HttpClient)是一个开源的Java库,用于发送HTTP请求并处理HTTP响应。它提供了一组易于使用的API,用于构建和执行HTTP请求,并处理请求和响应的各个方面,如URL处理、请求头、请求体、响应状态、响应内容等。下面是一些关于使用ApacheHttpComponentsCl......
  • Apache HttpComponents Client详解
     ApacheHttpComponentsClient(也称为HttpClient)是一个开源的Java库,用于发送HTTP请求并处理HTTP响应。它提供了一组易于使用的API,用于构建和执行HTTP请求,并处理请求和响应的各个方面,如URL处理、请求头、请求体、响应状态、响应内容等。下面是一些关于使用ApacheHttpComponentsCl......
  • Apache HttpComponents Client详解
    ​ ApacheHttpComponentsClient(也称为HttpClient)是一个开源的Java库,用于发送HTTP请求并处理HTTP响应。它提供了一组易于使用的API,用于构建和执行HTTP请求,并处理请求和响应的各个方面,如URL处理、请求头、请求体、响应状态、响应内容等。下面是一些关于使用ApacheHttpCompone......
  • Apache HttpComponents Client详解
    ​ ApacheHttpComponentsClient(也称为HttpClient)是一个开源的Java库,用于发送HTTP请求并处理HTTP响应。它提供了一组易于使用的API,用于构建和执行HTTP请求,并处理请求和响应的各个方面,如URL处理、请求头、请求体、响应状态、响应内容等。下面是一些关于使用ApacheHttpCompone......
  • visual studio 2022社区版安装图文详解
    0.社区版免费,功能够用1.官网下载VisualStudio2022IDE-适用于软件开发人员的编程工具(microsoft.com) 2.安装    等待   等待  安装成功会出现下面界面:  你有账号可以登录,没有可以创建,也可以选择暂时跳过此项。如果选择登录。 输入......
  • 硬盘SMART检测参数详解[转]
    一、SMART概述      要说Linux用户最不愿意看到的事情,莫过于在毫无警告的情况下发现硬盘崩溃了。诸如RAID的备份和存储技术可以在任何时候帮用户恢复数据,但为预防硬件崩溃造成数据丢失所花费的代价却是相当可观的,特别是在用户从来没有提前考虑过在这些情况下的应对措施时......
  • 内存管理
    目标多进程并发的场景下如何安全高效的共享内存提高内存利用率和内存寻址效率主要的内存管理技术引入虚拟内存,使进程对内存地址的访问从直接变为间接,实现了进程地址空间的隔离引入分页机制,实现细粒度的动态内存分配和管理,有效减少了内存碎片,提高了内存利用率通过TLB(地址......
  • Windows系统 如何配置Maven的本地仓库 【详解Maven settings.xml配置(指定本地仓库、
    1.确认安装Maven首先,我们需要确认已在计算机上安装了Maven。你可以从Maven官方网站:https://archive.apache.org/dist/maven/maven-3/下载适合你代码ide版本的Maven安装包,比如我idea2019就下载maven3.5-3.6之间的版本。1、先新建名为MAVEN_HOME的变量,值为你的的maven解压路径......
  • 《CUDA编程:基础与实践》读书笔记(2):CUDA内存
    1.全局内存核函数中的所有线程都能够访问全局内存(globalmemory)。全局内存的容量是所有设备内存中最大的,但由于它没有放在GPU芯片内部,因此具有相对较高的延迟和较低的访问速度,cudaMalloc分配的就是全局内存。此外,当处理逻辑上的二维或者三维问题时,还可以使用cudaMallocPitch和......
  • k8s---使用ingress配置域名转发时的traefik路径规则详解
    ingress中traefik的使用方式如下:apiVersion:extensions/v1beta1kind:Ingressmetadata:name:spark-client-testnamespace:defaultannotations:kubernetes.io/ingress.class:traefiktraefik.frontend.rule.type:PathPrefixspec:rules:-host:......