执行main方法
启动的时候执行,虚拟机参数添加: -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=d:\student.hprof
/**
* 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
* 它由三个部分组成:Student、WebPage和StudentTrace三个类
* 启动的时候执行,虚拟机参数添加: -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=d:\student.hprof
*/
public class StudentTrace {
static List<WebPage> webpages = new ArrayList<WebPage>();
public static void createWebPages() {
for (int i = 0; i < 100; i++) {
WebPage wp = new WebPage();
wp.setUrl("http://www." + Integer.toString(i) + ".com");
wp.setContent(Integer.toString(i));
webpages.add(wp);
}
}
public static void main(String[] args) {
createWebPages();//创建了100个网页
//创建3个学生对象
Student st3 = new Student(3, "Tom");
Student st5 = new Student(5, "Jerry");
Student st7 = new Student(7, "Lily");
for (int i = 0; i < webpages.size(); i++) {
if (i % st3.getId() == 0)
st3.visit(webpages.get(i));
if (i % st5.getId() == 0)
st5.visit(webpages.get(i));
if (i % st7.getId() == 0)
st7.visit(webpages.get(i));
}
//清理掉
webpages.clear();
//触发gc垃圾回收
System.gc();
}
}
class Student {
private int id;
private String name;
private List<WebPage> history = new ArrayList<>();
public Student(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<WebPage> getHistory() {
return history;
}
public void setHistory(List<WebPage> history) {
this.history = history;
}
public void visit(WebPage wp) {
if (wp != null) {
history.add(wp);
}
}
}
class WebPage {
private String url;
private String content;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
导入hprof文件开始分析
查看线程概述
打开hprof文件后点击下面图片的图标来查看线程概述
能看到main方法方法里面3个Student对象,三个student的shallow heap(浅堆)大小都是24, 但是三个对象的retained heap (深堆)都是不一样的.
第一个student对象如果被回收的话,就会回收3784个字节
第二个student对象如果被回收的话,就会回收1872个字节
第三个student对象如果被回收的话,就会回收1384个字节
为什么从第一个student对象到第三个student对象 深堆会越来越少呢???, 因为示例代码往三个student对象里面设置值
因为不同的分支条件不一样,进入第一个分支的多进入第二个分支的稍微少点,进入第三个分支的更少,所以第一个student对象深堆就大, 第二个student对象就稍微少点,第三个student对象就更少了.
深堆数量是怎么算出来的
以最下面的student对象为例子,history深堆大小是1288 ,打开history这个ArrayList之后,发现ArrayList内部的elementData数组有一堆元素,再打开elementData数组之后,就能看到elementData这个数组里面有一堆WebPage对象了
elementData数组的浅堆是80个字节,而elementData数组中的所有WebPage对象的深堆之和是1208个字节,所以加在一起就是elementData数组的深堆之和,也就是1288个字节.
如果elementData数组要是被回收的话,只能回收掉1288个字节,原因是有一些WebPage还被别的student所引用,
15个WebPage对象,每个对应152字节, 15*152=2280字节,即2280位elementData的实际大小,但是为什么elementData的深堆是1288呢?
因为能被7整除且能被3整除,以及能被5整除的数值有: 0 21 35 42 63 84 70 ,总共七个数, 这七个数儿对应的WebPage不只是被这个student独享,还被其它的student对象引用了,这些是不能计算深堆里面的, 7*152就是1064个字节 ,
2280-1064 =1216 , 是1216这个数字,但是1216和显示的1288还是不一样,为什么这样呢?还差72个字节,这72个字节是什么呢? 因为elementData这个Object的数组引用也要占据一些空间
虽然elementData存了15个元素,但是不代表elementData数组长度就是15. 数组默认长度是10,如果装不下了就扩容成1.5倍,就变成了15了 ,数组长度15就能被第三个student对象装得下.
15个elementData的元素,每个元素占用4个字节, 15*4 = 60字节.
60+8个对象头的字节数+数组自己长度计数(4个字节) = 72字节
所以这就匹配上了:
因为第三个student对象在触发gc的时候只能有8个对象被回收掉, 每个占用152个字节,8个就是1216.
1216再加上elementData元素的字节+对象头的字节数+数组自己长度计数 就是 1288个字节
查看某个对象是否被别的对象引用
通过这个功能可以查看某个对象是否能被GC清理掉,是否存在内存泄露问题
结果发现这个对象被三个对象引用了.
那么我们查看另外一个对象还有没有被其它对象引用
下图发现只有他自己引用了这个对象,那么当他自己被回收了之后,这个对象也会被回收