package org.github.jamm; import sun.misc.Unsafe; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.RuntimeMXBean; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; public abstract class MemoryLayoutSpecification { static final Unsafe unsafe; static { Unsafe tryGetUnsafe; try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); tryGetUnsafe = (sun.misc.Unsafe) field.get(null); } catch (Exception e) { tryGetUnsafe = null; } unsafe = tryGetUnsafe; } public static final MemoryLayoutSpecification SPEC = getEffectiveMemoryLayoutSpecification(); public abstract int getArrayHeaderSize(); public abstract int getObjectHeaderSize(); public abstract int getObjectPadding(); public abstract int getReferenceSize(); public abstract int getSuperclassFieldPadding(); /* Indicates if UNSAFE object size determination is available */ public static boolean hasUnsafe() { return unsafe != null; } /** @return sizeOfField(field.getType()) */ public static int sizeOf(Field field) { return sizeOfField(field.getType()); } /** * @return The memory size of a field of a class of the provided type; for Objects this is the size of the reference only */ public static int sizeOfField(Class<?> type) { if (!type.isPrimitive()) return SPEC.getReferenceSize(); if (type == boolean.class || type == byte.class) return 1; else if (type == char.class || type == short.class) return 2; else if (type == float.class || type == int.class) return 4; else if (type == double.class || type == long.class) return 8; throw new IllegalStateException(); } /** * @return The size of the provided instance as defined by the detected MemoryLayoutSpecification. For an array this * is dependent on the size of the array, but for an object this is fixed for all instances */ public static long sizeOf(Object obj) { Class<?> type = obj.getClass(); if (type.isArray()) return sizeOfArray(obj, type); return sizeOfInstance(type); } /** * @return this allocated heap size of the instance provided; for arrays this is equivalent to sizeOf(obj), * which uses the memory layout specification, however for objects this method uses */ public static long sizeOfWithUnsafe(Object obj) { Class<?> type = obj.getClass(); if (type.isArray()) return sizeOfArray(obj, type); return sizeOfInstanceWithUnsafe(type); } // this is very close to accurate, but occasionally yields a slightly incorrect answer (when long fields are used // and cannot be 8-byte aligned, an extra 4-bytes is allocated. // sizeOfInstanceWithUnsafe is safe against this miscounting public static long sizeOfInstance(Class<?> type) { long size = SPEC.getObjectHeaderSize() + sizeOfDeclaredFields(type); while ((type = type.getSuperclass()) != Object.class && type != null) size += roundTo(sizeOfDeclaredFields(type), SPEC.getSuperclassFieldPadding()); return roundTo(size, SPEC.getObjectPadding()); } // attemps to use sun.misc.Unsafe to find the maximum object offset, this work around helps deal with long alignment public static long sizeOfInstanceWithUnsafe(Class<?> type) { while (type != null) { long size = 0; for (Field f : declaredFieldsOf(type)) size = Math.max(size, unsafe.objectFieldOffset(f) + sizeOf(f)); if (size > 0) return roundTo(size, SPEC.getObjectPadding()); type = type.getSuperclass(); } return roundTo(SPEC.getObjectHeaderSize(), SPEC.getObjectPadding()); } public static long sizeOfArray(Object instance, Class<?> type) { return sizeOfArray(Array.getLength(instance), sizeOfField(type.getComponentType())); } /** * Memory an array * @param length Number of elements in the array * @param type the array class type * @return In-memory size of the array */ public static long sizeOfArray(int length, Class<?> type) { return sizeOfArray(length, sizeOfField(type.getComponentType())); } /** * Memory an array will consume * @param length Number of elements in the array * @param elementSize In-memory size of each element's primitive stored * @return In-memory size of the array */ public static long sizeOfArray(int length, long elementSize) { return roundTo(SPEC.getArrayHeaderSize() + length * elementSize, SPEC.getObjectPadding()); } private static long sizeOfDeclaredFields(Class<?> type) { long size = 0; for (Field f : declaredFieldsOf(type)) size += sizeOf(f); return size; } private static Iterable<Field> declaredFieldsOf(Class<?> type) { List<Field> fields = new ArrayList<Field>(); for (Field f : type.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) fields.add(f); } return fields; } private static long roundTo(long x, int multiple) { return ((x + multiple - 1) / multiple) * multiple; } private static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() { final String dataModel = System.getProperty("sun.arch.data.model"); if ("32".equals(dataModel)) { // Running with 32-bit data model return new MemoryLayoutSpecification() { public int getArrayHeaderSize() { return 12; } public int getObjectHeaderSize() { return 8; } public int getObjectPadding() { return 8; } public int getReferenceSize() { return 4; } public int getSuperclassFieldPadding() { return 4; } }; } boolean modernJvm = true; final String strSpecVersion = System.getProperty("java.specification.version"); final boolean hasDot = strSpecVersion.indexOf('.') != -1; if (hasDot) { if ("1".equals(strSpecVersion.substring(0, strSpecVersion.indexOf('.')))) { // Java 1.6, 1.7, 1.8 final String strVmVersion = System.getProperty("java.vm.version"); final int vmVersion = Integer.parseInt(strVmVersion.substring(0, strVmVersion.indexOf('.'))); modernJvm = vmVersion >= 17; } } final int alignment = getAlignment(); if (modernJvm) { long maxMemory = 0; for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { maxMemory += mp.getUsage().getMax(); } if (maxMemory < 30L * 1024 * 1024 * 1024) { // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM // total for all memory pools (yes, including code cache). return new MemoryLayoutSpecification() { public int getArrayHeaderSize() { return 16; } public int getObjectHeaderSize() { return 12; } public int getObjectPadding() { return alignment; } public int getReferenceSize() { return 4; } public int getSuperclassFieldPadding() { return 4; } }; } } /* Worst case we over count. */ // In other cases, it's a 64-bit uncompressed OOPs object model return new MemoryLayoutSpecification() { public int getArrayHeaderSize() { return 24; } public int getObjectHeaderSize() { return 16; } public int getObjectPadding() { return alignment; } public int getReferenceSize() { return 8; } public int getSuperclassFieldPadding() { return 8; } }; } // check if we have a non-standard object alignment we need to round to private static int getAlignment() { RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); for (String arg : runtimeMxBean.getInputArguments()) { if (arg.startsWith("-XX:ObjectAlignmentInBytes=")) { try { return Integer.parseInt(arg.substring("-XX:ObjectAlignmentInBytes=".length())); } catch (Exception e){} } } return 8; } }