From timur.gbd at gmail.com Sun Jun 5 21:47:55 2016 From: timur.gbd at gmail.com (=?UTF-8?B?0KLQuNC80YPRgCDQk9C40LHQsNC00YPQu9C70LjQvQ==?=) Date: Mon, 6 Jun 2016 00:47:55 +0300 Subject: [PATCH] 7901019: jol estimates do not take AllocationStyle into the account In-Reply-To: References: <56A2029B.5080909@oracle.com> <56A653F0.4060708@oracle.com> <56A6B258.1080807@gmail.com> <570F8C38.9040801@oracle.com> Message-ID: <57549E0B.2020307@gmail.com> Hey there! I fixed tests, there were several bugs during determining allocation types of java types, also I removed autoAlign option from HotspotLayouter, because it seems, that now it is unnecessary, since we emulate original Hotspot algorithm of layouting. On 24.04.16 20:59, Timur Gibadullin wrote: > Hi, ok, I'll check out. -------------- next part -------------- Index: jol-core/src/main/java/org/openjdk/jol/info/ClassData.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jol-core/src/main/java/org/openjdk/jol/info/ClassData.java (revision 78:7bf2496e895b7b0bab77314646ddb94f76ddc8aa) +++ jol-core/src/main/java/org/openjdk/jol/info/ClassData.java (revision 78+:7bf2496e895b+) @@ -24,13 +24,17 @@ */ package org.openjdk.jol.info; +import org.openjdk.jol.datamodel.DataModel; + import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.List; +import sun.misc.Contended; /** * Holds the class data, without the layout information. @@ -92,7 +96,14 @@ } ClassData cd = new ClassData(o, klass.getName()); + Class superKlass = klass.getSuperclass(); + cd.isContended = klass.getAnnotation(Contended.class) != null; + + if (superKlass != null) { + cd.addSuperClassData(klass.getSuperclass()); + } + do { for (Field f : klass.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { @@ -113,6 +124,8 @@ private final String arrayComponentKlass; private final long length; private final boolean isArray; + private boolean isContended; + private ClassData superClass; /** * Constructs the empty ClassData, suited for regular class. @@ -130,6 +143,8 @@ this.arrayKlass = null; this.arrayComponentKlass = null; this.isArray = false; + this.superClass = null; + this.isContended = false; } /** @@ -152,6 +167,8 @@ this.classNames = null; this.length = length; this.isArray = true; + this.superClass = null; + this.isContended = false; } /** @@ -164,6 +181,15 @@ } /** + * Add the super-class data of the class. + * + * @param superClass super class + */ + public void addSuperClassData(Class superClass) { + this.superClass = parseClass(superClass); + } + + /** * Add the field data. * * @param fieldData the data to add @@ -183,6 +209,21 @@ } /** + * Counts the total size of fields. + * + * @return fields size + */ + public int fieldSize(DataModel model) { + int fieldSize = 0; + + for (FieldData f : fields) { + fieldSize += model.sizeOf(f.typeClass()); + } + + return fieldSize; + } + + /** * Get the fields' data for the given class. * * @param klass class name @@ -224,6 +265,24 @@ */ public boolean isArray() { return isArray; + } + + /** + * Get ClassData of the super-class. + * + * @return ClassData + */ + public ClassData superClass() { + return superClass; + } + + /** + * Does the class have @Contended annotation? + * + * @return true, if class has @Contended annotation; false otherwise + */ + public boolean isContended() { + return isContended; } /** Index: jol-core/src/test/java/org/openjdk/jol/layouters/LayouterInvariantsTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jol-core/src/test/java/org/openjdk/jol/layouters/LayouterInvariantsTest.java (revision 78:7bf2496e895b7b0bab77314646ddb94f76ddc8aa) +++ jol-core/src/test/java/org/openjdk/jol/layouters/LayouterInvariantsTest.java (revision 78+:7bf2496e895b+) @@ -58,8 +58,10 @@ for (DataModel model : MODELS) { for (boolean hierarchyGaps : BOOLS) { for (boolean superClassGaps : BOOLS) { - for (boolean autoAlign : BOOLS) { - ClassLayout.parseClass(cl, new HotSpotLayouter(model, hierarchyGaps, superClassGaps, autoAlign)); + for (boolean compactFields : BOOLS) { + for (int fieldAllocationStyle : new int [] {0, 1, 2}) { + ClassLayout.parseClass(cl, new HotSpotLayouter(model, hierarchyGaps, superClassGaps, compactFields, fieldAllocationStyle)); + } } } } Index: jol-core/src/main/java/org/openjdk/jol/info/FieldData.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jol-core/src/main/java/org/openjdk/jol/info/FieldData.java (revision 78:7bf2496e895b7b0bab77314646ddb94f76ddc8aa) +++ jol-core/src/main/java/org/openjdk/jol/info/FieldData.java (revision 78+:7bf2496e895b+) @@ -28,6 +28,7 @@ import org.openjdk.jol.vm.VM; import java.lang.reflect.Field; +import sun.misc.Contended; /** * Holds the field information, without the layout. @@ -45,7 +46,7 @@ * @return field data */ public static FieldData create(String hostKlass, String fieldName, String fieldType) { - return new FieldData(null, -1, hostKlass, fieldName, fieldType); + return new FieldData(null, -1, hostKlass, fieldName, fieldType, false, null); } /** @@ -55,7 +56,17 @@ * @return field data */ public static FieldData parse(Field field) { - return new FieldData(field, VM.current().fieldOffset(field), field.getDeclaringClass().getSimpleName(), field.getName(), field.getType().getSimpleName()); + Contended contentded = field.getAnnotation(Contended.class); + boolean isContended = contentded != null; + return new FieldData( + field, + VM.current().fieldOffset(field), + field.getDeclaringClass().getSimpleName(), + field.getName(), + field.getType().getSimpleName(), + isContended, + isContended ? contentded.value() : null + ); } private final String name; @@ -63,13 +74,17 @@ private final String klass; private final Field refField; private final long vmOffset; + private final boolean isContended; + private final String contendedGroup; - private FieldData(Field refField, long vmOffset, String hostKlass, String fieldName, String fieldType) { + private FieldData(Field refField, long vmOffset, String hostKlass, String fieldName, String fieldType, boolean isContended, String contendedGroup) { this.klass = hostKlass; this.name = fieldName; this.type = fieldType; this.refField = refField; this.vmOffset = vmOffset; + this.isContended = isContended; + this.contendedGroup = contendedGroup; } /** @@ -100,6 +115,33 @@ } /** + * Answers whether the field has contentded annotation. + * + * @return true, if the field is contended + */ + public boolean isContended() { + return isContended; + } + + /** + * Get contentded group of the field. + * + * @return String + */ + public String contendedGroup() { + return contendedGroup; + } + + /** + * Get original Field. + * + * @return Field which is represented by the FieldData + */ + public Field refField() { + return refField; + } + + /** * Gets the string representation of field value, * if appropriate. * @@ -139,4 +181,7 @@ } } + public String toString() { + return refField.toString(); + } } Index: jol-core/src/main/java/org/openjdk/jol/layouters/HotSpotLayouter.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- jol-core/src/main/java/org/openjdk/jol/layouters/HotSpotLayouter.java (revision 79:e8e889a8ae3d64ff2447ed587a0762b5a1e6f951) +++ jol-core/src/main/java/org/openjdk/jol/layouters/HotSpotLayouter.java (revision 79+:e8e889a8ae3d+) @@ -31,34 +31,96 @@ import org.openjdk.jol.info.FieldLayout; import org.openjdk.jol.util.MathUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.IllegalStateException; +import java.util.Arrays; import java.util.BitSet; import java.util.Collection; +import java.util.EnumMap; +import java.util.HashSet; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; + /** * VM layout simulator. * * @author Aleksey Shipilev */ public class HotSpotLayouter implements Layouter { + // The next classes have predefined hard-coded fields offsets. + private static HashSet predefinedFieldsOffsetsClasses = new HashSet(Arrays.asList( + "java.lang.AssertionStatusDirectives", + "java.lang.Class", + "java.lang.ClassLoader", + "java.lang.ref.Reference", + "java.lang.ref.SoftReference", + "java.lang.StackTraceElement", + "java.lang.String", + "java.lang.Throwable", + "java.lang.Boolean", + "java.lang.Character", + "java.lang.Float", + "java.lang.Double", + "java.lang.Byte", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long" + )); + static final int CONTENDED_PADDING_WIDTH = Integer.getInteger("contendedPaddingWidth", 128); + private final DataModel model; private final boolean takeHierarchyGaps; private final boolean takeSuperGaps; - private final boolean autoAlign; + private final boolean compactFields; + private final int fieldAllocationStyle; public HotSpotLayouter(DataModel model) { - this(model, false, false, false); + this(model, false, false, true, -1); } - public HotSpotLayouter(DataModel model, boolean takeHierarchyGaps, boolean takeSuperGaps, boolean autoAlign) { + public HotSpotLayouter(DataModel model, boolean takeHierarchyGaps, boolean takeSuperGaps, boolean compactFields, int fieldAllocationStyle) { this.model = model; this.takeHierarchyGaps = takeHierarchyGaps; this.takeSuperGaps = takeSuperGaps; - this.autoAlign = autoAlign; + this.compactFields = compactFields; + this.fieldAllocationStyle = fieldAllocationStyle; } + private enum FieldAllocationType { + OOP, // Oops + BYTE, // Boolean, Byte + SHORT, // shorts, char + WORD, // ints + DOUBLE, // aligned long or double + BAD_ALLOCATION_TYPE + } + + private FieldAllocationType allocationTypeFor(Field field) { + String simpleName = field.getType().getSimpleName(); + Class superClass = field.getType(); + + while ((superClass = superClass.getSuperclass()) != null) { + simpleName = superClass.getSimpleName(); + } + + if (simpleName.equals("Object")) { + return FieldAllocationType.OOP; + } else if (simpleName.equals("boolean") || simpleName.equals("byte")) { + return FieldAllocationType.BYTE; + } else if (simpleName.equals("short") || simpleName.equals("char")) { + return FieldAllocationType.SHORT; + } else if (simpleName.equals("int") || simpleName.equals("float")) { + return FieldAllocationType.WORD; + } else if (simpleName.equals("long") || simpleName.equals("double")) { + return FieldAllocationType.DOUBLE; + } + + return FieldAllocationType.BAD_ALLOCATION_TYPE; + } + @Override public ClassLayout layout(ClassData cd) { SortedSet result = new TreeSet(); @@ -69,64 +131,318 @@ int scale = model.sizeOf(cd.arrayComponentType()); long instanceSize = base + cd.arrayLength() * scale; - instanceSize = MathUtil.align(instanceSize, autoAlign ? Math.max(model.objectAlignment(), scale) : model.objectAlignment()); + instanceSize = MathUtil.align(instanceSize, model.objectAlignment()); base = MathUtil.align(base, Math.max(4, scale)); result.add(new FieldLayout(FieldData.create(cd.arrayClass(), "", cd.arrayComponentType()), base, scale * cd.arrayLength())); return new ClassLayout(cd, result, model.arrayHeaderSize(), instanceSize, false); } + EnumMap fieldsAllocationCount = new EnumMap(FieldAllocationType.class); + EnumMap nextOffset = new EnumMap(FieldAllocationType.class); + EnumMap spaceCount = new EnumMap(FieldAllocationType.class); + EnumMap spaceOffset = new EnumMap(FieldAllocationType.class); + EnumMap allocationTypeSizes = new EnumMap(FieldAllocationType.class); + + for (FieldAllocationType atype : FieldAllocationType.values()) { + fieldsAllocationCount.put(atype, 0); + nextOffset.put(atype, 0); + spaceCount.put(atype, 0); + spaceOffset.put(atype, 0); + } + allocationTypeSizes.put(FieldAllocationType.OOP, model.sizeOf("oop")); + allocationTypeSizes.put(FieldAllocationType.BYTE, model.sizeOf("byte")); + allocationTypeSizes.put(FieldAllocationType.SHORT, model.sizeOf("short")); + allocationTypeSizes.put(FieldAllocationType.WORD, model.sizeOf("int")); + allocationTypeSizes.put(FieldAllocationType.DOUBLE, model.sizeOf("long")); + List hierarchy = cd.classHierarchy(); + for (String k : hierarchy) { - BitSet claimed = new BitSet(); + Collection fields = cd.fieldsFor(k); + for (FieldData f : fields) { + FieldAllocationType atype = allocationTypeFor(f.refField()); + Integer count = (Integer) fieldsAllocationCount.get(atype); + fieldsAllocationCount.put(atype, ++count); + } + } - claimed.set(0, model.headerSize()); + int fieldSize = cd.superClass() == null ? 0 : cd.superClass().fieldSize(model); + int firstOopOffset = 0; + int nextFieldOffset = 0; + int nextPaddedOffset = 0; + // Count the contended fields by type. + int contendedCount = 0; + EnumMap facContended = new EnumMap(FieldAllocationType.class); + for (String k : hierarchy) { Collection fields = cd.fieldsFor(k); - - SortedSet current = new TreeSet(); - for (int size : new int[]{8, 4, 2, 1}) { - for (FieldData f : fields) { + for (FieldData f : fields) { - int fSize = model.sizeOf(f.typeClass()); - if (fSize != size) continue; + FieldAllocationType atype = allocationTypeFor(f.refField()); + if (f.isContended()) { + Integer count = facContended.get(atype); - for (int t = 0; t < Integer.MAX_VALUE; t++) { - if (claimed.get(t * size, (t + 1) * size).isEmpty()) { - claimed.set(t * size, (t + 1) * size); - current.add(new FieldLayout(f, t * size, size)); - break; + facContended.put(atype, count == null ? 1 : ++count); + contendedCount++; - } - } - } + } + } + } + + int fieldsStart = model.headerSize() + fieldSize * model.sizeOf("oop"); + + nextFieldOffset = fieldsStart; + + boolean isContendedClass = cd.isContended(); + + // Class is contended, pad before all the fields + if (isContendedClass) { + nextFieldOffset += CONTENDED_PADDING_WIDTH; - } + } - result.addAll(current); - if (takeSuperGaps) { - // do nothing - } else if (takeHierarchyGaps) { - // claim only the class body up to the field - int lastSet = claimed.length(); - claimed.set(0, lastSet); + // Compute the non-contended fields count. + // The packing code below relies on these counts to determine if some field + // can be squeezed into the alignment gap. Contended fields are obviously + // exempt from that. + int doubleCount = fieldsAllocationCount.get(FieldAllocationType.DOUBLE) - + (facContended.containsKey(FieldAllocationType.DOUBLE) ? facContended.get(FieldAllocationType.DOUBLE) : 0); + int wordCount = fieldsAllocationCount.get(FieldAllocationType.WORD) - + (facContended.containsKey(FieldAllocationType.WORD) ? facContended.get(FieldAllocationType.WORD) : 0); + int shortCount = fieldsAllocationCount.get(FieldAllocationType.SHORT) - + (facContended.containsKey(FieldAllocationType.SHORT) ? facContended.get(FieldAllocationType.SHORT) : 0); + int byteCount = fieldsAllocationCount.get(FieldAllocationType.BYTE) - + (facContended.containsKey(FieldAllocationType.BYTE) ? facContended.get(FieldAllocationType.BYTE) : 0); + int oopCount = fieldsAllocationCount.get(FieldAllocationType.OOP) - + (facContended.containsKey(FieldAllocationType.OOP) ? facContended.get(FieldAllocationType.OOP) : 0); + + firstOopOffset = 0; // will be set for first oop field + + boolean compactFields = this.compactFields; + int allocationStyle = this.fieldAllocationStyle; + if (allocationStyle < 0 || allocationStyle > 2) { + allocationStyle = 1; // Optimistic + } + + // Use default fields allocation order for classes, which have predefined hard-coded fields offsets. + if ((allocationStyle != 0 || compactFields) && + predefinedFieldsOffsetsClasses.contains(cd.name())) { + allocationStyle = 0; // Allocate oops first + compactFields = false; // Don't compact fields + } + + // Rearrange fields for a given allocation style + if (allocationStyle == 0) { + // Fields order: oops, longs/doubles, ints, shorts/chars, bytes, padded fields + nextOffset.put(FieldAllocationType.OOP, nextFieldOffset); + nextOffset.put(FieldAllocationType.DOUBLE, + nextOffset.get(FieldAllocationType.OOP) + (oopCount * model.sizeOf("oop"))); + } else if (allocationStyle == 1) { + // Fields order: longs/doubles, ints, shorts/chars, bytes, oops, padded fields + nextOffset.put(FieldAllocationType.DOUBLE, nextFieldOffset); + } else if (allocationStyle == 2) { + // Fields allocation: oops fields in super and sub classes are together. + if (allocationStyle == 2) { + allocationStyle = 1; // allocate oops last + nextOffset.put(FieldAllocationType.DOUBLE, nextFieldOffset); + } - } else { + } else { - // claim the entire class body, plus some alignment - int lastSet = claimed.length(); - claimed.set(0, MathUtil.align(lastSet, model.sizeOf("java.lang.Object"))); + throw new IllegalStateException(); - } + } + + // Try to squeeze some of the fields into the gaps due to + // long/double alignment. + if (doubleCount > 0) { + int offset = nextOffset.get(FieldAllocationType.DOUBLE); + nextOffset.put(FieldAllocationType.DOUBLE, MathUtil.align(offset, allocationTypeSizes.get(FieldAllocationType.DOUBLE))); + if (compactFields && offset != nextOffset.get(FieldAllocationType.DOUBLE)) { + // Allocate available fields into the gap before double field. + int length = nextOffset.get(FieldAllocationType.DOUBLE) - offset; + spaceOffset.put(FieldAllocationType.WORD, offset); + if (wordCount > 0) { + wordCount -= 1; + spaceCount.put(FieldAllocationType.WORD, 1); // Only one will fit + length -= allocationTypeSizes.get(FieldAllocationType.WORD); + offset += allocationTypeSizes.get(FieldAllocationType.WORD); - } + } + spaceOffset.put(FieldAllocationType.SHORT, offset); + while (length >= allocationTypeSizes.get(FieldAllocationType.SHORT) && shortCount > 0) { + shortCount -= 1; + spaceCount.put(FieldAllocationType.SHORT, spaceCount.get(FieldAllocationType.SHORT) + 1); + length -= allocationTypeSizes.get(FieldAllocationType.SHORT); + offset += allocationTypeSizes.get(FieldAllocationType.SHORT); + } + spaceOffset.put(FieldAllocationType.BYTE, offset); + while (length > 0 && byteCount > 0) { + byteCount -= 1; + spaceCount.put(FieldAllocationType.BYTE, spaceCount.get(FieldAllocationType.BYTE) + 1); + length -= 1; + } + // Allocate oop field in the gap if there are no other fields for that. + spaceOffset.put(FieldAllocationType.OOP, offset); + if (length >= allocationTypeSizes.get(FieldAllocationType.OOP) && oopCount > 0 && + allocationStyle != 0) { // when oop fields not first + oopCount -= 1; + spaceCount.put(FieldAllocationType.OOP, 1); // Only one will fit + length -= allocationTypeSizes.get(FieldAllocationType.OOP); + offset += allocationTypeSizes.get(FieldAllocationType.OOP); + } + } + } - int instanceSize; - if (autoAlign) { - int a = 4; - for (FieldLayout f : result) { - a = Math.max(a, model.sizeOf(f.typeClass())); + nextOffset.put(FieldAllocationType.WORD, nextOffset.get(FieldAllocationType.DOUBLE) + + (doubleCount * allocationTypeSizes.get(FieldAllocationType.DOUBLE))); + nextOffset.put(FieldAllocationType.SHORT, nextOffset.get(FieldAllocationType.WORD) + + (wordCount * allocationTypeSizes.get(FieldAllocationType.WORD))); + nextOffset.put(FieldAllocationType.BYTE, nextOffset.get(FieldAllocationType.SHORT) + + (shortCount * allocationTypeSizes.get(FieldAllocationType.SHORT))); + nextPaddedOffset = nextOffset.get(FieldAllocationType.BYTE) + byteCount; + + // let oops jump before padding with this allocation style + if (allocationStyle == 1) { + nextOffset.put(FieldAllocationType.OOP, nextPaddedOffset); + if (oopCount > 0) { + nextOffset.put(FieldAllocationType.OOP, MathUtil.align(nextOffset.get(FieldAllocationType.OOP), allocationTypeSizes.get(FieldAllocationType.OOP))); } - instanceSize = MathUtil.align(claimed.length(), a); + nextPaddedOffset = nextOffset.get(FieldAllocationType.OOP) + (oopCount * allocationTypeSizes.get(FieldAllocationType.OOP)); + } + + HashSet layoutedFields = new HashSet(); + + // Iterate over fields again and compute correct offsets. + // The field allocation type was temporarily stored in the offset slot. + // oop fields are located before non-oop fields. + for (String k : hierarchy) { + + Collection fields = cd.fieldsFor(k); + + for (FieldData f : fields) { + + // skip already laid out fields + if (layoutedFields.contains(f.toString())) continue; + + // contended instance fields are handled below + if (f.isContended()) continue; + + int realOffset = 0; + FieldAllocationType atype = allocationTypeFor(f.refField()); + int allocationTypeSize = allocationTypeSizes.get(atype); + int allocationTypeSpaceCount = spaceCount.get(atype); + int allocationTypeSpaceOffset = spaceOffset.get(atype); + + // pack the rest of the fields + if (atype == FieldAllocationType.DOUBLE) { + int nextDoubleOffset = nextOffset.get(atype); + realOffset = nextOffset.get(FieldAllocationType.DOUBLE); + nextOffset.put(atype, nextDoubleOffset + allocationTypeSize); + } else if (atype != FieldAllocationType.BAD_ALLOCATION_TYPE) { + if (allocationTypeSpaceCount > 0) { + realOffset = allocationTypeSpaceOffset; + spaceOffset.put(atype, allocationTypeSpaceOffset + allocationTypeSize); + spaceCount.put(atype, allocationTypeSpaceCount - 1); - } else { + } else { - instanceSize = MathUtil.align(claimed.length(), model.objectAlignment()); + int allocationTypeNextOffset = nextOffset.get(atype); + realOffset = allocationTypeNextOffset; + nextOffset.put(atype, allocationTypeNextOffset + allocationTypeSize); - } + } + } else { + throw new IllegalStateException(); + } + layoutedFields.add(f.toString()); + result.add(new FieldLayout(f, realOffset, model.sizeOf(f.typeClass()))); + } + } + + // Handle the contended cases. + // + // Each contended field should not intersect the cache line with another contended field. + // In the absence of alignment information, we end up with pessimistically separating + // the fields with full-width padding. + // + // Additionally, this should not break alignment for the fields, so we round the alignment up + // for each field. + if (contendedCount > 0) { + + // if there is at least one contended field, we need to have pre-padding for them + nextPaddedOffset += CONTENDED_PADDING_WIDTH; + + // collect all contended groups + HashSet contendedGroups = new HashSet(); + for (String k : hierarchy) { + + Collection fields = cd.fieldsFor(k); + + for (FieldData f : fields) { + if (f.isContended()) { + contendedGroups.add(f.contendedGroup()); + } + } + } + + for (String currentGroup : contendedGroups) { + + for (String k : hierarchy) { + + Collection fields = cd.fieldsFor(k); + + for (FieldData f : fields) { + + // skip already laid out fields + if (layoutedFields.contains(f.toString())) continue; + + // skip non-contended fields and fields from different group + if (!f.isContended() || !f.contendedGroup().equals(currentGroup)) continue; + + int realOffset = 0; + FieldAllocationType atype = allocationTypeFor(f.refField()); + + if (atype != FieldAllocationType.BAD_ALLOCATION_TYPE) { + int allocationTypeSize = allocationTypeSizes.get(atype); + nextPaddedOffset = MathUtil.align(nextPaddedOffset, allocationTypeSize); + realOffset = nextPaddedOffset; + nextPaddedOffset += allocationTypeSize; + + if (atype == FieldAllocationType.OOP && firstOopOffset == 0) { // Undefined + firstOopOffset = realOffset; + } + } else { + throw new IllegalStateException(); + } + + if (f.contendedGroup().equals("")) { + // Contended group defines the equivalence class over the fields: + // the fields within the same contended group are not inter-padded. + // The only exception is default group, which does not incur the + // equivalence, and so requires intra-padding. + nextPaddedOffset += CONTENDED_PADDING_WIDTH; + } + + result.add(new FieldLayout(f, realOffset, model.sizeOf(f.typeClass()))); + } + } + + // Start laying out the next group. + // Note that this will effectively pad the last group in the back; + // this is expected to alleviate memory contention effects for + // subclass fields and/or adjacent object. + // If this was the default group, the padding is already in place. + if (currentGroup != "") { + nextPaddedOffset += CONTENDED_PADDING_WIDTH; + } + } + } + + // Entire class is contended, pad in the back. + // This helps to alleviate memory contention effects for subclass fields + // and/or adjacent object. + if (isContendedClass) { + nextPaddedOffset += CONTENDED_PADDING_WIDTH; + } + + int notalignedFieldsEnd = nextPaddedOffset; + int instanceSize = MathUtil.align(notalignedFieldsEnd, model.objectAlignment()); + return new ClassLayout(cd, result, model.headerSize(), instanceSize, true); } @@ -135,6 +451,7 @@ return "VM Layout Simulation (" + model + (takeHierarchyGaps ? ", hierarchy gaps" : "") + (takeSuperGaps ? ", super gaps" : "") - + (autoAlign ? ", autoalign" : "") + ")"; + + (compactFields ? ", compact fields" : "") + + "field allocation style: " + fieldAllocationStyle + ")"; } } From aleksey.shipilev at oracle.com Mon Jun 6 16:22:13 2016 From: aleksey.shipilev at oracle.com (Aleksey Shipilev) Date: Mon, 6 Jun 2016 19:22:13 +0300 Subject: [PATCH] 7901019: jol estimates do not take AllocationStyle into the account In-Reply-To: <57549E0B.2020307@gmail.com> References: <56A2029B.5080909@oracle.com> <56A653F0.4060708@oracle.com> <56A6B258.1080807@gmail.com> <570F8C38.9040801@oracle.com> <57549E0B.2020307@gmail.com> Message-ID: <5755A335.4080200@oracle.com> Hi, On 06/06/2016 12:47 AM, ????? ?????????? wrote: > Hey there! I fixed tests, there were several bugs during determining > allocation types of java types, also I removed autoAlign option from > HotspotLayouter, because it seems, that now it is unnecessary, since we > emulate original Hotspot algorithm of layouting. Actually, autoAlign is needed to allow layout simulation (same with take{Hierarchy|Super}Gaps). HotspotLayouter in default mode should match the current VM layout precisely. I have corrected a few things in the patch, and added a new tests that matches the actual VM layout against HotspotLayouter: http://cr.openjdk.java.net/~shade/7901019/AllocationStyle-v2.patch This identified at least one bug: new code takes all fields from the entire hierarchy, and lays them out. This is not how a real layouter works: it works on fields of each class in isolation. Actual layout: Class9943 object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 1 boolean Class9942.field0 N/A 13 3 (alignment/padding gap) N/A 16 8 double Class9943.field0 N/A 24 2 short Class9943.field1 N/A 26 6 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 3 bytes internal + 6 bytes external = 9 bytes total VM Layout Simulation (current, field allocation style: 2) Class9943 object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 (alignment/padding gap) N/A 16 8 double Class9943.field0 N/A 24 2 short Class9943.field1 N/A 26 1 boolean Class9942.field0 N/A 27 5 (loss due to the next object alignment) Instance size: 32 bytes Space losses: 4 bytes internal + 5 bytes external = 9 bytes total Notice how Class9942 field is laid out after all field in patched HotspotLayouter. This is incorrect. Once you fix that, take{Hierarchy|Super}Gaps gain meaning: hierarchy gap is a gap due to "next class field block" alignment, and super gap is the hole left in superclass after layout, that may be used to put some subclass fields. Thanks, -Aleksey From enis.soz at gmail.com Tue Jun 7 00:47:20 2016 From: enis.soz at gmail.com (=?UTF-8?Q?Enis_S=C3=B6ztutar?=) Date: Mon, 6 Jun 2016 17:47:20 -0700 Subject: sizeOf() calculation from ClassLayout seems to be wrong Message-ID: Hi, I was testing ClassLayout with small byte[]'s, and noticed something with a byte[] size of 9: [B object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) f5 00 00 f8 (11110101 00000000 00000000 11111000) (-134217483) 12 4 (object header) 11 00 00 00 (00010001 00000000 00000000 00000000) (17) 16 17 byte [B. N/A 33 7 (loss due to the next object alignment) Instance size: 40 bytes Space losses: 0 bytes internal + 7 bytes external = 7 bytes total We seem to be calculating an extra 8 bytes in the byte[] layout after the header. The instance size is calculated as 40, however, VM.current().sizeOf() returns 32. This little UT also fails with the latest source code base: public class ArraySizeTest { @Test public void testByteArraySize() { System.err.println(VM.current().details()); // debug for (int i = 0; i < 100; i++) { byte[] b = new byte[i]; long vmSize = VM.current().sizeOf(i); long clSize = ClassLayout.parseInstance(b).instanceSize(); Assert.assertEquals("sizeOf() from VM and ClassLayout does not match, VM.sizeOf()=" + vmSize + ", ClassLayout.instanceSize()=" + clSize, vmSize, clSize ); } } } I was not able to open a JIRA issue for this, figured I can report here. Thanks, Enis From aleksey.shipilev at oracle.com Tue Jun 7 07:26:17 2016 From: aleksey.shipilev at oracle.com (Aleksey Shipilev) Date: Tue, 7 Jun 2016 10:26:17 +0300 Subject: sizeOf() calculation from ClassLayout seems to be wrong In-Reply-To: References: Message-ID: <57567719.9080304@oracle.com> Hi, On 06/07/2016 03:47 AM, Enis S?ztutar wrote: > [B object internals: > > OFFSET SIZE TYPE DESCRIPTION VALUE > 0 4 (object header) 01 00 00 00 (00000001 > 00000000 00000000 00000000) (1) > 4 4 (object header) 00 00 00 00 (00000000 > 00000000 00000000 00000000) (0) > 8 4 (object header) f5 00 00 f8 (11110101 > 00000000 00000000 11111000) (-134217483) > 12 4 (object header) 11 00 00 00 (00010001 > 00000000 00000000 00000000) (17) > 16 17 byte [B. N/A > 33 7 (loss due to the next object alignment) > > Instance size: 40 bytes This is a layout for byte[17] -- notice the length field which says "17". And this is a correct layout. > This little UT also fails with the latest source code base: > > public class ArraySizeTest { > > @Test > public void testByteArraySize() { > System.err.println(VM.current().details()); // debug > > for (int i = 0; i < 100; i++) { > byte[] b = new byte[i]; > long vmSize = VM.current().sizeOf(i); This calculates the size of boxed Integer "i", not byte[] "b" ;) For sure, it is 16 bytes always. If you sizeOf(b), then the test passes on all i-s in [0, 100000]. > long clSize = ClassLayout.parseInstance(b).instanceSize(); > Assert.assertEquals("sizeOf() from VM and ClassLayout does not match, > VM.sizeOf()=" > + vmSize + ", ClassLayout.instanceSize()=" + clSize, vmSize, clSize > ); > } > } > } Thanks, -Aleksey From enis.soz at gmail.com Tue Jun 7 08:42:56 2016 From: enis.soz at gmail.com (=?UTF-8?Q?Enis_S=C3=B6ztutar?=) Date: Tue, 7 Jun 2016 01:42:56 -0700 Subject: sizeOf() calculation from ClassLayout seems to be wrong In-Reply-To: <57567719.9080304@oracle.com> References: <57567719.9080304@oracle.com> Message-ID: Thanks, I was trying to simplify a more complex method in some other test and seems it got mixed up. Sorry for the confusion. Enis On Tue, Jun 7, 2016 at 12:26 AM, Aleksey Shipilev < aleksey.shipilev at oracle.com> wrote: > Hi, > > On 06/07/2016 03:47 AM, Enis S?ztutar wrote: > > [B object internals: > > > > OFFSET SIZE TYPE DESCRIPTION VALUE > > 0 4 (object header) 01 00 00 00 (00000001 > > 00000000 00000000 00000000) (1) > > 4 4 (object header) 00 00 00 00 (00000000 > > 00000000 00000000 00000000) (0) > > 8 4 (object header) f5 00 00 f8 (11110101 > > 00000000 00000000 11111000) (-134217483) > > 12 4 (object header) 11 00 00 00 (00010001 > > 00000000 00000000 00000000) (17) > > 16 17 byte [B. N/A > > 33 7 (loss due to the next object alignment) > > > > Instance size: 40 bytes > > This is a layout for byte[17] -- notice the length field which says > "17". And this is a correct layout. > > > This little UT also fails with the latest source code base: > > > > public class ArraySizeTest { > > > > @Test > > public void testByteArraySize() { > > System.err.println(VM.current().details()); // debug > > > > for (int i = 0; i < 100; i++) { > > byte[] b = new byte[i]; > > long vmSize = VM.current().sizeOf(i); > > This calculates the size of boxed Integer "i", not byte[] "b" ;) For > sure, it is 16 bytes always. If you sizeOf(b), then the test passes on > all i-s in [0, 100000]. > > > long clSize = ClassLayout.parseInstance(b).instanceSize(); > > Assert.assertEquals("sizeOf() from VM and ClassLayout does not > match, > > VM.sizeOf()=" > > + vmSize + ", ClassLayout.instanceSize()=" + clSize, vmSize, > clSize > > ); > > } > > } > > } > > Thanks, > -Aleksey > > From volker.simonis at gmail.com Tue Jun 7 08:59:17 2016 From: volker.simonis at gmail.com (Volker Simonis) Date: Tue, 7 Jun 2016 10:59:17 +0200 Subject: sizeOf() calculation from ClassLayout seems to be wrong In-Reply-To: References: Message-ID: On Tue, Jun 7, 2016 at 2:47 AM, Enis S?ztutar wrote: > Hi, > > I was testing ClassLayout with small byte[]'s, and noticed something with a > byte[] size of 9: > > [B object internals: > > OFFSET SIZE TYPE DESCRIPTION VALUE > > 0 4 (object header) 01 00 00 00 (00000001 > 00000000 00000000 00000000) (1) > > 4 4 (object header) 00 00 00 00 (00000000 > 00000000 00000000 00000000) (0) > > 8 4 (object header) f5 00 00 f8 (11110101 > 00000000 00000000 11111000) (-134217483) > > 12 4 (object header) 11 00 00 00 (00010001 > 00000000 00000000 00000000) (17) > > 16 17 byte [B. N/A > > 33 7 (loss due to the next object alignment) > > Instance size: 40 bytes > > Space losses: 0 bytes internal + 7 bytes external = 7 bytes total > > We seem to be calculating an extra 8 bytes in the byte[] layout after the > header. The instance size is calculated as 40, however, > VM.current().sizeOf() returns 32. Hi Enis, I'm not sure what ClassLayout is reporting, but after the header, an array first has its length field (which is an int) and thereafter the array elements begin on a 8-byte aligned address. So if you're running without compressed oops you have the following layout: (gdb) call find($r3) "Executing find" 0x00003ffe3fe99458 is an oop [B {0x00003ffe3fe99458} - klass: {type array byte} - length: 9 - 0: 1 - 1: 2 - 2: 3 - 3: 4 - 4: 5 - 5: 6 - 6: 7 - 7: 8 - 8: 9 (gdb) x /12x $r3 0x3ffe3fe99458: 0x00000001 0x00000000 0x167007d0 0x00003ffe 0x3ffe3fe99468: 0x00000009 0x00000000 0x04030201 0x08070605 0x3ffe3fe99478: 0x00000009 0x00000000 0xbaadbabe 0xbaadbabe The first 16 bytes are the object header. At address 0x3ffe3fe99468 you have the arrays size (9) and at 0x3ffe3fe99470 the start of the array elements. Notice the you loose 4 bytes because the start of the elements has to be 8-byte aligned. Also the array is padded with 4 bytes at the end to finish at a 8-byte aligned address. So in total the array occupies 40 bytes in the heap. With compressed oops, the layout looks as follows: (gdb) call find($r3) "Executing find" 0x00000000c3e1a928 is an oop [B {0x00000000c3e1a928} - klass: {type array byte} - length: 9 - 0: 1 - 1: 2 - 2: 3 - 3: 4 - 4: 5 - 5: 6 - 6: 7 - 7: 8 - 8: 9 (gdb) x /12x $r3 0xc3e1a928: 0x00000001 0x00000000 0x200000fa 0x00000009 0xc3e1a938: 0x04030201 0x08070605 0x00000009 0x00000000 0xc3e1a948: 0xbaadbabe 0xbaadbabe 0xbaadbabe 0xbaadbabe Now the object header is 12 bytes and the arrays length field fits into the first 16 bytes. The array elements start at 0xc3e1a938 but again they have to finish at an 8-byte aligned address so they are padded with 4 bytes at the end. In total, the array occupies 32 bytes in compressed oops mode. Regards, Volker PS: gdb is your friend :) If you happen to be in Tallin the next days you can come and hear http://2016.geekout.ee/schedule/hotspot-debugging-at-the-os-level (or watch an older version online: https://www.youtube.com/watch?v=JZpEskA_89U) > > > This little UT also fails with the latest source code base: > > public class ArraySizeTest { > > @Test > > public void testByteArraySize() { > > System.err.println(VM.current().details()); // debug > > for (int i = 0; i < 100; i++) { > > byte[] b = new byte[i]; > > long vmSize = VM.current().sizeOf(i); > > long clSize = ClassLayout.parseInstance(b).instanceSize(); > > Assert.assertEquals("sizeOf() from VM and ClassLayout does not match, > VM.sizeOf()=" > > + vmSize + ", ClassLayout.instanceSize()=" + clSize, vmSize, clSize > ); > > } > > } > > } > > I was not able to open a JIRA issue for this, figured I can report here. > > Thanks, > > Enis From aleksey.shipilev at oracle.com Fri Jun 10 09:00:11 2016 From: aleksey.shipilev at oracle.com (aleksey.shipilev at oracle.com) Date: Fri, 10 Jun 2016 09:00:11 +0000 Subject: hg: code-tools/jol: jol-cli: "shapes" tool, provides the class shapes in JAR/heapdumps. Message-ID: <201606100900.u5A90BDO021398@aojmv0008.oracle.com> Changeset: 5f7a8eb55932 Author: shade Date: 2016-06-10 11:38 +0300 URL: http://hg.openjdk.java.net/code-tools/jol/rev/5f7a8eb55932 jol-cli: "shapes" tool, provides the class shapes in JAR/heapdumps. ! jol-cli/src/main/java/org/openjdk/jol/Main.java + jol-cli/src/main/java/org/openjdk/jol/operations/ObjectShapes.java ! jol-core/src/main/java/org/openjdk/jol/info/ClassData.java ! jol-core/src/main/java/org/openjdk/jol/util/Multiset.java From aleksey.shipilev at oracle.com Fri Jun 10 09:00:20 2016 From: aleksey.shipilev at oracle.com (aleksey.shipilev at oracle.com) Date: Fri, 10 Jun 2016 09:00:20 +0000 Subject: hg: code-tools/jol: jol-cli: "heapdumpstats" tool, provides the brief heapdump statistics. Message-ID: <201606100900.u5A90Kob021587@aojmv0008.oracle.com> Changeset: 30c3fbadf049 Author: shade Date: 2016-06-10 11:59 +0300 URL: http://hg.openjdk.java.net/code-tools/jol/rev/30c3fbadf049 jol-cli: "heapdumpstats" tool, provides the brief heapdump statistics. ! jol-cli/src/main/java/org/openjdk/jol/Main.java + jol-cli/src/main/java/org/openjdk/jol/operations/HeapDumpStats.java