【转载】8年开发架构师浅析SpringBoot的JVM的内存占用与Docker-spring.io

作者:Java高级架构狮
链接:https://www.jianshu.com/p/67c90867e311
来源:简书

 

8年开发架构师浅析SpringBoot的JVM的内存占用与Docker-spring.io

 

 

JVM可能是一个复杂的野兽。值得庆幸的是,大部分复杂性都在幕后,我们作为应用程序开发人员和部署人员通常不必过于担心。随着基于容器的部署策略的兴起,需要引起注意的一个复杂领域是JVM的内存占用。

两种内存

JVM将其内存分为两大类:堆内存和非堆内存。堆内存是人们通常最熟悉的部分。它是存储由应用程序创建的对象的位置。它们一直存在,直到它们不再被引用并被垃圾收集。通常,应用程序使用的堆量将根据当前负载而波动。

JVM的非堆内存分为几个不同的区域。我们可以使用HotSpot VM的本机内存跟踪(NMT)来检查这些区域的内存使用情况。请注意,虽然NMT不跟踪所有原生本机Native内存使用情况(例如,它不跟踪第三方本机代码内存分配),但对于大类典型的Spring应用程序来说已经足够了。可以通过启动应用程序-XX:NativeMemoryTracking=summary然后使用jcmd <pid> VM.native_memory summary来显示内存使用情况摘要来使用NMT 。

让我们通过查看应用程序来说明NMT的使用,在这种情况下,我们的老朋友Petclinic。下面的饼图显示了当使用48MB最大堆(-Xmx48M)启动Petclinic时由NMT报告的JVM的内存使用量(减去其自身的开销):

 

批注:使用nmt工具,即在java的启动时添加-XX:NativeMemoryTracking=detail 参数,然后java进程启动后,使用jdk下的jcmd工具查看:jcmd 430 VM.native_memory detail 如果不带detail或者detail改为summary只会打印出概览信息。

你会在标准输出得到类似下面的内容(本文去掉了许多与本文无关或者重复的信息):

14179:

Native Memory Tracking:

Total: reserved=653853KB, committed=439409KB
-                 Java Heap (reserved=262144KB, committed=262144KB)
                            (mmap: reserved=262144KB, committed=262144KB) 

-                     Class (reserved=82517KB, committed=81725KB)
                            (classes #17828)
                            (malloc=1317KB #26910) 
                            (mmap: reserved=81200KB, committed=80408KB) 

-                    Thread (reserved=20559KB, committed=20559KB)
                            (thread #58)
                            (stack: reserved=20388KB, committed=20388KB)
                            (malloc=102KB #292) 
                            (arena=69KB #114)

-                      Code (reserved=255309KB, committed=41657KB)
                            (malloc=5709KB #11730) 
                            (mmap: reserved=249600KB, committed=35948KB) 

-                        GC (reserved=1658KB, committed=1658KB)
                            (malloc=798KB #676) 
                            (mmap: reserved=860KB, committed=860KB) 

-                  Compiler (reserved=130KB, committed=130KB)
                            (malloc=31KB #357) 
                            (arena=99KB #3)

-                  Internal (reserved=5039KB, committed=5039KB)
                            (malloc=5007KB #20850) 
                            (mmap: reserved=32KB, committed=32KB) 

-                    Symbol (reserved=18402KB, committed=18402KB)
                            (malloc=14972KB #221052) 
                            (arena=3430KB #1)

-    Native Memory Tracking (reserved=2269KB, committed=2269KB)
                            (malloc=53KB #1597) 
                            (tracking overhead=2216KB)


-               Arena Chunk (reserved=187KB, committed=187KB)
                            (malloc=187KB) 

-                   Unknown (reserved=5640KB, committed=5640KB)
                            (mmap: reserved=5640KB, committed=5640KB) 
 . . .
Virtual memory map:

————————————————
样例来自:版权声明:本文为CSDN博主「jicahoo」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jicahoo/article/details/50933469

还可以通过下面VM参数在JVM退出时打印NMT报告。

-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

 

正如您所看到的,非堆内存占绝大多数JVM的内存使用量,堆内存仅占总数的六分之一。在这种情况下,大约44MB(垃圾收集后立即使用33MB)。非堆内存使用总量为223MB。

本机Native内存区域

  • 压缩类空间:用于存储有关已加载的类的信息。受到约束MaxMetaspaceSize。已加载的类数的函数。
  • 线程:JVM中线程使用的内存。正在运行的线程数的函数。
    • 批注:设置-Xss=256k,限制每个线程的堆大小
  • 代码缓存:JIT用于存储其输出的内存。已加载的类数的函数。受到约束ReservedCodeCacheSize。可以通过调整JIT来减少,例如,禁用分层编译。
    • 批注:添加参数 -Djava.compiler=NONE
  • GC:存储GC使用的数据。根据使用的垃圾收集器而有所不同。
    • 批注:-XX:ParallelGCThreads=2
  • 符号:存储符号,如字段名称,方法签名和实习字符串。过多的符号内存使用情况可能表明字符串过于激进。
  • 内部:存储不适合任何其他区域的其他内部数据。

非堆内存与堆内存的不同

与堆内存相比,非堆内存在负载下不太可能发生变化。一旦应用程序加载了它将使用的所有类并且JIT完全预热,事情就会陷入稳定状态。要查看压缩类空间使用量的减少,加载类的类加载器需要进行垃圾回收。在将应用程序部署到servlet容器或应用程序服务器时,这种情况更常见 - 应用程序的类加载器将在取消部署应用程序时进行垃圾收集 - 但现代应用程序部署方法很少发生。

调整JVM的大小

配置JVM以有效利用给定数量的可用RAM并不容易。如果您启动JVM -Xmx16M并期望它最多可以使用16MB的RAM,那么您会感到非常惊讶。

调整JVM大小的一个有趣的方面是JIT的代码缓存。默认情况下,HotSpot JVM最多可使用240MB。如果代码缓存太小,JIT将耗尽空间来存储其输出,因此性能将受到影响。如果缓存太大,可能会浪费内存。在调整代码缓存大小时,查看应用程序内存使用情况及其性能的影响非常重要。

在Docker容器中运行时,Java的最新版本现在知道容器的内存限制并尝试相应地调整JVM的大小。不幸的是,这种大小调整经常过度分配非堆内存并且分配不足。假设您有一个在具有2个CPU和512MB可用内存的容器中运行的应用程序。您希望它能够处理更多负载,因此您将CPU加倍为4,将内存加倍至1GB。如上所述,堆使用通常根据负载而变化,而非堆使用则更少。因此,我们希望将大部分额外的512MB内存提供给堆来应对增加的负载。不幸的是,JVM默认情况下不会这样做,并且会在其堆和非堆区域之间更均等地分配额外的内存。

值得庆幸的是,CloudFoundry团队拥有丰富的JVM内存占用知识。如果您要将应用程序推送到CloudFoundry,构建包将自动为您应用此知识。

这对Spring来说意味着什么?

考虑到堆和非堆内存使用情况,我们花了很多时间在Spring团队中考虑性能和内存利用率。限制非堆内存使用的一种方法是使Framework的一部分尽可能通用。这方面的一个例子是使用反射来创建应用程序的bean并将其注入到应用程序的bean中。由于使用了反射,所使用的Framework代码量保持不变,无论应用程序包含多少bean。我们使用基于堆的缓存来优化启动时间,一旦启动完成就清除此缓存。然后,垃圾收集器可以轻松地回收堆内存,从而在应用程序处理其工作负载时为应用程序提供尽可能多的内存。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值