内存消耗分析

前言

内存消耗分析请先了解java虚拟机
https://blog.csdn.net/glamour2015/article/details/104294620
JVM垃圾回收与内存调优
https://blog.csdn.net/glamour2015/article/details/104241942
常见OOM异常分析
https://blog.csdn.net/glamour2015/article/details/104833387

Java应用对于内存的使用包括两方面 JVM堆内存 和 JVM堆外内存。
JVM堆内存
Java应用对内存的消耗上主要是在JVM堆内存上。在正式环境中,多数Java应用都会将 -Xms 和 -Xmx设为相同的值,避免运行期要不断申请内存。
JVM堆外内存
目前Java应用只有在创建线程和使用Direct ByteBuffer 时才会操作JVM堆外的内存JVM。对于JVM堆以外的内存方面的消耗,最为值得关注的是swap的消耗 以及 物理内存的消耗,这两方面的消耗都可基于操作系统的命令来查看。

关于java应用的内存,通常只需要关注jvm内存,但有些特殊情况也需要关注物理内存。关于jvm内存,常见的工具有jstat, jmap, pidstat, vmstat, top

JVM内存消耗过多会导致GC执行频繁,CPU消耗增加,应用线程的执行速度严重下降,甚至造成OutOfMemoryError,最终导致Java进程退出。

Linux内存消耗分析工具介绍

在Linux中可通过vmstat、sar、top、pidstat, pmap等方式来查看 swap和物理内存的消耗状况。

** vmstat**

在命令行中输入vmstat,其中的信息和内存相关的主要是memory下的swpd、free、buff、cache以及swap下的si 和 so.

在这里插入图片描述
其中swpd是指虚拟内存已使用的部分,单位为kb;free表示空闲的物理内存;buffer表示用于缓冲的内存;cache表示用于作为缓存的内存
swap下的si是指每秒从disk读至内存的数据量;so是指每秒从内存中写入disk的数据量。

swpd值过高通常是由于物理内存不够用,操作系统将物理内存中的一部分数据转为放入硬盘上进行存储,以腾出足够的空间给当前运行的程序使用。

在目前运行的程序变化后,即从硬盘上重新读取数据到内存中,以便恢复程序的运行,这个过程会产生swap IO, 因此看swap的消耗情况主要关注的是swapIO的状况,如swapIO发生得较频繁,那么严重影响系统的性能。

由于Java应用是单进程应用,因此只要JVM的内存设置不是过大,是不会操作到swap区域的。物理内存消耗过高可能是由于JVM内存设置过大、创建的Java线程过多或通过Direct ByteBuffer往物理内存中放置了过多的对象造成的。

sar

通过sar的-r参数可查看内存的消耗状况
在这里插入图片描述
图-16

物理内存相关的信息主要是kbmemfree、kbmemused、%memused、kbbuffers、kbcached, 当物理内存有空闲时,Linux会使用一些物理内存用于buffer和cache, 以提升系统的运行效率,因此可以认为系统中可用的物理内存为: kbmemfree + kbmemused + kbcached.

sar相比vmstat的好处是可以查询历史状况,以更加准确地分析趋势状况,例如 sar -r -f /temp/log/sa/sa12

vmstat 和 sar的共同弱点是不能分析进程所占用的内存量。

top

通过top可查看进程所消耗的内存量,不过top中看到的Java进程消耗的内存。因为Java进程是包括了JVM已分配的内存加上Java应用所耗费的JVM以外的物理内存,这会导致top中看到Java进程所消耗的内存大小有可能超过 -Xmx 加上 -XX:MaxPermSize设置的内存大小,并且Java程序在启动后也只是占据了-Xms的地址空间,但并没有占据实际的内存,只有在相应的地址空间被使用过后才被计入消耗的内存中。因此纯粹的根据top很难判断出Java进程消耗的内存中有多少是属于JVM的,有多少是属于JVM外的内存。

一个小技巧是对由于内存满而发生过Full GC的应用而言(不是主动调用System.gc的应用),多数情况下(例如由于产生的对象过大导致执行Full GC并抛出OutOfMemoryError的现象就要出外)可以认为其Java进程中显示出来的内存消耗值即为 JVM -Xmx的值 + 消耗的JVM外的内存值。
在这里插入图片描述
图-17

pidstat

通过 pid也可以查看进程所消耗的内存量,命令格式为: pidstat -r -p [pid] [interval] [times], 例如如下:

在这里插入图片描述

查看该进程所占用的物理内存RSS(Resident Set Size)和虚拟内存的大小VSZ(virtual memory size)。

从以上的几个工具来看,最佳的内存消耗分析方法是结合top或pidstat,以及JVM的内存分析工具来共同分析内存消耗状况。

JVM内存消耗分析工具

对于JVM内存消耗状况分析的方法工具有:JVM(jmap、jstat、mat、visualvm等方法)
详情跳转 https://blog.csdn.net/glamour2015/article/details/104467876

下面通过例子分别展示Java应用对物理内存的消耗和对JVM堆内的消耗。

JVM堆外内存消耗分析

基于Direct ByteBuffer可以很容易地实现对物理内存的直接操作,而无须耗费JVM heap区。

例子中,为了更清晰地观察内存的变化情况,放入了多个Thread.sleep,加上 -Xms140 -Xmx140参数执行上面的代码,在执行过程中结合top命令和jstat命令查看java进程占用内存的大小以及JVM heap的变化情况。

[root@localhost ~]# jps
4661 jar
4670 Jps
[root@localhost ~]# pidstat -r -p 4661 1 100
jps找到java进程,jstat -gcutil [pid] 1000 10

结合上面top和jstat观察到的状态,可以查出 direct bytebuffer消耗的是JVM heap 外的物理内存。但它同样是基于GC方式来释放的,同时也可以看出JVM heap一旦使用后,即使进行了GC,进行中仍然会显示之前其所消耗的内存大小,因此JVM内存中具体的消耗状况必须通过JDK提供的命令才可以准确分析。

除了Direct Bytebuffer方式对JVM外物理内存的消耗外,创建线程也会消耗一定大小的内存。这一方面取决于-Xss对应值的大小,另一方面也取决于线程stack的深度,当线程退出时,其所占用的内存将自动释放。

JVM堆内存消耗分析(需要重点补充)

Java应用中除了Direct Bytebuffer、创建线程等操作JVM外物理内存的方法外,大多数都是对于JVM heap区的消耗。

在Java程序出现内存消耗过多、GC频繁或OutOfMemoryError的情况后,要首先分析其所耗费的是JVM外的物理内存还是JVM heap区。 如为JVM外的物理内存,则要分析程序中线程的数量以及Direct Bytebuffer的使用情况;如果为JVM heap区,则要结合JDK提供的工具或外部的工具分析程序中的具体对象的内存占用情况。

异常gc :

a. 通常gc发生意味着总归是有一块区域空间不足而触发gc。而许多导致异常gc的情况通常是持有了不必要的引用而没有即时的释放,比如像cache这样的地方就容易处理不好导致内存泄露引发异常gc。

b. 有可能是程序的行为是正常的,但是由于没有配置对合适的gc参数导致异常gc,这种情况通常需要调优gc参数或者堆代大小参数。

c. Full gc 发生的情况:

永久代满
年老代满
minor gc晋升到旧生代的平均大小大于旧生代剩余大小
CMS gc中promotion fail或concurrent mode fail

OOM:

a. OOM经常伴随着异常gc,之所以单独拿出来讲,是因为它的危害更大一些,异常gc顶多是收集速度过快或者回收不了内存,但是起码有个缓冲时间,但是出了OOM问题就大了。至于各种类型的OOM如何区分,如何发生,请参考这里(http://www.jianshu.com/p/2fdee831ed03),算是总结得比较全面的。对于常见的OOM,基本上可以一下子指出问题所在。

b. heap区,对象创建过多或持有太多无效引用(泄露)或者堆内存分配不足。使用jmap找到内存中对象的分布,使用ps找到相应进程及初始内存配置。

c. stack区, 不正确的递归调用。

d. perm区,初始加载包过多,分配内存不足。

e. 堆外内存区,分配ByteBuffer未释放导致。

发布了58 篇原创文章 · 获赞 1 · 访问量 5450

猜你喜欢

转载自blog.csdn.net/glamour2015/article/details/105032674
今日推荐