业务开发需求中,在进行一些大批量数据处理的时候,会有很多对象驻留在内存中,此时可能会需要有一些对内存占用情况的考虑,以下几个方法能帮助快速判断出对象占用内存大小的情况,以帮助开发人员在后期参数设置上提供一定的参考依据和帮助

准备工作,新建几个用来存放数据的Java对象,可以稍微复杂点,实际上的这个学生信息对象会比这个更复杂很多,这里只是举例,如下

public class StudentInfo {

    private String name;
    private int age;
    private Address address;
    private EducationalExperience educationalExperience;



    //get、set方法省略
}
public class Address {

    private RegionInfo province;
    private RegionInfo city;
    private RegionInfo district;

    //get、set方法省略
}
public class RegionInfo {
    private String name;
    private int regionCode;
    private String abbreviation;

    
//get、set方法省略
}
public class EducationalExperience {

    private EducationInformation[] educationInformationData;

    //get、set方法省略
}
public class EducationInformation {
    private String schoolName;
    private Date startTime;
    private Date endTime;
    private LearningStatusEnum learningStatus;


    //get、set方法省略
}
public enum LearningStatusEnum {
    /**
     * 正在学习
     */
    CURRENTLY_STUDYING("正在学习",1),
    /**
     * 已毕业
     */
    GRADUATED("已毕业",2),
    /**
     * 即将入学
     */
    ABOUT_TO_ENROLL("即将入学",3)
    ;

    private int statusCode;
    private String statusName;

    LearningStatusEnum(String s, int i) {
        this.statusCode = i;
        this.statusName = s;
    }

    //get、set方法省略
}

在代码中构建一个学生信息的对象,有点长,这不重要

StudentInfo studentInfo = new StudentInfo();
studentInfo.setName("CheungQ");
studentInfo.setAge(16);

RegionInfo regionInfoProvince = new RegionInfo();
regionInfoProvince.setName("江苏");
regionInfoProvince.setRegionCode(321);
regionInfoProvince.setAbbreviation("苏");
RegionInfo regionInfoCity = new RegionInfo();
regionInfoCity.setName("南京");
regionInfoCity.setRegionCode(21);
regionInfoCity.setAbbreviation("宁");
RegionInfo regionInfoDistrict = new RegionInfo();
regionInfoDistrict.setName("玄武区");
regionInfoDistrict.setRegionCode(3821);
Address address = new Address();
address.setProvince(regionInfoProvince);
address.setCity(regionInfoCity);
address.setDistrict(regionInfoDistrict);
studentInfo.setAddress(address);

EducationInformation education1 = new EducationInformation();
education1.setSchoolName("NEET家里蹲大学");
education1.setStartTime(new Date());
education1.setEndTime(new Date());
education1.setLearningStatus(LearningStatusEnum.GRADUATED);
EducationInformation[] information = new EducationInformation[]{education1};
EducationalExperience educationalExperience = new EducationalExperience();
educationalExperience.setEducationInformationData(information);
studentInfo.setEducationalExperience(educationalExperience);
  • JOL工具类ClassLayout和GraphLayout

添加依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

使用代码查看对象

System.out.println("studentInfo:"+ClassLayout.parseInstance(studentInfo).toPrintable());

这里需要特地说明下ClassLayout方法中只能计算当前这个Java对象所占内存的大小,如果是下面引用的子对象是不会算进来的。不过JOL相比于其他的方法不同的是,他可以详细里的列出对象内的结构的情况,包括对象头、成员变量、对齐填充等信息,相关拓展可以看下这篇文章《对象占多少字节》,此外还有方法可以查看到完整的对象大小信息,如下。

GraphLayout.parseInstance(studentInfo).toFootprint()

另外,除了直接引用包,IDEA中也有一个JOL插件可以使用,可以在Plugins中搜索安装

使用方法也很简单,安装完成后,在你需要查看的类名上右键菜单中有一个Show Object Layout选项,点击即可

  • 使用jdk8自带API:ObjectSizeCalculator

请注意,ObjectSizeCalculator 仅在 HotSpot VM 上受支持,JDK 11 上也不再存在 ObjectSizeCalculator。必要时需要先执行

System.setProperty("java.vm.name","Java HotSpot(TM) ");

我们直接使用该方法看下刚刚我们创建的对象

System.out.println("studentInfo:"+ObjectSizeCalculator.getObjectSize(studentInfo));

可以得到如下

  • 借助org.apache.lucene工具类

添加对应依赖,并使用方法

org.apache.lucene.util.RamUsageEstimator#sizeOf(java.lang.Object)

获取对象占用内存,大小,这里有一点需要补充下,后来另外尝试过<version>8.1.0</version>中已经没有这个方法了

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>4.0.0</version>
</dependency>

翻看了下4.0.0版本中这个方法的源码,基本思路是双层while循环配合一个stack来递归遍历整个对象并统计大小,所以这种做法在遇到某些大对象的时候应该是有一定风险或者问题的。我们回到主题,调用下看下

System.out.println("Lucence get size:"+RamUsageEstimator.sizeOf(studentInfo));

可以得到结果

  • 一点补充

我们直接拿一个字符串,把上面几种的方式都跑一遍

System.out.println("GraphLayout"+GraphLayout.parseInstance(str).toFootprint());
System.out.println("ClassLayout:"+ClassLayout.parseInstance(str).toPrintable());
System.out.println("ObjectSizeCalculator:"+ObjectSizeCalculator.getObjectSize(str));
System.out.println("Lucence:"+RamUsageEstimator.sizeOf(str));
System.out.println("Lucence:"+RamUsageEstimator.shallowSizeOf(str));

可以看到GraphLayout、ObjectSizeCalculator、Lucence得到的都是768bytes,而ClassLayout和RamUsageEstimator.shallowSizeOf方法得到的24个bytes,这里其实是两种不同的计算方式的区别,Shallow Size(浅层大小)和Retain Size(保留的大小)。浅层大小是对象持有的内存大小,保留的大小是删除对象后释放的内存大小,以及从垃圾回收根目录 (GC 根) 无法访问的依赖对象。除此之外还有一个Deep Size(深大小)的概念,深大小与保留大小的区别在于那些存在共享的对象。

https://www.cnblogs.com/duanxz/p/5230856.html

所以对于一个String型对象而言浅层大小是不包含当前字符串中存储的字符信息的内存占用大小的,因为我们知道String型变量的本质实际上一个char[]数组,这个数组才是一个字符串真实保存实际文本内容的地方,再就是另一个话题了,不在这里展开https://www.cnblogs.com/jaylon/p/5721571.html