java 进程占用系统内存过高分析

您所在的位置:网站首页 查看java程序内存占用多少内存 java 进程占用系统内存过高分析

java 进程占用系统内存过高分析

2024-07-15 10:46| 来源: 网络整理| 查看: 265

JVM的内存

先放一张JVM的内存划分图,总体上可以分为堆和非堆(粗略划分,基于java8)

那么一个Java进程最大占用的物理内存为:

Max Memory = eden + survivor + old + String Constant Pool + Code cache + compressed class space + Metaspace + Thread stack(*thread num) + Direct + Mapped + JVM + Native Memory 堆和非堆内存

堆和非堆内存有以下几个概念:

init

表示JVM在启动时从操作系统申请内存管理的初始内存大小(以字节为单位)。JVM可能从操作系统请求额外的内存,也可以随着时间的推移向操作系统释放内存(经实际测试,这个内存并没有过主动释放)。这个init的值可能不会定义。

used

表示当前使用的内存量(以字节为单位)

committed

表示保证可供 Jvm使用的内存大小(以字节为单位)。 已提交内存的大小可能随时间而变化(增加或减少)。 JVM也可能向系统释放内存,导致已提交的内存可能小于 init,但是committed永远会大于等于used。

max

表示可用于内存管理的最大内存(以字节为单位)。

NMT追踪内存

NMT(Native Memory tracking)是一种Java HotSpot VM功能,可跟踪Java HotSpot VM的内部内存使用情况(jdk8+)。

本文简单介绍下该工具的使用,主要用来解释Java中的内存

开启

在启动参数中添加-XX:NativeMemoryTracking=detail

查看

jcmd 进程id VM.native_memory summary scale=MB

输出结果

Native Memory Tracking: Total: reserved=6988749KB, committed=3692013KB 堆内存 - Java Heap (reserved=5242880KB, committed=3205008KB) (mmap: reserved=5242880KB, committed=3205008KB) 类加载信息 - Class (reserved=1114618KB, committed=74642KB) (classes #10657) (malloc=4602KB #32974) (mmap: reserved=1110016KB, committed=70040KB) 线程栈 - Thread (reserved=255213KB, committed=255213KB) (thread #248) (stack: reserved=253916KB, committed=253916KB) (malloc=816KB #1242) (arena=481KB #494) 代码缓存 - Code (reserved=257475KB, committed=46551KB) (malloc=7875KB #10417) (mmap: reserved=249600KB, committed=38676KB) 垃圾回收 - GC (reserved=31524KB, committed=23560KB) (malloc=17180KB #2113) (mmap: reserved=14344KB, committed=6380KB) 编译器 - Compiler (reserved=598KB, committed=598KB) (malloc=467KB #1305) (arena=131KB #3) 内部 - Internal (reserved=6142KB, committed=6142KB) (malloc=6110KB #23691) (mmap: reserved=32KB, committed=32KB) 符号 - Symbol (reserved=11269KB, committed=11269KB) (malloc=8544KB #89873) (arena=2725KB #1) nmt - Native Memory Tracking (reserved=2781KB, committed=2781KB) (malloc=199KB #3036) (tracking overhead=2582KB) - Arena Chunk (reserved=194KB, committed=194KB) (malloc=194KB) - Unknown (reserved=66056KB, committed=66056KB) (mmap: reserved=66056KB, committed=66056KB)

nmt返回结果中有reserved和committed两个值,这里解释一下:

reserved

reserved memory 是指JVM 通过mmaped PROT_NONE 申请的虚拟地址空间,在页表中已经存在了记录(entries),保证了其他进程不会被占用。

在堆内存下,就是xmx值,jvm申请的最大保留内存。

committed

committed memory 是JVM向操做系统实际分配的内存(malloc/mmap),mmaped PROT_READ | PROT_WRITE,相当于程序实际申请的可用内存。

在堆内存下,就是xms值,最小堆内存,heap committed memory。

注意,committed申请的内存并不是说直接占用了物理内存,由于操作系统的内存管理是惰性的,对于已申请的内存虽然会分配地址空间,但并不会直接占用物理内存,真正使用的时候才会映射到实际的物理内存。所以committed > res也是很可能的

Linux内存与JVM内存

再来说说JVM内存与该进程的内存。

现在有一个Java进程,JVM所有已使用内存区域加起来才2G(不包括Native Memory,也没有显式调用JNI的地方),但从top/pmap上看该进程res已经2.9G了

#heap + noheap Memory used total max usage heap 1921M 2822M 4812M 39.93% par_eden_space 1879M 2457M 2457M 76.47% par_survivor_space 4M 307M 307M 1.56% cms_old_gen 37M 57M 2048M 1.84% nonheap 103M 121M -1 85.00% code_cache 31M 37M 240M 13.18% metaspace 63M 74M -1 85.51% compressed_class_space 7M 9M 1024M 0.75% direct 997K 997K - 100.00 mapped 0K 0K - NaN%

#top top -p 6267 top - 17:39:40 up 140 days, 5:39, 5 users, load average: 0.00, 0.01, 0.00 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie Cpu(s): 0.2%us, 0.1%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 8059152k total, 5255384k used, 2803768k free, 148872k buffers Swap: 0k total, 0k used, 0k free, 1151812k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6267 root 20 0 8930m 2.9g 17m S 0.0 37.6 4:13.31 java

那么其余的0.9G内存去哪了呢?

这时候就要介绍下JVM与Linux内存的联系了

当Java程序启动后,会根据Xmx为堆预申请一块保留内存,并不会直接使用,也不会占用物理内存

然后申请(malloc之类的方法)Xms大小的虚拟内存,但是由于操作系统的内存管理是惰性的,有一个内存延迟分配的概念。malloc虽然会分配内存地址空间,但是并没有映射到实际的物理内存,只有当对该地址空间赋值时,才会真正的占用物理内存,才会影响RES的大小。

所以可能会出现进程所用内存大于当前堆+非堆的情况。

比如说该Java程序在5分钟前,有一定活动,占用了2.6G堆内存(无论堆中的什么代),经过GC之后,虽然堆内存已经被回收了,堆占用很低,但GC的回收只是针对Jvm申请的这块内存区域,并不会调用操作系统释放内存。所以该进程的内存并不会释放,这时就会出现进程内存远远大于堆+非堆的情况。

至于Oracle文档上说的,Jvm可能会向操作系统释放内存,经过测试没有发现释放的情况。不过就算有主动释放的情况,也不太需要我们程序关心了。

RES(Resident Set Size)是常驻内存的意思,进程实际使用的物理内存

私信回复“资料”获取面试宝典《Java核心知识点整理.pdf》“,覆盖了JVM、锁、高并发、反射、Spring原理

jmap命令

首先看一下一个java进程的jmap输出:

 代码如下

[lex@chou ~]$ jmap -heap 837 Attaching to process ID 837, please wait... Debugger attached successfully. Server compiler detected. JVM version is 20.10-b01

using thread-local object allocation. Parallel GC with 2 thread(s)

Heap Configuration:    MinHeapFreeRatio = 40    MaxHeapFreeRatio = 70    MaxHeapSize      = 4294967296 (4096.0MB)    NewSize          = 1310720 (1.25MB)    MaxNewSize       = 17592186044415 MB    OldSize          = 5439488 (5.1875MB)    NewRatio         = 2    SurvivorRatio    = 8    PermSize         = 21757952 (20.75MB)    MaxPermSize      = 85983232 (82.0MB)

Heap Usage: PS Young Generation Eden Space:    capacity = 41025536 (39.125MB)    used     = 18413552 (17.560531616210938MB)    free     = 22611984 (21.564468383789062MB)    44.883147900858624% used From Space:    capacity = 4325376 (4.125MB)    used     = 3702784 (3.53125MB)    free     = 622592 (0.59375MB)    85.60606060606061% used To Space:    capacity = 4521984 (4.3125MB)    used     = 0 (0.0MB)    free     = 4521984 (4.3125MB)    0.0% used PS Old Generation    capacity = 539820032 (514.8125MB)    used     = 108786168 (103.74657440185547MB)    free     = 431033864 (411.06592559814453MB)    20.152302906758376% used PS Perm Generation    capacity = 85983232 (82.0MB)    used     = 60770232 (57.95500946044922MB)    free     = 25213000 (24.04499053955078MB)    70.67684080542588% used

ps命令  

然后再用ps看看:

 代码如下[lex@chou ~]$ ps -p 837 -o vsz,rss    VSZ   RSS 7794992 3047320

  关于这里的几个generation网上资料一大把就不细说了,这里算一下求和可以得知前者总共给Java环境分配了644M的内存,而ps输出的VSZ和RSS分别是7.4G和2.9G,这到底是怎么回事呢?

  前面jmap输出的内容里,MaxHeapSize 是在命令行上配的,-Xmx4096m,这个java程序可以用到的最大堆内存。

  VSZ是指已分配的线性空间大小,这个大小通常并不等于程序实际用到的内存大小,产生这个的可能性很多,比如内存映射,共享的动态库,或者向系统申请了更多的堆,都会扩展线性空间大小

pmap命令

要查看一个进程有哪些内存映射,可以使用 pmap 命令来查看:

 代码如下[lex@chou ~]$ pmap -x 837 837:   java Address           Kbytes     RSS   Dirty Mode   Mapping 0000000040000000      36       4       0 r-x--  java 0000000040108000       8       8       8 rwx--  java 00000000418c9000   13676   13676   13676 rwx--    [ anon ] 00000006fae00000   83968   83968   83968 rwx--    [ anon ] 0000000700000000  527168  451636  451636 rwx--    [ anon ] 00000007202d0000  127040       0       0 -----    [ anon ] ... ... 00007f55ee124000       4       4       0 r-xs-  az.png 00007fff017ff000       4       4       0 r-x--    [ anon ] ffffffffff600000       4       0       0 r-x--    [ anon ] ----------------  ------  ------  ------ total kB         7796020 3037264 3023928

  

       Address: 内存分配地址

       Kbytes:   实际分配的内存大小

       RSS:       程序实际占用的内存大小

       Mapping: 分配该内存的模块的名称

      这里可以看到很多anon,这些表示这块内存是由mmap分配的。

  RSZ是Resident Set Size,常驻内存大小,即进程实际占用的物理内存大小, 在现在这个例子当中,RSZ和实际堆内存占用差了2.3G,这2.3G的内存组成分别为:

  JVM本身需要的内存,包括其加载的第三方库以及这些库分配的内存

  NIO的DirectBuffer是分配的native memory

  内存映射文件,包括JVM加载的一些JAR和第三方库,以及程序内部用到的。上面 pmap 输出的内容里,有一些静态文件所占用的大小不在Java的heap里,因此作为一个Web服务器,赶紧把静态文件从这个Web服务器中人移开吧,放到nginx或者CDN里去吧。

  JIT, JVM会将Class编译成native代码,这些内存也不会少,如果使用了Spring的AOP,CGLIB会生成更多的类,JIT的内存开销也会随之变大,而且Class本身JVM的GC会将其放到Perm Generation里去,很难被回收掉,面对这种情况,应该让JVM使用ConcurrentMarkSweep GC,并启用这个GC的相关参数允许将不使用的class从Perm Generation中移除, 参数配置: -XX:+UseConcMarkSweepGC -X:+CMSPermGenSweepingEnabled -X:+CMSClassUnloadingEnabled,如果不需要移除而Perm Generation空间不够,可以加大一点: -X:PermSize=256M -X:MaxPermSize=512M

  JNI,一些JNI接口调用的native库也会分配一些内存,如果遇到JNI库的内存泄露,可以使用valgrind等内存泄露工具来检测

  线程栈,每个线程都会有自己的栈空间,如果线程一多,这个的开销就很明显了

  jmap/jstack 采样,频繁的采样也会增加内存占用,如果你有服务器健康监控,记得这个频率别太高,否则健康监控变成致病监控了。

jstat命令

  关于JVM的几个GC堆和GC的情况,可以用jstat来监控,例如监控进程837每隔1000毫秒刷新一次,输出20次:

 代码如下[lex@chou ~]$ jstat -gcutil 837 1000 20   S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT      0.00  80.43  24.62  87.44  98.29   7101  119.652    40   19.719  139.371   0.00  80.43  33.14  87.44  98.29   7101  119.652    40   19.719  139.371

  几个字段分别含义如下:

  S0

  年轻代中第一个survivor(幸存区)已使用的占当前容量百分比

  S1

  年轻代中第二个survivor(幸存区)已使用的占当前容量百分比

  E

  年轻代中Eden(伊甸园)已使用的占当前容量百分比

  O

  old代已使用的占当前容量百分比

  P

  perm代已使用的占当前容量百分比

  YGC

  从应用程序启动到采样时年轻代中gc次数

  YGCT

  从应用程序启动到采样时年轻代中gc所用时间(s)

  FGC

  从应用程序启动到采样时old代(全gc)gc次数

  FGCT

  从应用程序启动到采样时old代(全gc)gc所用时间(s)

  GCT

  从应用程序启动到采样时gc用的总时间(s)

结论

  因此如果正常情况下jmap输出的内存占用远小于 RSZ,可以不用太担心,除非发生一些严重错误,比如PermGen空间满了导致OutOfMemoryError发生,或者RSZ太高导致引起系统公愤被OOM Killer给干掉,就得注意了,该加内存加内存,没钱买内存加交换空间,或者按上面列的组成部分逐一排除。

  这几个内存指标之间的关系是:VSZ >> RSZ >> Java程序实际使用的堆大小



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3