新建一個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
得到的結果如下: