一. 上节回顾
1. 内存
2. 场景一:磁盘和文件写案例
3. 命令:vmstat
二. 上节的两个问题
问题一:buffer是磁盘读数据还是写数据的缓存?
问题二:cache是对文件读数据的缓存,是不是也会缓存写文件的数据?
问题一分析步骤:
java进程内存溢出,问题定位以及分析(mat)
1. 运行下面的命令,清理缓存,从文件/tmp/file中,读取数据写入空设备
echo 3 > /proc/sys/vm/drop_cachesdd 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_cachesdd 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
Exceptionorg.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进程导致的