本文首先介绍测量对象内存布局的其中一种方法,Instrumentation
+ sa-jdi
核心代码
- 代码托管在:https://github.com/zhongmingmao/java_object_layout
- 采用
Instrumentation
+ sa-jdi
的方式需要自己编写代码,比较繁琐,OpenJDK
提供的JOL
(Java Object Layout) 工具则是开箱即用
,非常方便,后续博文会进一步介绍JOL的使用
测量对象大小
通过Instrumentation
测量对象占用的空间大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
|
public class SizeOfObjectUtil {
static Instrumentation inst;
public static void premain(String args, Instrumentation instP) { inst = instP; }
public static long sizeOf(Object obj) { return inst.getObjectSize(obj); }
public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<>(); Deque<Object> toBeQueue = new ArrayDeque<>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { 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() || field.getName().contains("this")) { continue; }
field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; }
static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
|
创建对象
通过反射
创建对象,这些对象都是具有代表性的实例,下一博文继续分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
public class CreateObjectUtil {
static List<Object> objects = new ArrayList<>();
static void createObject(Class<?> clazz) throws Exception { String packageName = clazz.getName().substring(0, clazz.getName().lastIndexOf(".")); String resourcePath = clazz.getResource("").getPath(); Set<String> outClassSet = new HashSet<>();
if (resourcePath.contains("!")) { String jarFilePath = resourcePath.substring(resourcePath.indexOf(":") + 1, resourcePath.lastIndexOf("!")); new JarFile(jarFilePath).stream().forEach(jarEntry -> { if (jarEntry.getName().endsWith(".class")) { String className = jarEntry.getName() .substring(0, jarEntry.getName().length() - 6).replace("/", "."); if (className.contains(packageName)) { instance(className, outClassSet); } } }); } else { for (File subFile : new File(resourcePath).listFiles()) { String className = String.format("%s.%s", packageName, subFile.getName().substring(0, subFile.getName().lastIndexOf("."))); if (className.contains(packageName)) { instance(className, outClassSet); } } } }
private static void instance(String className, Set<String> outClassSet) { try { Class<?> klass = Class.forName(className); if (className.contains("$")) { outClassSet.add(className.substring(0, className.lastIndexOf("$"))); return; } Object object = klass.newInstance(); objects.add(object); System.out.println(String.format("%20s : shallow size = %d Bytes , retained size= %s Bytes", klass.getCanonicalName(), sizeOf(object), fullSizeOf(object)));
} catch (Exception e) { e.printStackTrace(); } }
public static void main(String[] args) throws Exception { createObject(CompressedOopsTestClass.class); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); reader.readLine(); } }
|
打印内存布局
通过Hotspot JVM
提供的工具,打印JVM进程的内存映像
到磁盘文件,便于后续继续分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package me.zhongmingmao.layout;
import sun.jvm.hotspot.oops.HeapPrinter; import sun.jvm.hotspot.oops.HeapVisitor; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.tools.Tool;
public class PrintObjectMemLayout extends Tool {
@Override public void run() { VM vm = VM.getVM(); ObjectHeap objHeap = vm.getObjectHeap(); HeapVisitor heapVisitor = new HeapPrinter(System.out); objHeap.iterate(heapVisitor); }
public static void main(String[] args) throws InterruptedException { PrintObjectMemLayout layout = new PrintObjectMemLayout(); layout.execute(args); layout.stop(); } }
|
使用
git clone
1
| $ git clone https://github.com/zhongmingmao/java_object_layout
|
mvn clean install
1
| $ cd java_object_layout && mvn clean install
|
创建对象
1 2 3 4 5 6
| $ cd create-object/target
# -Xms5m -Xmx5m ➔ 限制Heap大小是为了加快JVM内存映像的导出时间和减小文件大小,可以依据实际情况进行调整 # -XX:+UseCompressedOops ➔ 开启指针压缩,可以关闭观察差异,默认打开 $ java -cp create-object-1.0-SNAPSHOT.jar -javaagent:./size-of-object-1.0-SNAPSHOT.jar -Xms5m -Xmx5m -XX:+UseCompressedOops me.zhongmingmao.create.CreateObjectUtil me.zhongmingmao.create.classes.CompressedOopsTestClass : shallow size = 24 Bytes , retained size= 56 Bytes
|
导出JVM内存布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 另起一个会话 $ cd print-object-mem-layout/src/main/java
$ javac -cp $JAVA_HOME/lib/sa-jdi.jar me/zhongmingmao/layout/PrintObjectMemLayout.java
# 需要sudo权限 $ sudo java -cp $JAVA_HOME/lib/sa-jdi.jar:. me.zhongmingmao.layout.PrintObjectMemLayout $(jps | grep CreateObjectUtil | awk '{print $1}') > heap_oops_compress.txt
$ du -sh heap_oops_compress.txt 9.7M heap_oops_compress.txt # 上面限制Heap大小,导出的文件也很小,便于分析
# 通过导出的JVM内存映像就能对对象的内存布局进行分析 $ cat heap_oops_compress.txt | grep CompressedOopsTestClass | head -n 1 "me/zhongmingmao/create/classes/CompressedOopsTestClass.class" @ 0x00000007bfa31ea0 (object size = 24)
|
待续
下一博文将通过Instrumentation
+ sa-jdi
来分析对象的内存布局