一、 进程内存统计

cat /proc/[pid]/status
通过/proc/[pid]/status可以查看进程的内存使用情况,包括虚拟内存大小(VmSize),物理内存大小(VmRSS),数据段大小(VmData),栈的大小(VmStk),代码段的大小(VmExe),共享库的代码段大小(VmLib)等等。

* Name: java /*进程的程序名*/
* State: S (sleeping) /*进程的状态信息,具体参见*/
* Tgid: 9744 /*线程组号*/
* Pid: 9744 /*进程pid*/
* PPid: 7672 /*父进程的pid*/
* TracerPid: 0 /*跟踪进程的pid*/
* VmPeak: 60184 kB /*进程地址空间的大小*/
* VmSize: 60180 kB /*进程虚拟地址空间的大小reserved_vm:进程在预留或特殊的内存间的物理页*/
* VmLck: 0 kB /*进程已经锁住的物理内存的大小.锁住的物理内存不能交换到硬盘*/
* VmHWM: 18020 kB /*文件内存映射和匿名内存映射的大小*/
* VmRSS: 18020 kB /*应用程序正在使用的物理内存的大小,就是用ps命令的参数rss的值 (rss)*/
* VmData: 12240 kB /*程序数据段的大小(所占虚拟内存的大小),存放初始化了的数据*/
* VmStk: 84 kB /*进程在用户态的栈的大小*/
* VmExe: 576 kB /*程序所拥有的可执行虚拟内存的大小,代码段,不包括任务使用的库 */
* VmLib: 21072 kB /*被映像到任务的虚拟内存空间的库的大小*/
* VmPTE: 56 kB /*该进程的所有页表的大小*/
* Threads: 1 /*共享使用该信号描述符的任务的个数*/

二、JVM 内存分配

java内存组成介绍:堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。” “在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。

可以看出JVM主要管理两种类型的内存:堆和非堆。

简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的。

所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中。

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

  2. NIO 的 DirectBuffer 是分配的 native memory

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

  4. 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

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

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

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

1. 方法区

也称”永久代” 、“非堆”,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为 16 MB,最大值为 64 MB,可以通过-XX: PermSize 和 -XX: MaxPermSize 参数限制方法区的大小。
运行时常量池:是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

2. 虚拟机栈

描述的是java 方法执行的内存模型:每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。

每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。声明周期与线程相同,是线程私有的。

局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

3. 本地方法栈

与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。

4. 堆

也叫做java 堆、GC堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。

该内存区域存放了对象实例及数组(所有 new 的对象)。其大小通过 -Xms (最小值) 和 -Xmx (最大值) 参数设置,-Xms为 JVM 启动时申请的最小内存,默认为操作系统物理内存的 1/64 但小于 1G;

-Xmx 为 JVM 可申请的最大内存,默认为物理内存的1/4但小于 1G,默认当空余堆内存小于 40% 时,JVM 会增大 Heap 到 -Xmx 指定的大小,可通过 -XX:MinHeapFreeRation= 来指定这个比列;

当空余堆内存大于70%时,JVM 会减小 heap 的大小到 -Xms 指定的大小,可通过XX:MaxHeapFreeRation= 来指定这个比列,对于运行系统,为避免在运行时频繁调整 Heap 的大小,通常 -Xms 与 -Xmx 的值设成一样。

由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代主要存储新创建的对象和尚未进入老年代的对象。老年代存储经过多次新生代GC(Minor GC)任然存活的对象。

5. 程序计数器

是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。

三、直接内存

直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。

四、JVM 内存分析

1. 查看 JVM 堆内存情况

[root@server ~]$ jmap -heap 837
Attaching to process ID 837, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆内存初始化配置
  MinHeapFreeRatio = 0 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40)
  MaxHeapFreeRatio = 100 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70)
  MaxHeapSize      = 2082471936 (1986.0MB) //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小
  NewSize          = 1310720 (1.25MB)//对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小
  MaxNewSize       = 17592186044415    MB//对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小
  OldSize          = 5439488 (5.1875MB)//对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小
  NewRatio         = 2 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
  SurvivorRatio    = 8 //对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值 
  PermSize         = 21757952 (20.75MB)  //对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小
  MaxPermSize      = 85983232 (82.0MB)//对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小
  G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆内存使用情况
PS Young Generation
Eden Space://Eden区内存分布
  capacity = 33030144 (31.5MB)//Eden区总容量
  used     = 1524040 (1.4534378051757812MB)  //Eden区已使用
  free     = 31506104 (30.04656219482422MB)  //Eden区剩余容量
  4.614088270399305% used //Eden区使用比率
From Space:  //其中一个Survivor区的内存分布
  capacity = 5242880 (5.0MB)
  used     = 0 (0.0MB)
  free     = 5242880 (5.0MB)
  0.0% used
To Space:  //另一个Survivor区的内存分布
  capacity = 5242880 (5.0MB)
  used     = 0 (0.0MB)
  free     = 5242880 (5.0MB)
  0.0% used
PS Old Generation //当前的Old区内存分布
  capacity = 86507520 (82.5MB)
  used     = 0 (0.0MB)
  free     = 86507520 (82.5MB)
  0.0% used
PS Perm Generation//当前的 “永生代” 内存分布
  capacity = 22020096 (21.0MB)
  used     = 2496528 (2.3808746337890625MB)
  free     = 19523568 (18.619125366210938MB)
  11.337498256138392% used
670 interned Strings occupying 43720 bytes.

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

VSZ是指已分配的线性空间大小,这个大小通常并不等于程序实际用到的内存大小,产生这个的可能性很多,比如内存映射,共享的动态库,或者向系统申请了更多的堆,都会扩展线性空间大小,要查看一个进程有哪些内存映射,可以使用 pmap 命令来查看:
pmap -x [pid]

[root@server ~]$ 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

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

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

查看 JVM 堆各个分区的内存情况

jstat -gcutil [pid]

[root@server ~]$ 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

分析 JVM 堆内存中的对象

查看存活的对象统计

jmap -histo:live [pid]

dump 内存

jmap -dump:format=b,file=heapDump [pid]

然后用jhat命令可以参看

jhat -port 5000 heapDump
在浏览器中访问:http://localhost:5000/ 查看详细信息