1、前言
之前我们在 JVisualVM 中,经常能看到对象大小,如图所示:
对象大小
然后,我们理所当然的认为这个对象以及对象所引用的对象总共这么大,然而这种认知是错误的。比如,我做如下实验,创建一个 Person 类,Person 类里就只有一个简单的 name 属性。代码如下:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
}
public class NIUBI {
private String name;
private String desc;
private List<Person> personList = new ArrayList<>();
public NIUBI(String name, String desc) {
this.name = name;
this.desc = desc;
for(int i = 0; i < 5000000; i++){
this.personList.add(new Person("mamammmammm" + i));
}
}
public static void main(String[] args) throws InterruptedException {
NIUBI niubi = new NIUBI("22", "dd");
System.out.println(ClassLayout.parseInstance(niubi).toPrintable());
Thread.sleep(5000);
System.out.println(RamUsageEstimator.sizeOf(niubi) / 1024 / 1024);
System.out.println(RamUsageEstimator.shallowSizeOf(niubi));
Thread.sleep(500000 * 1000);
}
}
这个对象看起来就很大,然后我用一个 java 中估算对象大小的工具来打印对象大小。我分别用了:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
这个主要是打印对象的内存布局的。
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency>
这个主要是计算对象大小以及该对象引用的数据大小。
使用两个不同的工具是想看一下每个工具得到的结果是否正确,防止一个工具得出偏颇的结局。
2、现象
运行代码,得到如下输出:
输出
可见 NIUBI 对象本身的大小才 24B,但是 NIUBI 加上引用的 Person 对象总共有480MB。然后我们从 JVisualVm 来查看相应的类:
NIUBI
Person
NIUBI 大小只有 40B(跟24B差别不大,可能是不同工具的问题),Person 大小有接近114MB(String 也算对象)。从上图可以看到,JVisualVm 查看相应的大小时,只是查看对象本身的大小,并不是对象 + 引用的对象大小,要不然 NIUBI 也应该是480MB才对。
3、结论
我们在 JVisualVm 查看堆内存的时候,发现有某个对象可能几百万,然后占用内存才2G左右,不能以为这个对象 + 引用对象大小就2G,可能算了此对象引用的对象后,你会发现实际占用内存比2G多得多,所以才会导致内存爆炸。
一般可以根据最顶层的类(这里是 NIUBI)来计算保留大小,也能大致从 GcRoots 出发,找到 NIUBI + 其所引用的所有类的大小。
查保留
网友评论