Skip to content

Instantly share code, notes, and snippets.

@raphw
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raphw/7345bae62abd424fb040 to your computer and use it in GitHub Desktop.
Save raphw/7345bae62abd424fb040 to your computer and use it in GitHub Desktop.
Modified version of JOL's class layout
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.jol.info;
import org.openjdk.jol.layouters.CurrentLayouter;
import org.openjdk.jol.layouters.Layouter;
import org.openjdk.jol.util.VMSupport;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
/**
* Handles the class data *with* the layout information.
*/
public class ClassLayout {
/**
* Produce the class layout for the given class.
* This produces the layout for the current VM.
*
* @param klass class to work on
* @return class layout
*/
public static ClassLayout parseClass(Class<?> klass) {
return parseClass(klass, new CurrentLayouter());
}
/**
* Produce the class layout for the given class, and given layouter.
*
* @param klass class to work on
* @param layouter class layouter
* @return class layout
*/
public static ClassLayout parseClass(Class<?> klass, Layouter layouter) {
return layouter.layout(ClassData.parseClass(klass));
}
private final ClassData classData;
private final SortedSet<FieldLayout> fields;
private final int headerSize;
private final int size;
private final boolean detailedHeader;
/**
* Builds the class layout.
*
* @param classData class data
* @param fields field layouts
* @param headerSize header size
* @param instanceSize instance size
* @param check whether to check important invariants
*/
public ClassLayout(ClassData classData, SortedSet<FieldLayout> fields, int headerSize, int instanceSize, boolean check) {
this.classData = classData;
this.fields = fields;
this.headerSize = headerSize;
this.size = instanceSize;
detailedHeader = false;
if (check) {
checkInvariants();
}
}
private ClassLayout(ClassData classData, SortedSet<FieldLayout> fields, int headerSize, int instanceSize, boolean check, boolean detailedHeader) {
this.classData = classData;
this.fields = fields;
this.headerSize = headerSize;
this.size = instanceSize;
this.detailedHeader = detailedHeader;
if (check) {
checkInvariants();
}
}
private void checkInvariants() {
long lastOffset = 0;
for (FieldLayout f : fields) {
if (f.offset() % f.size() != 0) {
throw new IllegalStateException("Field " + f + " is not aligned");
}
if (f.offset() + f.size() > instanceSize()) {
throw new IllegalStateException("Field " + f + " is overflowing the object of size " + instanceSize());
}
if (f.offset() < lastOffset) {
throw new IllegalStateException("Field " + f + " overlaps with the previous field");
}
lastOffset = f.offset() + f.size();
}
}
/**
* Answer the set of fields, including those in superclasses
*
* @return sorted set of fields
*/
public SortedSet<FieldLayout> fields() {
return fields;
}
/**
* Answer instance size
*
* @return instance size
*/
public int instanceSize() {
return size;
}
/**
* Answer header size
*
* @return header size
*/
public int headerSize() {
return headerSize;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (FieldLayout f : fields()) {
sb.append(f).append("\n");
}
sb.append("size = ").append(size).append("\n");
return sb.toString();
}
/**
* Produce printable stringly representation of class layout.
* This method does not require alive instance, just the class.
*
* @return human-readable layout info
*/
public String toPrintable() {
return toPrintable(null);
}
/**
* Produce printable stringly representation of class layout.
* This method accepts instance to read the actual data from.
*
* @param instance instance to work on
* @return human-readable layout info
*/
public String toPrintable(Object instance) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
int maxTypeLen = 5;
for (FieldLayout f : fields()) {
maxTypeLen = Math.max(f.typeClass().length(), maxTypeLen);
}
int maxDescrLen = 30;
for (FieldLayout f : fields()) {
maxDescrLen = Math.max((f.hostClass() + "" + f.name()).length(), maxDescrLen);
}
pw.println(classData.name() + " object internals:");
pw.printf(" %6s %5s %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", "OFFSET", "SIZE", "TYPE", "DESCRIPTION", "VALUE");
if (instance != null) {
if (detailedHeader) {
ObjectHeaderLayout.make().print(pw, instance, maxTypeLen, maxDescrLen);
} else {
for (long off = 0; off < headerSize(); off += 4) {
pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", off, 4, "", "(object header)",
toHex(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
toHex(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
toHex(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
toHex(VMSupport.U.getByte(instance, off + 3) & 0xFF) + " " +
"(" +
toBinary(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
toBinary(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
toBinary(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
toBinary(VMSupport.U.getByte(instance, off + 3) & 0xFF) + ")"
);
}
}
} else {
pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", 0, headerSize(), "", "(object header)", "N/A");
}
int nextFree = headerSize();
int interLoss = 0;
int exterLoss = 0;
for (FieldLayout f : fields()) {
if (f.offset() > nextFree) {
pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", nextFree, (f.offset() - nextFree), "", "(alignment/padding gap)", "N/A");
interLoss += (f.offset() - nextFree);
}
pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n",
f.offset(),
f.size(),
f.typeClass(),
f.hostClass() + "." + f.name(),
(instance != null) ? f.safeValue(instance) : "N/A"
);
nextFree = f.offset() + f.size();
}
VMSupport.SizeInfo info = VMSupport.tryExactObjectSize(instance, this);
if (info.instanceSize() != nextFree) {
exterLoss = info.instanceSize() - nextFree;
pw.printf(" %6d %5s %" + maxTypeLen + "s %s%n", nextFree, exterLoss, "", "(loss due to the next object alignment)");
}
if (info.exactSize()) {
pw.printf("Instance size: %d bytes (reported by VM agent)%n", info.instanceSize());
} else {
if (instance != null) {
pw.printf("Instance size: %d bytes (estimated, add this JAR via -javaagent: to get accurate result)%n", info.instanceSize());
} else {
pw.printf("Instance size: %d bytes (estimated, the sample instance is not available)%n", info.instanceSize());
}
}
pw.printf("Space losses: %d bytes internal + %d bytes external = %d bytes total%n", interLoss, exterLoss, interLoss + exterLoss);
pw.close();
return sw.toString();
}
// very ineffective, so what?
private static String toBinary(int x) {
String s = Integer.toBinaryString(x);
int deficit = 8 - s.length();
for (int c = 0; c < deficit; c++) {
s = "0" + s;
}
return s.substring(0, 4) + " " + s.substring(4);
}
// very ineffective, so what?
private static String toHex(int x) {
String s = Integer.toHexString(x);
int deficit = 2 - s.length();
for (int c = 0; c < deficit; c++) {
s = "0" + s;
}
return s;
}
public ClassLayout withDetailedHeader() {
return new ClassLayout(classData, fields, headerSize, size, false, true);
}
private static class ObjectHeaderLayout {
public static ObjectHeaderLayout make() {
switch (VMSupport.ADDRESS_SIZE) {
case 8:
return new ObjectHeaderLayout(Arrays.asList(new Entry(2, "lock"),
new Entry(1, "biased lock"),
new Entry(4, "age"),
new Entry(1, "(unused)"),
new Entry(31, "hash code"),
new Entry(25, "(unused)"),
new Entry(VMSupport.USE_COMPRESSED_REFS ? 32 : 64, "class pointer")));
case 4:
return new ObjectHeaderLayout(Arrays.asList(new Entry(2, "lock"),
new Entry(1, "biased lock"),
new Entry(4, "age"),
new Entry(25, "hash code"),
new Entry(32, "class pointer")));
default:
throw new IllegalStateException("Unknown address size: " + VMSupport.ADDRESS_SIZE);
}
}
public void print(PrintWriter pw, Object instance, int maxTypeLen, int maxDescrLen) {
int bitOffset = 0;
for (Entry entry : entries) {
int rangeValue = 0;
int valueOffset = 0;
for (long byteOffset = bitOffset / 8;
byteOffset < (bitOffset + entry.bitLength) / 8 + ((bitOffset + entry.bitLength) % 8 == 0 ? 0 : 1);
byteOffset++) {
int currentByte = VMSupport.U.getByte(instance, byteOffset) & 0xFF;
if (byteOffset * 8 < bitOffset) {
currentByte &= 0xFF >> (bitOffset % 8);
}
if (byteOffset * 8 + 8 > bitOffset + entry.bitLength) {
currentByte &= (0xFF << (8 - ((bitOffset + entry.bitLength) % 8))) & 0xFF;
}
rangeValue |= currentByte << (valueOffset * 8);
valueOffset++;
}
pw.printf(" %6s %5s %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n",
bitOffset / 8 + "+" + bitOffset % 8, entry.bitLength / 8 + "+" + entry.bitLength % 8, "", entry.name,
Integer.toHexString(rangeValue) + " (" + Integer.toBinaryString(rangeValue) + ")");
bitOffset += entry.bitLength;
}
}
private final List<Entry> entries;
private ObjectHeaderLayout(List<Entry> entries) {
this.entries = entries;
}
private static class Entry {
private final int bitLength;
private final String name;
private Entry(int bitLength, String name) {
this.bitLength = bitLength;
this.name = name;
}
}
}
}
Index: jol-core/src/main/java/org/openjdk/jol/info/ClassLayout.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jol-core/src/main/java/org/openjdk/jol/info/ClassLayout.java (revision 31:9a546334aa57fa0e114db159b2a8a5345c249b3e)
+++ jol-core/src/main/java/org/openjdk/jol/info/ClassLayout.java (revision 31+:9a546334aa57+)
@@ -30,6 +30,8 @@
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
import java.util.SortedSet;
/**
@@ -64,6 +66,8 @@
private final int headerSize;
private final int size;
+ private final boolean detailedHeader;
+
/**
* Builds the class layout.
*
@@ -78,11 +82,23 @@
this.fields = fields;
this.headerSize = headerSize;
this.size = instanceSize;
+ detailedHeader = false;
if (check) {
checkInvariants();
}
}
+ private ClassLayout(ClassData classData, SortedSet<FieldLayout> fields, int headerSize, int instanceSize, boolean check, boolean detailedHeader) {
+ this.classData = classData;
+ this.fields = fields;
+ this.headerSize = headerSize;
+ this.size = instanceSize;
+ this.detailedHeader = detailedHeader;
+ if (check) {
+ checkInvariants();
+ }
+ }
+
private void checkInvariants() {
long lastOffset = 0;
for (FieldLayout f : fields) {
@@ -170,19 +186,23 @@
pw.println(classData.name() + " object internals:");
pw.printf(" %6s %5s %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", "OFFSET", "SIZE", "TYPE", "DESCRIPTION", "VALUE");
if (instance != null) {
+ if (detailedHeader) {
+ ObjectHeaderLayout.make().print(pw, instance, maxTypeLen, maxDescrLen);
+ } else {
- for (long off = 0; off < headerSize(); off += 4) {
- pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", off, 4, "", "(object header)",
- toHex(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
- toHex(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
- toHex(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
- toHex(VMSupport.U.getByte(instance, off + 3) & 0xFF) + " " +
- "(" +
- toBinary(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
- toBinary(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
- toBinary(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
- toBinary(VMSupport.U.getByte(instance, off + 3) & 0xFF) + ")"
- );
- }
+ for (long off = 0; off < headerSize(); off += 4) {
+ pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", off, 4, "", "(object header)",
+ toHex(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
+ toHex(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
+ toHex(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
+ toHex(VMSupport.U.getByte(instance, off + 3) & 0xFF) + " " +
+ "(" +
+ toBinary(VMSupport.U.getByte(instance, off + 0) & 0xFF) + " " +
+ toBinary(VMSupport.U.getByte(instance, off + 1) & 0xFF) + " " +
+ toBinary(VMSupport.U.getByte(instance, off + 2) & 0xFF) + " " +
+ toBinary(VMSupport.U.getByte(instance, off + 3) & 0xFF) + ")"
+ );
+ }
+ }
} else {
pw.printf(" %6d %5d %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n", 0, headerSize(), "", "(object header)", "N/A");
}
@@ -253,4 +273,74 @@
return s;
}
+ public ClassLayout withDetailedHeader() {
+ return new ClassLayout(classData, fields, headerSize, size, false, true);
+ }
+
+ private static class ObjectHeaderLayout {
+
+ public static ObjectHeaderLayout make() {
+ switch (VMSupport.ADDRESS_SIZE) {
+ case 8:
+ return new ObjectHeaderLayout(Arrays.asList(new Entry(2, "lock"),
+ new Entry(1, "biased lock"),
+ new Entry(4, "age"),
+ new Entry(1, "(unused)"),
+ new Entry(31, "hash code"),
+ new Entry(25, "(unused)"),
+ new Entry(VMSupport.USE_COMPRESSED_REFS ? 32 : 64, "class pointer")));
+ case 4:
+ return new ObjectHeaderLayout(Arrays.asList(new Entry(2, "lock"),
+ new Entry(1, "biased lock"),
+ new Entry(4, "age"),
+ new Entry(25, "hash code"),
+ new Entry(32, "class pointer")));
+ default:
+ throw new IllegalStateException("Unknown address size: " + VMSupport.ADDRESS_SIZE);
+ }
+ }
+
+ public void print(PrintWriter pw, Object instance, int maxTypeLen, int maxDescrLen) {
+ int bitOffset = 0;
+ for (Entry entry : entries) {
+ int rangeValue = 0;
+ int valueOffset = 0;
+ for (long byteOffset = bitOffset / 8;
+ byteOffset < (bitOffset + entry.bitLength) / 8 + ((bitOffset + entry.bitLength) % 8 == 0 ? 0 : 1);
+ byteOffset++) {
+ int currentByte = VMSupport.U.getByte(instance, byteOffset) & 0xFF;
+ if (byteOffset * 8 < bitOffset) {
+ currentByte &= 0xFF >> (bitOffset % 8);
+ }
+ if (byteOffset * 8 + 8 > bitOffset + entry.bitLength) {
+ currentByte &= (0xFF << (8 - ((bitOffset + entry.bitLength) % 8))) & 0xFF;
+ }
+ rangeValue |= currentByte << (valueOffset * 8);
+ valueOffset++;
+ }
+ pw.printf(" %6s %5s %" + maxTypeLen + "s %-" + maxDescrLen + "s %s%n",
+ bitOffset / 8 + "+" + bitOffset % 8, entry.bitLength / 8 + "+" + entry.bitLength % 8, "", entry.name,
+ Integer.toHexString(rangeValue) + " (" + Integer.toBinaryString(rangeValue) + ")");
+ bitOffset += entry.bitLength;
+ }
+ }
+
+ private final List<Entry> entries;
+
+ private ObjectHeaderLayout(List<Entry> entries) {
+ this.entries = entries;
+ }
+
+ private static class Entry {
+
+ private final int bitLength;
+
+ private final String name;
+
+ private Entry(int bitLength, String name) {
+ this.bitLength = bitLength;
+ this.name = name;
+ }
+ }
+ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment