通常,堆是Java应用程序中最大的内存消耗者,但是还有其他一些。**除了堆之外,JVM从本地内存中分配了相当大的块来维护其类元数据,应用程序代码,由JIT生成的代码,内部数据结构等。**在以下各节中,我们将探讨其中的一些分配。

可以看到整个memory主要包含了Java Heap、Class、Thread、Code、GC、Compiler、Internal、Other、Symbol、Native Memory Tracking、Arena Chunk这几部分;其中reserved表示应用可用的内存大小,committed表示应用正在使用的内存大小
Java Heap部分表示heap内存目前占用了463MB;
Class部分表示已经加载的classes个数为8801,其metadata占用了50MB;
Thread部分表示目前有225个线程,占用了27MB;
Code部分表示JIT生成的或者缓存的instructions占用了17MB;
GC部分表示目前已经占用了15MB的内存空间用于帮助GC;
Compiler部分表示compiler生成code的时候占用了26MB;
Internal部分表示命令行解析、JVMTI等占用了5MB;
Other部分表示尚未归类的占用了2MB;
Symbol部分表示诸如string table及constant pool等symbol占用了10MB;
Native Memory Tracking部分表示该功能自身占用了5MB;
Arena Chunk部分表示arena chunk占用了63MB
一个arena表示使用malloc分配的一个memory chunk,这些chunks可以被其他subsystems做为临时内存使用,比如pre-thread的内存分配,它的内存释放是成bulk的
在本节中,我们针对不同的优化方案使用了少数JVM调整标志。使用以下技巧,我们几乎可以找到与特定概念相关的所有调整标志:
$ java -XX:+PrintFlagsFinal -version | grep
该PrintFlagsFinal打印所有- *XX *在JVM选项。例如,要查找所有与Metaspace相关的标志:
$ java -XX:+PrintFlagsFinal -version | grep Metaspace // truncated uintx MaxMetaspaceSize = 18446744073709547520 {product} uintx MetaspaceSize = 21807104 {pd product} // truncated
既然我们知道了JVM中本机内存分配的常见来源,那么该是时候找出如何监视它们了。**首先,我们应该使用另一个JVM调整标志启用本地内存跟踪:*-XX:NativeMemoryTracking = off | sumary | detail。 ***默认情况下,NMT处于关闭状态,但我们可以使它查看其观测结果的摘要或详细视图。
假设我们要跟踪典型的Spring Boot应用程序的本机分配:
$ java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC -jar app.jar
在这里,我们使用G1作为GC算法,在分配300 MB堆空间的同时启用NMT。
启用NMT后,我们可以随时使用*jcmd *命令获取本机内存信息 :
$ jcmd VM.native_memory
为了找到JVM应用程序的PID,我们可以使用 jps命令:
$ jps -l 7858 app.jar // This is our app7899 sun.tools.jps.Jps
现在,如果我们将 jcmd *与适当的*pid一起使用, *VM.native_memory *将使JVM打印出有关本机分配的信息:
$ jcmd 7858 VM.native_memory
让我们逐节分析NMT输出。
NMT报告保留和提交的内存总量,如下所示:
Native Memory Tracking:Total: reserved=1731124KB, committed=448152KB
保留的内存代表我们的应用程序可能使用的内存总量。相反,已提交的内存等于我们的应用程序当前正在使用的内存量。
尽管分配了300 MB的堆,但我们的应用程序的总保留内存几乎为1.7 GB,远不止于此。同样,已提交的内存大约为440 MB,这又远远超过了300 MB。
在合计部分之后,NMT报告每个分配源的内存分配。因此,让我们深入探讨每个来源。
NMT按预期报告了我们的堆分配:
Java Heap (reserved=307200KB, committed=307200KB) (mmap: reserved=307200KB, committed=307200KB)
300 MB的保留和提交内存,与我们的堆大小设置匹配。
NMT关于已加载类的类元数据的说明如下:
Class (reserved=1091407KB, committed=45815KB) (classes #6566) (malloc=10063KB #8519) (mmap: reserved=1081344KB, committed=35752KB)
保留了将近1 GB的空间,并有45 MB的空间用于加载6566类。
这是关于线程分配的NMT报告:
Thread (reserved=37018KB, committed=37018KB) (thread #37) (stack: reserved=36864KB, committed=36864KB) (malloc=112KB #190) (arena=42KB #72)
总共为37个线程的堆栈分配了36 MB的内存–每个堆栈几乎1 MB。JVM在创建时将内存分配给线程,因此保留和提交的分配是相等的。
让我们看看NMT对JIT生成和缓存的汇编指令的评价:
Code (reserved=251549KB, committed=14169KB) (malloc=1949KB #3424) (mmap: reserved=249600KB, committed=12220KB)
当前,将近有13 MB的代码被缓存,并且该数量可能会增加到大约245 MB。
这是有关G1 GC内存使用情况的NMT报告:
GC (reserved=61771KB, committed=61771KB) (malloc=17603KB #4501) (mmap: reserved=44168KB, committed=44168KB)
我们可以看到,几乎有60 MB的空间被保留并致力于帮助G1。
让我们看看一个简单得多的GC(例如串行GC)的内存使用情况:
$ java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseSerialGC -jar app.jar
串行GC几乎不使用1 MB:
GC (reserved=1034KB, committed=1034KB) (malloc=26KB #158) (mmap: reserved=1008KB, committed=1008KB)
显然,我们不应该仅仅因为内存使用率就选择了GC算法,因为串行GC的“停滞不前”性质可能会导致性能下降。
这是有关符号分配的NMT报告,例如字符串表和常量池:
Symbol (reserved=10148KB, committed=10148KB) (malloc=7295KB #66194) (arena=2853KB #1)
将近10 MB分配给符号。
NMT使我们能够跟踪内存分配如何随时间变化。**首先,我们应将应用程序的当前状态标记为基线:
$ jcmd VM.native_memory baseline
然后,过一会儿,我们可以将当前内存使用量与该基准进行比较:
$ jcmd 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
保留和提交的总内存分别增加了3 MB和6 MB。可以很容易地发现内存分配中的其他波动。
1:针对不知道怎么面试,面试没有信心的小伙伴,我们会给你一个offer保障。
2:我们会监督你15-20天内把面试体系技术点掌握至少7成,这样足够你去找到满意的工作了。
3:我们是面向面试学习指导,不会带你们去写代码,会把项目真实开发的迭代过程和技术细节如何实现业务功能都详细教清楚,你能在面试中流畅表达清楚就行了,项目经验你不用担心(技术老师提供的真实项目经验肯定拿的出手),自己学和别人带着系统学,效率完全不一样。