diff --git a/lucene/core/src/java/org/apache/lucene/util/Constants.java b/lucene/core/src/java/org/apache/lucene/util/Constants.java
index 3ee60defc3f..d18fedaec3b 100644
--- a/lucene/core/src/java/org/apache/lucene/util/Constants.java
+++ b/lucene/core/src/java/org/apache/lucene/util/Constants.java
@@ -27,6 +27,11 @@ import org.apache.lucene.LucenePackage;
public final class Constants {
private Constants() {} // can't construct
+ /** JVM vendor info. */
+ public static final String JVM_VENDOR = System.getProperty("java.vm.vendor");
+ public static final String JVM_VERSION = System.getProperty("java.vm.version");
+ public static final String JVM_NAME = System.getProperty("java.vm.name");
+
/** The value of System.getProperty("java.version"). **/
public static final String JAVA_VERSION = System.getProperty("java.version");
diff --git a/lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java b/lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java
index 30fddc58739..377db0f85b2 100644
--- a/lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java
+++ b/lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java
@@ -24,14 +24,50 @@ import java.text.DecimalFormatSymbols;
import java.util.*;
/**
- * Estimates the size of Java objects using a simple memory model
- * for primitive size information.
+ * Estimates the size (memory representation) of Java objects.
+ *
+ * @see #sizeOf(Object)
+ * @see #shallowSizeOf(Object)
+ * @see #shallowSizeOfInstance(Class)
*
* @lucene.internal
*/
public final class RamUsageEstimator {
+ /**
+ * JVM diagnostic features.
+ */
+ public static enum JvmFeature {
+ OBJECT_REFERENCE_SIZE("Object reference size estimated using array index scale."),
+ ARRAY_HEADER_SIZE("Array header size estimated using array based offset."),
+ FIELD_OFFSETS("Shallow instance size based on field offsets."),
+ OBJECT_ALIGNMENT("Object alignment retrieved from HotSpotDiagnostic MX bean.");
+
+ public final String description;
+
+ private JvmFeature(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return super.name() + " (" + description + ")";
+ }
+ }
+
+ /** JVM info string for debugging and reports. */
+ public final static String JVM_INFO_STRING;
+
+ /** One kilobyte bytes. */
+ public static final long ONE_KB = 1024;
- private RamUsageEstimator() {} // no instance
+ /** One megabyte bytes. */
+ public static final long ONE_MB = ONE_KB * ONE_KB;
+
+ /** One gigabyte bytes.*/
+ public static final long ONE_GB = ONE_KB * ONE_MB;
+
+ /** No instantiation. */
+ private RamUsageEstimator() {}
public final static int NUM_BYTES_BOOLEAN = 1;
public final static int NUM_BYTES_BYTE = 1;
@@ -42,9 +78,19 @@ public final class RamUsageEstimator {
public final static int NUM_BYTES_LONG = 8;
public final static int NUM_BYTES_DOUBLE = 8;
+ /**
+ * Number of bytes this jvm uses to represent an object reference.
+ */
public final static int NUM_BYTES_OBJECT_REF;
-
+
+ /**
+ * Number of bytes to represent an object header (no fields, no alignments).
+ */
public final static int NUM_BYTES_OBJECT_HEADER;
+
+ /**
+ * Number of bytes to represent an array header (no content, but with alignments).
+ */
public final static int NUM_BYTES_ARRAY_HEADER;
/**
@@ -69,9 +115,20 @@ public final class RamUsageEstimator {
primitiveSizes.put(long.class, Integer.valueOf(NUM_BYTES_LONG));
}
+ /**
+ * A handle to sun.misc.Unsafe.
+ */
private final static Object theUnsafe;
+
+ /**
+ * A handle to sun.misc.Unsafe#fieldOffset(Field).
+ */
private final static Method objectFieldOffsetMethod;
- private final static boolean useUnsafe, isSupportedJVM;
+
+ /**
+ * All the supported "internal" JVM features detected at clinit.
+ */
+ private final static EnumSet supportedFeatures;
/**
* Initialize constants and try to collect information about the JVM internals.
@@ -85,80 +142,71 @@ public final class RamUsageEstimator {
// so on 64 bit JVMs it'll be align(16 + 4, @8) = 24.
int arrayHeader = Constants.JRE_IS_64BIT ? 24 : 12;
- Object unsafe = null;
- Method objectFieldOffsetM = null;
- boolean supportedJvm = true;
+ supportedFeatures = EnumSet.noneOf(JvmFeature.class);
+
+ Class> unsafeClass = null;
+ Object tempTheUnsafe = null;
try {
- final Class> unsafeClass = Class.forName("sun.misc.Unsafe");
+ unsafeClass = Class.forName("sun.misc.Unsafe");
final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
- unsafe = unsafeField.get(null);
-
- // get object reference size by getting scale factor of Object[] arrays:
- try {
- final Method arrayIndexScaleM = unsafeClass.getMethod("arrayIndexScale", Class.class);
- referenceSize = ((Number) arrayIndexScaleM.invoke(unsafe, Object[].class)).intValue();
- } catch (Exception e) {
- // ignore
- supportedJvm = false;
- }
-
- // updated best guess based on reference size:
- objectHeader = Constants.JRE_IS_64BIT ? (8 + referenceSize) : 8;
- arrayHeader = Constants.JRE_IS_64BIT ? (8 + 2 * referenceSize) : 12;
-
- // get the object header size:
- // - first try out if the field offsets are not scaled (see warning in Unsafe docs)
- // - get the object header size by getting the field offset of the first field of a dummy object
- // If the scaling is byte-wise and unsafe is available, enable dynamic size measurement for
- // estimateRamUsage().
- try {
- objectFieldOffsetM = unsafeClass.getMethod("objectFieldOffset", Field.class);
- final Field dummy1Field = DummyTwoLongObject.class.getDeclaredField("dummy1");
- final int ofs1 = ((Number) objectFieldOffsetM.invoke(unsafe, dummy1Field)).intValue();
- final Field dummy2Field = DummyTwoLongObject.class.getDeclaredField("dummy2");
- final int ofs2 = ((Number) objectFieldOffsetM.invoke(unsafe, dummy2Field)).intValue();
- if (Math.abs(ofs2 - ofs1) == NUM_BYTES_LONG) {
- final Field baseField = DummyOneFieldObject.class.getDeclaredField("base");
- objectHeader = ((Number) objectFieldOffsetM.invoke(unsafe, baseField)).intValue();
- } else {
- // it is not safe to use Unsafe.objectFieldOffset(),
- // as it may be scaled (see "cookie" comment in Unsafe), better use defaults
- // and conventional size estimation:
- objectFieldOffsetM = null;
- supportedJvm = false;
- }
- } catch (Exception e) {
- // on exception ensure useUnsafe will be set to false later:
- objectFieldOffsetM = null;
- supportedJvm = false;
- }
+ tempTheUnsafe = unsafeField.get(null);
+ } catch (Exception e) {
+ // Ignore.
+ }
+ theUnsafe = tempTheUnsafe;
- // Get the array header size by retrieving the array base offset
- // (offset of the first element of an array).
- try {
- final Method arrayBaseOffsetM = unsafeClass.getMethod("arrayBaseOffset", Class.class);
- // we calculate that only for byte[] arrays, it's actually the same for all types:
- arrayHeader = ((Number) arrayBaseOffsetM.invoke(unsafe, byte[].class)).intValue();
- } catch (Exception e) {
- // ignore
- supportedJvm = false;
+ // get object reference size by getting scale factor of Object[] arrays:
+ try {
+ final Method arrayIndexScaleM = unsafeClass.getMethod("arrayIndexScale", Class.class);
+ referenceSize = ((Number) arrayIndexScaleM.invoke(theUnsafe, Object[].class)).intValue();
+ supportedFeatures.add(JvmFeature.OBJECT_REFERENCE_SIZE);
+ } catch (Exception e) {
+ // ignore.
+ }
+
+ // "best guess" based on reference size. We will attempt to modify
+ // these to exact values if there is supported infrastructure.
+ objectHeader = Constants.JRE_IS_64BIT ? (8 + referenceSize) : 8;
+ arrayHeader = Constants.JRE_IS_64BIT ? (8 + 2 * referenceSize) : 12;
+
+ // get the object header size:
+ // - first try out if the field offsets are not scaled (see warning in Unsafe docs)
+ // - get the object header size by getting the field offset of the first field of a dummy object
+ // If the scaling is byte-wise and unsafe is available, enable dynamic size measurement for
+ // estimateRamUsage().
+ Method tempObjectFieldOffsetMethod = null;
+ try {
+ final Method objectFieldOffsetM = unsafeClass.getMethod("objectFieldOffset", Field.class);
+ final Field dummy1Field = DummyTwoLongObject.class.getDeclaredField("dummy1");
+ final int ofs1 = ((Number) objectFieldOffsetM.invoke(theUnsafe, dummy1Field)).intValue();
+ final Field dummy2Field = DummyTwoLongObject.class.getDeclaredField("dummy2");
+ final int ofs2 = ((Number) objectFieldOffsetM.invoke(theUnsafe, dummy2Field)).intValue();
+ if (Math.abs(ofs2 - ofs1) == NUM_BYTES_LONG) {
+ final Field baseField = DummyOneFieldObject.class.getDeclaredField("base");
+ objectHeader = ((Number) objectFieldOffsetM.invoke(theUnsafe, baseField)).intValue();
+ supportedFeatures.add(JvmFeature.FIELD_OFFSETS);
+ tempObjectFieldOffsetMethod = objectFieldOffsetM;
}
} catch (Exception e) {
- // ignore
- supportedJvm = false;
+ // Ignore.
+ }
+ objectFieldOffsetMethod = tempObjectFieldOffsetMethod;
+
+ // Get the array header size by retrieving the array base offset
+ // (offset of the first element of an array).
+ try {
+ final Method arrayBaseOffsetM = unsafeClass.getMethod("arrayBaseOffset", Class.class);
+ // we calculate that only for byte[] arrays, it's actually the same for all types:
+ arrayHeader = ((Number) arrayBaseOffsetM.invoke(theUnsafe, byte[].class)).intValue();
+ supportedFeatures.add(JvmFeature.ARRAY_HEADER_SIZE);
+ } catch (Exception e) {
+ // Ignore.
}
NUM_BYTES_OBJECT_REF = referenceSize;
NUM_BYTES_OBJECT_HEADER = objectHeader;
NUM_BYTES_ARRAY_HEADER = arrayHeader;
- useUnsafe = (unsafe != null && objectFieldOffsetM != null);
- if (useUnsafe) {
- theUnsafe = unsafe;
- objectFieldOffsetMethod = objectFieldOffsetM;
- } else {
- theUnsafe = objectFieldOffsetMethod = null;
- }
// Try to get the object alignment (the default seems to be 8 on Hotspot,
// regardless of the architecture).
@@ -176,18 +224,34 @@ public final class RamUsageEstimator {
objectAlignment = Integer.parseInt(
vmOption.getClass().getMethod("getValue").invoke(vmOption).toString()
);
+ supportedFeatures.add(JvmFeature.OBJECT_ALIGNMENT);
} catch (InvocationTargetException ite) {
if (!(ite.getCause() instanceof IllegalArgumentException))
throw ite;
// ignore the error completely and use default of 8 (32 bit JVMs).
}
} catch (Exception e) {
- // ignore
- supportedJvm = false;
+ // Ignore.
}
+
NUM_BYTES_OBJECT_ALIGNMENT = objectAlignment;
- isSupportedJVM = supportedJvm;
+ JVM_INFO_STRING = "[JVM: " +
+ Constants.JVM_NAME + ", " + Constants.JVM_VERSION + ", " + Constants.JVM_VENDOR + ", " +
+ Constants.JAVA_VENDOR + ", " + Constants.JAVA_VERSION + "]";
+ }
+
+ /**
+ * Cached information about a given class.
+ */
+ private static final class ClassCache {
+ public final long alignedShallowInstanceSize;
+ public final Field[] referenceFields;
+
+ public ClassCache(long alignedShallowInstanceSize, Field[] referenceFields) {
+ this.alignedShallowInstanceSize = alignedShallowInstanceSize;
+ this.referenceFields = referenceFields;
+ }
}
// Object with just one field to determine the object header size by getting the offset of the dummy field:
@@ -204,14 +268,14 @@ public final class RamUsageEstimator {
}
/**
- * Returns true, if the current JVM is supported by {@code RamUsageEstimator}.
+ * Returns true, if the current JVM is fully supported by {@code RamUsageEstimator}.
* If this method returns {@code false} you are maybe using a 3rd party Java VM
* that is not supporting Oracle/Sun private APIs. The memory estimates can be
* imprecise then (no way of detecting compressed references, alignments, etc.).
* Lucene still tries to use sensible defaults.
*/
public static boolean isSupportedJVM() {
- return isSupportedJVM;
+ return supportedFeatures.size() == JvmFeature.values().length;
}
/**
@@ -272,13 +336,7 @@ public final class RamUsageEstimator {
* should be GCed.
*/
public static long sizeOf(Object obj) {
- final Set