一、Java 进程中有哪些组件会占用内存?

通过 Native Memory Tracking 可以观察到有以下 JVM 组件。在命令行加上-XX:NativeMemoryTracking=summary,会增加3MB左右内存,损失5%-10%CPU,不宜用于生产环境。

使用 jcmd <pid> VM.native_memory summary scale=MB 可以查看内存分配结果。如果报告"Native memory tracking is not enabled", 是因为该进程起动时命令行没有加上-XX:NativeMemoryTracking=summary参数。

1.1 Java 堆

最显而易见的就是 Java 堆,它是 Java 对象存在的地方。它会占用 -Xmx 参数指定大小的内存。

1.2 线程

线程堆栈也会申请内存。堆栈大小由 -Xss 选项指定,默认每个线程1M,幸运的是情况并非那么糟糕。操作系统会以延迟分配的方式分配内存页面,比如在第一次使用时分配,因此实际使用的内存要低得多,通常每个线程堆栈占用80至200KB。

还有其他 JVM 部件会占用本地内存,但它们在总内存消耗中通常比例不大。

1.3 类加载

类的元数据存储在 Metaspace 堆外区域中,包括方法字节码、符号、常量池、注解等。加载的类越多,使用的元数据就越多。可以通过 -XX:MaxMetaspaceSize(默认无上限)和 -XX:CompressedClassSpaceSize(默认1G)选项控制元数据总大小。

1.4 符号表

JVM 有两个主要的 hashtable:符号表包含名称、签名、标识符等,String 表包含对 interned String 引用。如果 Native Memory Tracking 显示 String 表使用了大量内存,这可能意味着应用程序调用 String.intern 过于频繁。

1.5 垃圾回收器

GC 需要额外的内存进行堆管理,主要用于 GC 自身的结构与算法。这些结构包括 Mark Bitmap、Mark Stack(遍历对象关系图)、Remembered Set(记录 region 之间引用)等等。其中一些可以直接调优,例如 -XX: MarkStackSizeMax 选项,另一些依赖于堆布局。其中 G1 region (-XX:G1HeapRegionSize)占用内存较大,Remembered Set 占用内存较小。

GC 的内存开销因算法而异,其中 -XX:+UseSerialGC 与 -XX:+UseShenandoahGC 的开销最小,而 G1 或 CMS 则会轻松占用大约10%的堆内存。

1.6 代码缓存
代码缓存包含动态生成的代码,JIT 编译生成的方法、解释器以及运行时 stub 代码。代码大小受 -XX:ReservedCodeCacheSize选项限制(默认为240M)。关闭 -XX:-TieredCompilation 可以减少已编译代码的数量,从而减小代码缓存。

1.7 编译器

JIT 编译器本身工作时也需要内存。可以通过关闭 Tiered Compilation 或者 -XX:CICompilerCount 减少编译使用的线程数。


1.8 Direct Buffer

应用程序可以通过 ByteBuffer.allocateDirect 调用直接请求非堆内存。默认的非堆内存大小限制由 -Xmx 选项指定,但也可以使用 -XX:MaxDirectMemorySize 覆盖配置。Direct ByteBuffer 包含在 Native Memory Tracking 输出的 Other 区域,在 JDK 11 之前包含在 Internal 区域。

通过 JMX 可以在 JConsole 或 Java Mission Control 中直接看到 Direct Memory 的使用量:

1.9 Native Library

System.Loadlibrary 加载的 JNI 代码可以不受 JVM 控制分配堆外内存,标准 Java Class Library 也是如此。尤其是未关闭的 Java 资源可能造成本地内存泄漏。典型的例子是 ZipInputStream 和 DirectoryStream。

JVMTI 代理,尤其是 jdwp 调试代理,也会造成内存消耗过多。

二 实例

2.1 一个很简单的Java程序内存占用: 15MB

Class: 5039 KB
Thread: 3042 KB
Code: 2557 KB
JavaHeap: 2048 KB
Symbol: 1742 KB
Internal: 230 KB
NativeMemoryTracking: 42 KB

ArenaChunk: 32 KB
GC: 19 KB
Total: 14751 KB [ Heap= 2048 , Thread= 3042 Class= 6781 , Other: 2880 ]

命令行:java -Xint -XX:+UseSerialGC -XX:NativeMemoryTracking=summary -Xmx1024k -Xss160K cn.PigEater

Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode) java version "1.8.0_181"

SprintBoot Jar包 1.7MB .

 

2.2 一个功能简单的Spring Cloud微服务(使用redis/mybatis)内存占用:162MB

JavaHeap: 64 MB
Class: 62 MB
Symbol: 17 MB
Thread: 11 MB
NativeMemoryTracking: 3 MB
Code: 3 MB
Internal: 2 MB
Total: 162 MB [ Heap= 64 , Thread= 11 Class+Symbol= 79 , Other: 8 ]

命令行:java -XX:+UseSerialGC -XX:NativeMemoryTracking=summary -Xms64M -Xmx64M -Xss256k -XX:MaxMetaspaceSize=128M -XX:MetaspaceSize=64M -Xint -Deureka.client.serviceUrl.defaultZone=http://10.32.0.21:8761/eureka/ -jar target/user-kernel-server-0.0.1-SNAPSHOT.jar

SpringBoot Jar包 59MB

 

运行完10000条请求后内存占用:

Class: 68 MB
JavaHeap: 64 MB
Symbol: 18 MB
Thread: 14 MB
NativeMemoryTracking: 3 MB
Internal: 3 MB
Code: 3 MB
Total: 173 MB [ Heap= 64 , Thread= 14 Class= 86 , Other: 9 ]

 

2.3 功能简单的Spring Cloud微服务,使用Serial垃圾回收,64MB Heap内存,执行10000个请求后:220MB

Class: 75 MB
JavaHeap: 64 MB
Code: 41 MB
Thread: 18 MB
Symbol: 18 MB
NativeMemoryTracking: 3 MB
Internal: 3 MB

Total: 222 MB [ Heap= 64 , Thread= 18 Class= 93 , Other: 47 ]

命令行:java -XX:+UseSerialGC -XX:NativeMemoryTracking=summary -Xms64M -Xmx64M -Xss256k -Deureka.client.serviceUrl.defaultZone=http://10.32.0.21:8761/eureka/ -jar target/user-kernel-server-0.0.1-SNAPSHOT.jar

2.4 功能简单的Spring Cloud微服务,使用Serial垃圾回收,256MB Heap内存,执行10000个请求后:430MB

JavaHeap: 256 MB
Class: 74 MB
Code: 42 MB
Internal: 19 MB
Thread: 18 MB
Symbol: 18 MB
NativeMemoryTracking: 3 MB
GC: 1 MB
Total: 431 MB [ Heap= 256 , Thread= 18 Class= 92 , Other: 65 ]

命令行:java -XX:+UseSerialGC -XX:NativeMemoryTracking=summary -Xms256M -Xmx256M -Xss256k -Deureka.client.serviceUrl.defaultZone=http://10.32.0.21:8761/eureka/ -jar target/user-kernel-server-0.0.1-SNAPSHOT.jar

 

2.5 功能简单的Spring Cloud微服务,使用G1垃圾回收,256MB Heap内存,执行10000个请求后:510MB

JavaHeap: 256 MB
Class: 74 MB
GC: 59 MB
Code: 42 MB
Thread: 38 MB
Internal: 20 MB
Symbol: 18 MB
NativeMemoryTracking: 3 MB
Unknown: 0 MB
Total: 510 MB [ Heap= 256 , Thread= 38 Class= 92 , Other: 124 ]

 

三、微服务内存分配大致公式,以256MB Heap内存,Serial GC计算:

heap + 最大thread_num * 256k + jar_package_size * 3 + GC(Serial 0, CMS/G1 heap * 10%)

如:64MB heap, 60线程, jar包60MB,占用内存大约为 64 + 60 * 256k + 60 * 2 = 199 MB 可以正常动行,一般不会超过260 MB

      512MB heap, 300线程,jar包120MB,占用内丰大约为 512 + 300 * 0.25MB + 120 * 2 = 830MB,一般不会超过950MB

 

使用策略

1.1

Heap

1.2

Thread

1.3

Class

1.4

Symbol

1.5

GC

1.6

Code

1.7

Internal

1.8

Direct Buffer

1.9

Native Library

总计

使用策略

1.1

Heap

1.2

Thread

1.3

Class

1.4

Symbol

1.5

GC

1.6

Code

1.7

Internal

1.8

Direct Buffer

1.9

Native Library

总计

JDK1.8 分配方法

-Xmx256M

默认为机器内存的1/4

最小2MB

-Xss256k

默认为1M

最小160k

参考Jar包大小

加上JVM系统中必须的Jar包。

同1.3。

-XX:+UseSerialGC

-XX:+UseG1GC

动态增长,默认与GC有关,SerialGC接近0。

-Xint

-Xmixed

动态增长,与Jar包大小相关。

动态增长,与Jar包大小相关。默认没有使用,大小为0。默认没有使用,大小为0。 
2.1 简单Java程序2MB3MB5MB1.7MB 2.5MB   15MB
2.2 微服务64MB11MB62MB17MB 3MB2MB  162MB
2.3 微服务64MB18MB75MB18MB 41MB3MB  222MB
2.4 微服务 256MB18MB74MB18MB 42MB19MB  431MB
2.5 微服务+G1256MB38MB74MB18MB59MB42MB20MB  510MB
2.6 微服务+G11024MB77MB80MB18MB81MB60MB53MB  1398MB

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐