JVM堆外内存分配问题

JVM内存分为两类:堆内存和堆外内存,JVM复杂性内存占用便是其中一项。

内存分类

JVM将内存分为两大类:堆内存和非堆内存。

堆内存通常是最熟悉的部分,存储应用程序运行时创建的对象,由垃圾收集机制管理。

通常,应用程序对堆的使用随负载波动。

JVM非堆内存划分区域,使用HotSpot VM本机内存跟踪(NMT)检查。

应用程序启动时使用如下参数打开NMT,使用VM jcmd 查看内存使用情况。

-XX:NativeMemoryTracking=summary

注:NMT不跟踪与jvm无关的内存(如,第三方本地代码使用的内存)。

如下示例中,应用程序被限制在48M的堆空间中(-Xmx48M),图中是NMT显示JVM内存使用情况:

如图所示,非堆内存占用了很大比例,堆内存仅占总数六分之一。

示例中,大约是44MB(垃圾收集后立即使用33MB),非堆内存使用量223MB。

本地内存区域

Compressed class space

存储已加载的类信息,受到MaxMetaspaceSize限制。

Thread

JVM中线程使用的内存。

Code cache

存储JIT输出的内存。受ReservedCodeCacheSize限制。如,将JIT调优为禁用分层编译来减少。

GC

存储GC所使用的数据。垃圾收集器不同而不同。

Symbol

存储诸如字段名、方法签名及中间字符串等符号。过多的符号内存占用可能是字符串被过度使用的信号。

Internal

存储不适合其他区域的内部数据。

Differences

与堆内存相比,非堆内存在负载下不会发生太大变化。

一旦应用程序将要使用的类全部加载完成,且JIT预热完成,非堆内存就会稳定下来。

为减少压缩类空间(Compressed class space)的使用,需对加载类所用的类加载器执行垃圾收集。

这在过去很常见,应用程序部署到servlet容器,或应用程序服务器时,应用程序类加载器在应用程序卸载时将被垃圾收集,现今应用程序中很少使用。

非堆内存分配问题

在调整JVM大小时,一个有趣的区域就是JIT的代码缓存。

默认情况下,HotSpot JVM最多分配240MB,如果代码缓存太小,JIT将耗尽所分配的存储空间,从而影响性能。

如果缓存太大,会浪费内存。

在调整代码缓存大小时,要查看应用程序对内存的使用及性能。

最新版本java能感知到容器内存限制,尝试调整JVM内存大小。不幸的是,这种调整通常会过度分配非堆内存,导致堆内存分配不足。

假如,现在有一个应用程序在容器中运行,容器有2颗cpu和512MB可用内存。

如果,希望应用能处理更多负载,可将cpu增加一倍,达到4,内存增加到1GB。

堆使用情况通常随负载发生变化,非堆使用则要少得多。

所以,通常希望将额外的512MB内存分配给堆,应对增加的负载。

不幸的是,默认JVM不会这样做,而是在堆和非堆间平均地分配内存。,

各生态对JVM内存的使用都给予了高度关注,Spring生态使用通用编程减少框架代码。

并基于堆缓存优化启动时间,启动完成清除缓存,随后,GC会回收堆内存,尽可能为用程序负载腾出空间。