Java微服务内存占用分析
一、Java 进程中有哪些组件会占用内存?通过 Native Memory Tracking 可以观察到有以下 JVM 组件。在命令行加上-XX:NativeMemoryTracking=summary,会增加3MB左右内存,损失5%-10%CPU,不宜用于生产环境。使用 jcmd <pid> VM.native_memory summary scale=MB 可以查看内存分配...
一、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程序 | 2MB | 3MB | 5MB | 1.7MB | 2.5MB | 15MB | ||||
2.2 微服务 | 64MB | 11MB | 62MB | 17MB | 3MB | 2MB | 162MB | |||
2.3 微服务 | 64MB | 18MB | 75MB | 18MB | 41MB | 3MB | 222MB | |||
2.4 微服务 | 256MB | 18MB | 74MB | 18MB | 42MB | 19MB | 431MB | |||
2.5 微服务+G1 | 256MB | 38MB | 74MB | 18MB | 59MB | 42MB | 20MB | 510MB | ||
2.6 微服务+G1 | 1024MB | 77MB | 80MB | 18MB | 81MB | 60MB | 53MB | 1398MB |
更多推荐
所有评论(0)