这里,内存映像工具利用的是Eclipse Memory Analyzee,对Dump出来的堆转存储快照进行分析,可以分清楚到底是出现了内存泄露(Memory Leak)还是内存溢出(Memory Overflow)。
在Eclipse中离线安装MemoryAnalyzee的步骤如下(由于在线安装可能会有网络问题):
(1)去官网下载离线安装包:
即下载MemoyAnalyzer-1.6.1.201611251412.zip包。
(2)下载之后解压得到如下文件内容:
(3)打开Eclipse->Help->Install new Software,如下:
将干菜下载好的路径引入其中。点击Ok。
(4)点击Avaliable Software Sites
注意:将里面,所有有关http连接的去掉勾选,否则,在更新的时候可能失败,如下:
只要location是http开头的就去勾选。
(5)去掉Contact all update sites during install to findrequired software前面的勾
(6)点击next,即可成功安装
那么:Eclipse Memory Analyzer如何使用呢?
为了使用MAT,即Memory Analyzer Tool,这里我们使用的是Eclipse Memory Anaylyzer需要先明白内存分析的数据源是如何产生的。有两种方式:
(1)通过jmap(Memory Map for Java)命令生成堆转储快照(一般称为heapdump或dump文件)
(2)通过JVM参数 -XX:+HeapDumpOnOutOfMemoryError可以让JVM在出现内存溢出异常时Dump出当前内存堆转存储快照以便事后进行分析。
这里先介绍第二种情况,即JVM中内存溢出后能够自动dump出对转存储快照。
编写一个简单的堆溢出的代码如下:
class User{
String name;
String sex;
@Override
public String toString() {
return "User [name=" + name + ", sex=" + sex + "]";
}
}
public class MemoryLeak1 {
public static void main(String[] args) {
List<User> list = new ArrayList<User>();
while(true){
list.add(new User());
}
}
}
分析:上面的代码中,通过List list中不断的添加new User()实例,因为list的作用域是和main方法一样的,而new出来的额User的实例被list中的元素引用,所以,通过while循环new 出来的User不会被GC。因此,导致内存溢出。
为了分析JVM中内存溢出的情况,需要添加JVM的参数:-XX:+HeapDumpOnOutOfMemoryError,操作步骤如下:
点击:Run->DebugConfiguration,在左侧列表下的Java Application下对应的你的Java类,比如:我这里的类是MemoryLeak1,如下:
在Arguments中的VM arguments下填写:-XX:+HeapDumpOnOutOfMemoryError,然后,点击Apply,以及点击Debug,即运行我们之前写的那段会内存溢出的代码。在运行大概两三分钟之后,出现如下异常信息:
可以看到异常信息为java.lang.OutOfMemoryError:Java heap space,即内存溢出异常。上面的黑色字体是将内存堆转储为dump文件,这里的dump文件名称为:java_pid7568.hprof,其中7568是当前Java程序的进程号。
另外,需要注意的是刚才我们生成的dump文件java_pid7568.hprof,这里的路径默认是在项目的根路径下,如果你没有看到该文件,需要强制刷新当前项目,比如:点击项目,然后按F5。
接下来,就是将该dump文件通过我们上面的Memory Analyzer进行分析,步骤如下:
(1)在工具栏上:File->Open File…,选中java_pid7568.hprof文件,并打开
(2)但是产生了如下错误:
点击OK之后,有:
看了上述的问题,大致的意思是说内存溢出错误,后面又说了“Runngin Eclipse”,大致猜测应该是系统给Eclipse软件分配的内存太小,另外,我们查看了java_pid7568.hprof文件的大小为3,355,137KB,大概是3g左右。于是,我们又查看了Eclipse软件下的eclipse.ini文件:
那么eclipse.ini中的参数各是什么意思呢?在理解这些参数之前,需要知道Eclipse这款软件是由java语言开发的,因此,其自身也需要jvm支持的,那么它的默认参数配置就在eclipse.ini中,如下:
-startup
plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.300.v20150602-1417
-product
org.eclipse.epp.package.jee.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
512M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
512m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.7
-Xms512m
-Xmx1024m
如上,我们可以看到,Eclipse需要java 1.7版本的支持,一般我们需要安装Jdk环境,假如我们删除掉Jdk环境,那么会出现如下错误:
因此,Eclipse其实本质上就是一个Java程序,我们可以通过JVM自带的jps命令来查看java进程,如下:
我们输入jps发现进程id号为4316,与我们在Windows任务管理器中看到的eclipse对应的PID是一致的,证明了我们说的Eclipse本质上也是一个Java程序。
也需要设置最下的堆大小,这里默认是-Xms512m,最大的堆大小-Xmx1024m,显然最大的堆大小为1024m大概是1G左右,明显小于我们要打开的文件3G的大小,这里,我们将-Xmx设置5120m,如下:
-Dosgi.requiredJavaVersion=1.7
-Xms512m
-Xmx5120m
再次重启Eclipse,并打开我们之前的那个大概3G左右的dump文件,则可以成功打开。
这里,主要看下“Histogram”,这个会列出每个类的实例个数。以及“Leak Suspects”分析内存可能产生泄露的原因。我们看下:
Histogram:
这里Class Name是对应类型,Objects是对应实例的个数,很明显类com.tim.User的实例个数竟然达到70,091,071个,那么后面跟着的两列:Shallow Heap和Retained Heap是什么东西?
问题:Shallow Heap和Retained Heap 的区别?
ShallowHeap:Shallow中文的意思是肤浅、浅的意思,则Shallow Heap我们可以理解为浅的表面的堆,是对象本身占据的内存的大小,不包含其所引用的对象。
RetainedHeap:Retained中文的意思是保留、保存的意思,则Retained Heap我们可以理解为保留、保存的堆,是当前对象大小+当前对象可直接或者间接引用到的对象的大小总和。
分析:上面的Shallow Heap是比较好理解的,而要真正理解Retained Heap需要先理解GC roots是如何工作的?
GCroots:
---------------------
来源:oschina
链接:https://my.oschina.net/u/866802/blog/3066702