如何获取一个Java对象所占内存大小
新建一个maven工程
我们先在IDEA中新建一个名为ObjectSizeFetcherAgent
的maven工程,如下图:
在maven项目中的pom.xml
中新增一个打jar包的插件,如下:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <testExcludes> <testExclude>/src/test/**</testExclude> </testExcludes> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.0.2</version> <configuration> <archive> <!--使用manifestFile属性配置自定义的参数文件所在的--> <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> </plugin> </plugins> </build>
在项目的resources
中新建一个名为META-INF
的目录,在这个目录下新建一个名为MANIFEST.MF
的属性文件,如下图:
项目的JDK设置为1.8,如下图:
编写获取Java对象内存的工具方法
import java.lang.instrument.Instrumentation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; public class ObjectSizeFetcher { // instrumentation 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入 private static Instrumentation instrumentation; /** * 这个方法先于主方法(main)执行 * @param args * @param inst */ public static void premain(String args, Instrumentation inst) { instrumentation = inst; } /** * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、 * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小; * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 * * @param o 需要计算内存的对象 * @return 返回内存大小 */ public static long sizeOf(Object o) { return instrumentation.getObjectSize(o); } /** * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 * 注意:这个方法如果你看不懂也没关系,会用就行 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<Object>(); Deque<Object> toBeQueue = new ArrayDeque<>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的时候已经计基本类型和引用的长度,包括数组 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本类型名字长度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本类型需要深度遍历其对象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //静态不计 || field.getType().isPrimitive()) { //基本类型不重复计 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的对象不计;计算过的不计,也避免死循环 * * @param visited * @param obj * @return */ static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
计算一个对象的大小
我们在项目工程中新建一个名为Point
的类,然后写一个main
方法来测试new Point()
占用内存空间的大小,代码如下:
public class Point { private int x; private int y; public static void main(String [] args) { System.out.println(ObjectSizeFetcher.sizeOf(new Point())); } }
我们在文件resources/META-INF/MANIFEST.MF
中新增如下一行属性配置:
Premain-Class: com.twq.ObjectSizeFetcher
maven打包,如下:
打开操作系统的cmd
,进入到上面的jar包所在的目录,如下图:
执行如下的命令:
java -javaagent:ObjectSizeFetcherAgent-1.0-SNAPSHOT.jar Point
得到的结果如下: