xiaobaoqiu Blog

Think More, Code Less

Who Eats JVM's Memory

1.JVM内存占用上限

关于JVM的一些基础知识,可以看 JVM Internals,曾经我尝试翻译过,地址:http://xiaobaoqiu.github.io/blog/2014/09/11/internal-jvm/

这里要说的是,那些参数决定了JVM内存上限,比如常用的内存配置如下:

1
-Xms1024m -Xmx1024m -XX:PermSize=256m

说明堆(Heap)的大小1G,永久代大小256M.

另外,64位的操作系统(Linux服务器),线程的栈空间大小最大为1M:

1
2
3
4
5
6
7
[baoqiu.xiao@Xxx ...]$ java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
     intx CompilerThreadStackSize                   = 0               {pd product}        
     intx ThreadStackSize                           = 1024            {pd product}        
     intx VMThreadStackSize                         = 1024            {pd product}        
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)

JVM的占用上限由一下几个因素决定:

1. 堆内存
2. 持久代
3. 线程栈空间
4. 堆外内存

堆外内存参考: http://www.infoq.com/cn/news/2014/12/external-memory-heap-memory

线程数可以如下得到:

1
2
[baoqiu.xiao@Xxx ~]$ ps -m 1381 | wc -l
176

通常而言,我们只需要计算堆内存,持久代再加上线程栈空间.比如我们的本地一个小服务:

1
Max Memory = Heap(1G) + Perm(256M) + 176 * Thread stack (1M)

2.JVM RES数据来源

首先得知道top命令中RES数据从哪里来的.再Linux上,万物皆文件,因此RES的数据也是来源于文件.

2.1 /proc/pid内容

下面是一个典型的Tomcat应用的线程下的内容,进程号为1381,简单说描述每个文件的内容或者作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
attr         进程的属性
autogroup
auxv
cgroup
clear_refs
cmdline       启动进程时执行的命令
coredump_filter
cpuset
cwd           指向进程当前工作目录的软链
environ       进程执行时使用的环境变量
exe           这个就是起这个进程的执行文件
fd            进程打开的文件描述符,可以知道具体的文件路径
fdinfo
io            进程的io统计信息
limits            进程的软限制,硬限制等信息
loginuid
maps          进程相关的内存映射信息
mem           代进程持有的内存,不可读
mountinfo
mounts
mountstats
net
numa_maps
oom_adj       调节oom-killer的参数
oom_score         oom-killer打分,当需要是,oom-killer会根据各个进程的分数,kill掉某个进程
oom_score_adj         调节oom-killer的参数
pagemap       进程的虚拟页和物理内存页或者swap区的映射关系
personality
root          指向进程根目录的软链
sched
schedstat
sessionid
smaps             This file shows memory consumption for each of the process's mappings.
stack             This file provides a symbolic trace of the function calls in this process's kernel stack
stat          进程的状态
statm         进程使用的内存的状态
status            进程状态信息,比stat/statm更具可读性
syscall
task          进程包含的线程,子目录名是线程的ID
wchan

参考: http://man7.org/linux/man-pages/man5/proc.5.html

2.2 /proc/pid下内存相关文件

和内存相关的文件主要包括(解析各个文件的时间不同,可能内容上存在对不上的情况):

1. statm: 
2. stat:    
3. status
4. maps
5. smaps

1. statm

进程的内存使用,注意单位是page,内容的解析参考2.3节的内容.

2. stat

1381这个进程的stat文件及各个字段的含义(含义参见proc命令手册: man proc –> 搜stat, 见/proc/[pid]/stat):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1381 pid             进程号
(java)    comm            应用程序或命令的名字
S state           任务的状态(RSDZTW中一个字符),R:runnign, S:sleeping in an interruptible wait, D:waiting in uninterruptible disk sleep, Z:zombie, T is  traced  or  stopped, W is paging
1 ppid            父进程ID
1374  pgrp            线程组号
1374  session         该任务所在的会话组ID
0 tty_nr          进程的控制终端设备号
-1    tpgid           进程控制终端的前台任务id
4202496   flags           进程内核标志位
988135    minflt          任务不需要从硬盘拷数据而发生的缺页(minor faults)的次数
191   cminflt         任务的所有的waited-for子进程曾经发生的次缺页的次数累计值
252944    majflt          任务需要从硬盘拷数据而发生的缺页(minor faults)的次数
5 cmajflt             任务的所有的waited-for子进程需要从硬盘拷数据而发生的缺页(minor faults)的次数
229942    utime           任务在用户态(User mode)运行的时间,单位为时钟周期(clock ticks)
102219    stime           任务在内核态(kernel mode)运行的时间,单位为时钟周期(clock ticks)
0 cutime          任务的所有的waited-for子进程曾经在用户态运行的时间累计值,单位为时钟周期(clock ticks)
0 cstime          任务的所有的waited-for子进程曾经在核心态运行的时间累计值,单位为时钟周期(clock ticks)
20    priority            任务的动态优先级
0 nice            任务的静态优先级
179   num_threads     进程的线程数
0 itrealvalue     由于计时间隔导致的下一个 SIGALRM 发送进程的时延,单位jiffies
2107287914    starttime       任务启动的时间,单位为jiffies
3968053248    vsize           该任务的虚拟地址空间大小,单位byte
122542    rss             任务当前驻留物理地址空间的大小,单位page
18446744073709551615  rsslim          任务能驻留物理地址空间的最大值,单位byte
4194304   startcode       任务在虚拟地址空间的代码段的起始地址
4196452   endcode     任务在虚拟地址空间的代码段的结束地址
140734635670848   startstack      任务在虚拟地址空间的栈的结束地址
140734635653408   kstkesp         ESP(stack pointer, 栈指针)的当前值
250460799149  kstkeip         EIP(instruction pointer, 指令指针)的当前值
0 signal          pending信号的位图(bitmap),十进制数显示
0 blocked         blocked信号的位图(bitmap),十进制数显示
2 sigignore       ignored信号的位图(bitmap),十进制数显示
16800973  sigcatch        caught信号的位图(bitmap),十进制数显示
18446744073709551615  wchan           进程等待的channel.是系统调用地址
0 nswap           被swapped的页数(当前不用)
0 cnswap      所有子进程被swapped的页数的和(当前不用)
17    exit_signal     该进程结束时,向父进程所发送的信号
3 processor       进程最后一次执行的CPU号
0 rt_priority     实时调度优先级
0 policy          调度策略
4 delayacct_blkio_ticks   IO阻塞延迟汇总,单位时钟周期(clock ticks)
0 guest_time      进程的guest time(指费再运行guest操作系统上的时间),单位时钟周期(clock ticks)
0 cguest_time     子进程guest time

里面包含我们熟悉的RSS数据,注意其单位是page.

3. status

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[baoqiu.xiao@Xxx /proc/1381]$ sudo cat status
Name: Java                                        /*当前进程的命令*/
State:    S (sleeping)                           /*当前进程状态: "R (running)", "S (sleeping)", "D (disk sleep)", "T (stopped)", "T (tracing stop)", "Z (zombie)", or "X (dead)"*/
Tgid: 1381                                      /*线程组号*/
Pid:  1381                                             /*线程id*/
PPid: 1                                            /*父进程的pid*/
TracerPid:    0                                     /*跟踪进程的pid,如果没有就是0*/
Uid:  40001   40001   40001   40001      /*uid euid suid fsuid*/
Gid:  40001   40001   40001   40001      /*gid egid sgid fsgid*/
Utrace:   0
FDSize:   512                                        /*FDSize是当前分配的文件描述符*/
Groups:   40001                             /*这里的groups表示启动这个进程的用户所在的组*/
VmPeak:    3880116 kB                   /*当前进程运行过程中占用内存的峰值*/
VmSize:    3875052 kB                         /*进程现在正在占用的内存*/
VmLck:           0 kB                               /*进程已经锁住的物理内存的大小.锁住的物理内存不能交换到硬盘*/
VmHWM:      766000 kB                    /*程序得到分配到物理内存的峰值*/
VmRSS:      490168 kB                          /*程序现在使用的物理内存*/
VmData:    3718132 kB                   /*进程数据段的大小*/
VmStk:          88 kB                              /*进程堆栈段的大小*/
VmExe:           4 kB                              /*进程代码的大小*/
VmLib:       15720 kB                          /*进程所使用LIB库的大小*/
VmPTE:        2280 kB                           /*进程所使用LIB库的大小*/
VmSwap:     303616 kB                   /*进程占用Swap的大小*/
Threads:  179                                /*当前进程组线程数*/
SigQ: 0/62812                               /*表示当前待处理信号的个数*/
SigPnd:   0000000000000000             /*屏蔽位,存储了该线程的待处理信号,等同于线程的PENDING信号*/
ShdPnd:   0000000000000000      /*屏蔽位,存储了该线程组的待处理信号.等同于进程组的PENDING信号*/
SigBlk:   0000000000000000             /*放被阻塞的信号,等同于BLOCKED信号*/
SigIgn:   0000000000000002            /*存放被忽略的信号,等同于IGNORED信号*/
SigCgt:   2000000181005ccd            /*存放捕获的信号,等同于CAUGHT信号*/
CapInh:   0000000000000000
CapPrm:   0000000000000000
CapEff:   0000000000000000
CapBnd:   ffffffffffffffff
Cpus_allowed: f
Cpus_allowed_list:    0-3
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:    0
voluntary_ctxt_switches:  35
nonvoluntary_ctxt_switches:   9

关于其中的TracerPid,在Linux下,我们可以使用strace来跟踪命令的运行.

参考: http://blog.csdn.net/zjl410091917/article/details/8075691

4. maps

maps内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[baoqiu.xiao@l-crm3.des.dev.cn0 ~]$ head -30 maps
00400000-00401000 r-xp 00000000 fc:07 1179664                            /home/q/java/jdk1.7.0_45/bin/java
00600000-00601000 rw-p 00000000 fc:07 1179664                            /home/q/java/jdk1.7.0_45/bin/java
00b78000-00bed000 rw-p 00000000 00:00 0                                  [heap]
aff80000-100000000 rw-p 00000000 00:00 0 
3a4fe00000-3a4fe20000 r-xp 00000000 fc:02 2613                           /lib64/ld-2.12.so
3a5001f000-3a50020000 r--p 0001f000 fc:02 2613                           /lib64/ld-2.12.so
3a50020000-3a50021000 rw-p 00020000 fc:02 2613                           /lib64/ld-2.12.so
3a50021000-3a50022000 rw-p 00000000 00:00 0 
3a50200000-3a5038a000 r-xp 00000000 fc:02 25297                          /lib64/libc-2.12.so
3a5038a000-3a50589000 ---p 0018a000 fc:02 25297                          /lib64/libc-2.12.so
3a50589000-3a5058d000 r--p 00189000 fc:02 25297                          /lib64/libc-2.12.so
3a5058d000-3a5058e000 rw-p 0018d000 fc:02 25297                          /lib64/libc-2.12.so

第一列代表内存段的虚拟地址 第二列代表执行权限虚拟内存的权限,r=读,w=写,x=,s=共享,p=私有 第三列代表在进程地址里的偏移量 第四列映射文件的主设备号和次设备号 第五列映像文件的节点号,即inode 第六列是映像文件的路径

参考: http://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps

5. smaps

smaps代表进程的各个线程的内存消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[baoqiu.xiao@Xxx /proc/1381]$ sudo cat smaps
在smaps文件中,每一条记录(每块)表示进程虚拟内存空间中一块连续的区域.

...
00b78000-00bed000 rw-p 00000000 00:00 0                                  [heap]       /*和maps中的内容相同*/
Size:                468 kB                   /*表示该映射区域在虚拟内存空间中的大小*/
Rss:                  76 kB                    /*表示该映射区域当前在物理内存中实际占用了多少空间*/
Pss:                  76 kB                    /*该虚拟内存区域平摊计算后使用的物理内存大小*/
Shared_Clean:          0 kB             /*和其他进程共享的未被改写的page的大小*/
Shared_Dirty:          0 kB              /*和其他进程共享的被改写的page的大小*/
Private_Clean:         0 kB              /*未被改写的私有页面的大小*/
Private_Dirty:        76 kB
Referenced:           76 kB
Anonymous:            76 kB
AnonHugePages:         0 kB
Swap:                144 kB                 /*由于物理内存不足被swap到交换空间的大小*/
KernelPageSize:        4 kB             /*操作系统一个页面大小*/
MMUPageSize:           4 kB           /*CPU页面大小, MMU表示内存管理单元(Memory Management Unit)*/
...

关于其中的Pss,因为进程之间存在内存共享,比如该区域所映射的物理内存部分同时也被另一个进程映射了,且该部分物理内存的大小为1000KB(Rss会是1000KB),那么该进程分摊其中一半的内存,即Pss=500KB。

内存页引用计数>1,则表示是shared,否则是private.

参考: stackoverflow: what does pss mean in /proc/pid/smaps

Getting information about a process' memory usage from /proc/pid/smaps

另外,当前系统的page大小可以通过getconf命令获取,比如我当前的服务器page大小为4k:

1
2
[baoqiu.xiao@Xxx ~]$ getconf PAGESIZE
4096

2.3 top命令RES数据来源

top命令RES数据来源于/proc/PID/statm中的第二个字段.还是1381这个进程的statm文件内容和对应的top信息如下:

1
2
3
4
5
6
[baoqiu.xiao@Xxx /proc/1381]$ sudo cat statm
968763 122480 558 1 0 929555 0

[baoqiu.xiao@Xxx ~]$ top -p 1381
  PID  USER      PR   NI  VIRT     RES     SHR  S  %CPU  %MEM    TIME+       COMMAND                                                                                                                             
 1381 tomcat    20   0   3784m  478m 2228  S  2.0       6.1           54:53.36    java

/proc/[pid]/statm文件的几个字段的具体意义如下(注意单位是page):

1
2
3
4
5
6
7
size         (1) total program size(same as VmSize in /proc/[pid]/status)
resident      (2) (resident set size)(same as VmRSS in /proc/[pid]/status),即RSS
share         (3) shared pages (i.e., backed by a file)
text          (4) text (code)
lib               (5) library (unused in Linux 2.6)
data          (6) data + stack
dt                (7) dirty pages (unused in Linux 2.6)

可以验证,1381这个进程的RES大小来源, 和top命令中RES的数字是吻合的:

1
122480 * 4096 / (1024 * 1024) = 478.4375 MB

3.谁吃了JVM内存

写到这发现谢步下去了,因为我一开始就写错了.

想知道谁吃了我们JVM的内存,可以jmap dump下来,用MAT分析一下.

想知道谁吃了服务器的内存,建议看看参考里面的Linux Used内存到底哪里去了这篇文章.

参考: Linux Memory Management Overview

Understanding Process memory

How much memory are applications really using

Memory Management

Linux Used内存到底哪里去了

也看linux内存去哪儿了