java进程内存溢出案例

一. 上节回顾

1. 内存

2. 场景一:磁盘和文件写案例

3. 命令:vmstat

 

二. 上节的两个问题

问题一:buffer是磁盘读数据还是写数据的缓存?

问题二:cache是对文件读数据的缓存,是不是也会缓存写文件的数据?

 

问题一分析步骤:

java进程内存溢出,问题定位以及分析(mat)

1. 运行下面的命令,清理缓存,从文件/tmp/file中,读取数据写入空设备

echo 3 > /proc/sys/vm/drop_caches
dd if=/data/file of=/dev/null

2. 使用vmstat 1查看内存输出

观察vmstat的输出,会发现读取文件时,也就是bi大于0时,buff都是80,没有变化,而cache则在不停增长,可以得出什么结论?

cache是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据

 

问题二分析步骤:

那么磁盘读又是什么情况?

1. 首先清理缓存,从磁盘分区/dev/vda1中读取数据,写入空设备

echo 3 > /proc/sys/vm/drop_caches
dd if=/dev/sda1 of=/dev/null bs=500M count=1024

2. 使用vmstat 1查看内存输出

观察vmstat的输出,会发现读磁盘时,也就是bi大于0时,buff和cache都在增长,但buff增长快了很多,说明了什么问题?

说明读磁盘时,数据缓存到了buff中

 

经过上一个场景中的案例分析:可以对比得到这样的结论:

读文件时数据会缓存到cache中,而读磁盘时数据会缓存到buff中

也可以通过案例,了解到:

buff既可以用来将要写入磁盘数据的缓存,也可以用来缓存从磁盘读取数据的缓存

cache既可以用来从文件读取数据的页缓存,也可以用来写文件的页缓存

 

简单的总结:buff是对磁盘数据的缓存,而cache是对文件数据的缓存,它们既会用在读请求中,也会用在写请求中

 

三. java进程内存溢出案例

1. 内存溢出了,怎么定位?

java导致CPU高的问题,OOM,导致了CPU上不去,一直在50%

对进程来说,能够看到的其实是内核提供的虚拟内存,这些内存还需要通过页表,由系统映射为物理内存

当进程通过malloc()申请虚拟内存后,系统不会立即为其分配物理内存,而是每次访问时,才通过缺页异常嵌入内核中分配内存

备注:缺页异常:并不是每次CPU都能访问到相应的物理地址单位,因此这样映射失败了,就产生了缺页异常

 

Linux中还会使用buff和cache,分别把文件和磁盘读写的数据缓存到内存中

发生事故:

(1) 没有正确回收分配的内存,导致了内存泄漏

(2) 访问的是已分配内存边界外的地址,导致程序异常退出

 

内存的分配和回收

在前面讲进程的内存空间时,知道用户空间包含多个不同的内存段,比如:只读段、数据段、堆、栈以及文件映射,这些内存段正是应用程序使用内存的基本方式

比如:在程序定义了一个局部变量,比如一个整数数组 int data[32],定义了一个可以存储32个整数的内存段,由于这是一个局部变量,它会从内存空间的栈中分配内存

栈内存由系统自动分配和管理,一旦程序运行出现超过了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题

只读段:包括代码和常量

数据段:包括全局变量

堆:包括动态分配的内存,从低地址开始向上增长

文件映射段:包括动态库,共享内存,从高地址开始向下增长

栈:包括局部变量和函数调用的上下文

 

很多时候,事先并不知道数据的大小,所以就用到标准库函数malloc(),在程序中动态分配内存,这时候,系统就会从内存空间的堆中分配内存

堆内存由应用程序自己分配和管理,除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数free()来是否它们,如果应用程序没有正确释放堆内存,就会造成内存泄漏

 

那么其他内存段是否也会导致内存泄漏?

只读段:包括程序的代码和常量,由于是只读的,不会再去分配新的内存,所以不会产生内存泄漏

数据段:包括全局变量和静态变量,这些变量在定义时已经确定了大小,所以不会产生内存泄漏

文件映射段:包括动态库和共享内存,其中共享内存由程序动态分配和管理,所以,如果程序在分配后忘记了回收,就会导致跟内存类似的泄漏问题

 

内存泄漏是很严重的问题,如果出现了,不仅应用程序自己不能访问,系统也不能把内存再次分配给其他应用试验,内存泄漏不断积累,甚至整个系统的内存都被耗尽,机器也不能工作了

系统可以通过OOM进程杀死进程,但是在OOM引发之前,会出现很多反应,比如:CPU占用很高,内存一直上升,访问整个系统会越来越慢

 

2. 步骤

(1) 把pertest.war包放在Tomcat的webapps下

(2) vim catalina.sh,设置Java堆大小

重启tomcat后使用ps -ef | grep java查看

(3) 启动Tomcat,看到查看日志:tail -f catalina.out

(4) 使用jmeter或其他工具,发起请求

(5) 用vmstat观察内存变化,每隔3s输出一组数据

vmstat 3

从输出结果来看,内存的free列在不停的变小,而buff和cache基本保持不变。未使用的内存在不断减小,而buff和cache基本不变,这说明系统中使用的内存一直在升高,但并不能说明内存泄漏,因为应用程序运行中需要的内存也可能会增大,比如程序中用了一个动态增长的数组来缓存计算结果,占用内存自然会增长

 

那怎么确定是不是内存泄漏了?

方法一:访问页面:http://192.168.0.109:8080/pertest/init1.jsp

在页面出现了OOM

Exception

org.apache.jasper.JasperException: javax.servlet.ServletException: java.lang.OutOfMemoryError: Java heap space
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:604)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:499)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
Root Cause

javax.servlet.ServletException: java.lang.OutOfMemoryError: Java heap space
    org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:666)
    org.apache.jsp.init1_jsp._jspService(init1_jsp.java:167)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:476)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:385)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:329)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)

 

 

到这里输入以下命令

jmap -F -dump:format=b,file=pertestdump1220.bin 27910   # pertestdump1220.bin为文件名,可以自己任意命名

命令格式:

jmap [option] <pid>

-dump:生成Java堆栈的快照

-F:当虚拟机进程对-dump选项没有响应时,可使用这个选项生成dump快照

format=b:用二进制的数据生成

file:生成快照的名称

-histo:显示堆中对象统计信息,包括类,实例数量和合计容量

-heap:线上Java堆详细信息,如使用哪种回收器,参数配置,分代(老年代、持久代、年轻代)

 

方法二:输入top命令

输入top命令,也可以看到use%的CPU占用90%以上,并且这个就是当前这个Java进程导致的

 

posted @ 2020-04-12 14:56  cnhkzyy  阅读(571)  评论(0编辑  收藏  举报