Index: . =================================================================== --- . (revision 1302134) +++ . (working copy) Property changes on: . ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk:r1302133 Index: lucene =================================================================== --- lucene (revision 1302134) +++ lucene (working copy) Property changes on: lucene ___________________________________________________________________ Modified: svn:mergeinfo Merged /lucene/dev/trunk/lucene:r1302133 Index: lucene/contrib/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java =================================================================== --- lucene/contrib/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java (revision 1302134) +++ lucene/contrib/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java (working copy) @@ -53,6 +53,7 @@ import org.apache.lucene.store.RAMDirectory; // for javadocs import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.Constants; // for javadocs +import org.apache.lucene.util.RamUsageEstimator; /** * High-performance single-document main memory Apache Lucene fulltext search index. @@ -471,38 +472,12 @@ /** * Returns a reasonable approximation of the main memory [bytes] consumed by - * this instance. Useful for smart memory sensititive caches/pools. Assumes - * fieldNames are interned, whereas tokenized terms are memory-overlaid. - * + * this instance. Useful for smart memory sensititive caches/pools. * @return the main memory consumption */ - public int getMemorySize() { - // for example usage in a smart cache see nux.xom.pool.Pool - int PTR = VM.PTR; - int INT = VM.INT; - int size = 0; - size += VM.sizeOfObject(2*PTR + INT); // memory index - if (sortedFields != null) size += VM.sizeOfObjectArray(sortedFields.length); - - size += VM.sizeOfHashMap(fields.size()); - for (Map.Entry entry : fields.entrySet()) { // for each Field Info - Info info = entry.getValue(); - size += VM.sizeOfObject(2*INT + 3*PTR); // Info instance vars - if (info.sortedTerms != null) size += VM.sizeOfObjectArray(info.sortedTerms.length); - - int len = info.terms.size(); - size += VM.sizeOfHashMap(len); - Iterator> iter2 = info.terms.entrySet().iterator(); - while (--len >= 0) { // for each term - Map.Entry e = iter2.next(); - size += VM.sizeOfObject(PTR + 3*INT); // assumes substring() memory overlay -// size += STR + 2 * ((String) e.getKey()).length(); - ArrayIntList positions = e.getValue(); - size += VM.sizeOfArrayIntList(positions.size()); - } - } - return size; - } + public long getMemorySize() { + return RamUsageEstimator.sizeOf(this); + } private int numPositions(ArrayIntList positions) { return positions.size() / stride; @@ -1195,61 +1170,4 @@ if (DEBUG) System.err.println("MemoryIndexReader.doClose"); } } - - - /////////////////////////////////////////////////////////////////////////////// - // Nested classes: - /////////////////////////////////////////////////////////////////////////////// - private static final class VM { - - public static final int PTR = Constants.JRE_IS_64BIT ? 8 : 4; - - public static final int INT = 4; - private static final int LOG_PTR = (int) Math.round(log2(PTR)); - - /** - * Object header of any heap allocated Java object. - * ptr to class, info for monitor, gc, hash, etc. - */ - private static final int OBJECT_HEADER = 2*PTR; - - private VM() {} // not instantiable - - // assumes n > 0 - // 64 bit VM: - // 0 --> 0*PTR - // 1..8 --> 1*PTR - // 9..16 --> 2*PTR - private static int sizeOf(int n) { - return (((n-1) >> LOG_PTR) + 1) << LOG_PTR; - } - - public static int sizeOfObject(int n) { - return sizeOf(OBJECT_HEADER + n); - } - - public static int sizeOfObjectArray(int len) { - return sizeOfObject(INT + PTR*len); - } - - public static int sizeOfIntArray(int len) { - return sizeOfObject(INT + INT*len); - } - - public static int sizeOfHashMap(int len) { - return sizeOfObject(4*PTR + 4*INT) + sizeOfObjectArray(len) - + len * sizeOfObject(3*PTR + INT); // entries - } - - public static int sizeOfArrayIntList(int len) { - return sizeOfObject(PTR + INT) + sizeOfIntArray(len); - } - - /** logarithm to the base 2. Example: log2(4) == 2, log2(8) == 3 */ - private static double log2(double value) { - return Math.log(value) / Math.log(2); - } - - } - } Index: lucene/contrib/spellchecker/src/test/org/apache/lucene/search/suggest/LookupBenchmarkTest.java =================================================================== --- lucene/contrib/spellchecker/src/test/org/apache/lucene/search/suggest/LookupBenchmarkTest.java (revision 1302134) +++ lucene/contrib/spellchecker/src/test/org/apache/lucene/search/suggest/LookupBenchmarkTest.java (working copy) @@ -132,13 +132,12 @@ */ public void testStorageNeeds() throws Exception { System.err.println("-- RAM consumption"); - final RamUsageEstimator rue = new RamUsageEstimator(); for (Class cls : benchmarkClasses) { Lookup lookup = buildLookup(cls, dictionaryInput); System.err.println( String.format(Locale.ENGLISH, "%-15s size[B]:%,13d", lookup.getClass().getSimpleName(), - rue.estimateRamUsage(lookup))); + RamUsageEstimator.sizeOf(lookup))); } } Index: lucene/core/src/java/org/apache/lucene/search/FieldCache.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/FieldCache.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/search/FieldCache.java (working copy) @@ -609,22 +609,16 @@ protected final void setEstimatedSize(String size) { this.size = size; } + /** - * @see #estimateSize(RamUsageEstimator) - */ - public void estimateSize() { - estimateSize(new RamUsageEstimator(false)); // doesn't check for interned - } - /** * Computes (and stores) the estimated size of the cache Value * @see #getEstimatedSize */ - public void estimateSize(RamUsageEstimator ramCalc) { - long size = ramCalc.estimateRamUsage(getValue()); - setEstimatedSize(RamUsageEstimator.humanReadableUnits - (size, new DecimalFormat("0.#"))); - + public void estimateSize() { + long size = RamUsageEstimator.sizeOf(getValue()); + setEstimatedSize(RamUsageEstimator.humanReadableUnits(size)); } + /** * The most recently estimated size of the value, null unless * estimateSize has been called. Index: lucene/core/src/java/org/apache/lucene/util/AverageGuessMemoryModel.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/AverageGuessMemoryModel.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/AverageGuessMemoryModel.java (working copy) @@ -1,78 +0,0 @@ -package org.apache.lucene.util; - -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.util.IdentityHashMap; -import java.util.Map; - -/** - * An average, best guess, MemoryModel that should work okay on most systems. - * - */ -public class AverageGuessMemoryModel extends MemoryModel { - // best guess primitive sizes - private final Map,Integer> sizes = new IdentityHashMap,Integer>() { - { - put(boolean.class, Integer.valueOf(1)); - put(byte.class, Integer.valueOf(1)); - put(char.class, Integer.valueOf(2)); - put(short.class, Integer.valueOf(2)); - put(int.class, Integer.valueOf(4)); - put(float.class, Integer.valueOf(4)); - put(double.class, Integer.valueOf(8)); - put(long.class, Integer.valueOf(8)); - } - }; - - /* - * (non-Javadoc) - * - * @see org.apache.lucene.util.MemoryModel#getArraySize() - */ - @Override - public int getArraySize() { - return 16; - } - - /* - * (non-Javadoc) - * - * @see org.apache.lucene.util.MemoryModel#getClassSize() - */ - @Override - public int getClassSize() { - return 8; - } - - /* (non-Javadoc) - * @see org.apache.lucene.util.MemoryModel#getPrimitiveSize(java.lang.Class) - */ - @Override - public int getPrimitiveSize(Class clazz) { - return sizes.get(clazz).intValue(); - } - - /* (non-Javadoc) - * @see org.apache.lucene.util.MemoryModel#getReferenceSize() - */ - @Override - public int getReferenceSize() { - return 4; - } - -} Index: lucene/core/src/java/org/apache/lucene/util/Constants.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/Constants.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/Constants.java (working copy) @@ -17,6 +17,7 @@ * limitations under the License. */ +import java.lang.reflect.Field; import org.apache.lucene.LucenePackage; /** @@ -57,22 +58,36 @@ public static final String OS_VERSION = System.getProperty("os.version"); public static final String JAVA_VENDOR = System.getProperty("java.vendor"); - public static final boolean JRE_IS_64BIT; public static final boolean JRE_IS_MINIMUM_JAVA6; public static final boolean JRE_IS_MINIMUM_JAVA7; + + /** True iff running on a 64bit JVM */ + public static final boolean JRE_IS_64BIT; + static { - // NOTE: this logic may not be correct; if you know of a - // more reliable approach please raise it on java-dev! - final String x = System.getProperty("sun.arch.data.model"); - if (x != null) { - JRE_IS_64BIT = x.indexOf("64") != -1; - } else { - if (OS_ARCH != null && OS_ARCH.indexOf("64") != -1) { - JRE_IS_64BIT = true; + boolean is64Bit = false; + try { + final Class unsafeClass = Class.forName("sun.misc.Unsafe"); + final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + final Object unsafe = unsafeField.get(null); + final int addressSize = ((Number) unsafeClass.getMethod("addressSize") + .invoke(unsafe)).intValue(); + //System.out.println("Address size: " + addressSize); + is64Bit = addressSize >= 8; + } catch (Exception e) { + final String x = System.getProperty("sun.arch.data.model"); + if (x != null) { + is64Bit = x.indexOf("64") != -1; } else { - JRE_IS_64BIT = false; + if (OS_ARCH != null && OS_ARCH.indexOf("64") != -1) { + is64Bit = true; + } else { + is64Bit = false; + } } } + JRE_IS_64BIT = is64Bit; // this method only exists in Java 6: boolean v6 = true; Index: lucene/core/src/java/org/apache/lucene/util/FieldCacheSanityChecker.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/FieldCacheSanityChecker.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/FieldCacheSanityChecker.java (working copy) @@ -51,16 +51,17 @@ */ public final class FieldCacheSanityChecker { - private RamUsageEstimator ramCalc = null; + private boolean estimateRam; + public FieldCacheSanityChecker() { /* NOOP */ } + /** - * If set, will be used to estimate size for all CacheEntry objects - * dealt with. + * If set, estimate size for all CacheEntry objects will be calculateed. */ - public void setRamUsageEstimator(RamUsageEstimator r) { - ramCalc = r; + public void setRamUsageEstimator(boolean flag) { + estimateRam = flag; } @@ -79,8 +80,7 @@ */ public static Insanity[] checkSanity(CacheEntry... cacheEntries) { FieldCacheSanityChecker sanityChecker = new FieldCacheSanityChecker(); - // doesn't check for interned - sanityChecker.setRamUsageEstimator(new RamUsageEstimator(false)); + sanityChecker.setRamUsageEstimator(true); return sanityChecker.check(cacheEntries); } @@ -96,9 +96,9 @@ if (null == cacheEntries || 0 == cacheEntries.length) return new Insanity[0]; - if (null != ramCalc) { + if (estimateRam) { for (int i = 0; i < cacheEntries.length; i++) { - cacheEntries[i].estimateSize(ramCalc); + cacheEntries[i].estimateSize(); } } Index: lucene/core/src/java/org/apache/lucene/util/MemoryModel.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/MemoryModel.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/MemoryModel.java (working copy) @@ -1,48 +0,0 @@ -package org.apache.lucene.util; - -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with this - * work for additional information regarding copyright ownership. The ASF - * licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -/** - * Returns primitive memory sizes for estimating RAM usage. - * - */ -public abstract class MemoryModel { - - /** - * @return size of array beyond contents - */ - public abstract int getArraySize(); - - /** - * @return Class size overhead - */ - public abstract int getClassSize(); - - /** - * @param clazz a primitive Class - bool, byte, char, short, long, float, - * short, double, int - * @return the size in bytes of given primitive Class - */ - public abstract int getPrimitiveSize(Class clazz); - - /** - * @return size of reference - */ - public abstract int getReferenceSize(); - -} Index: lucene/core/src/java/org/apache/lucene/util/packed/Direct16.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Direct16.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Direct16.java (working copy) @@ -78,8 +78,7 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + - values.length * RamUsageEstimator.NUM_BYTES_SHORT; + return RamUsageEstimator.sizeOf(values); } public void clear() { Index: lucene/core/src/java/org/apache/lucene/util/packed/Direct32.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Direct32.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Direct32.java (working copy) @@ -74,8 +74,7 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + - values.length * RamUsageEstimator.NUM_BYTES_INT; + return RamUsageEstimator.sizeOf(values); } public void clear() { Index: lucene/core/src/java/org/apache/lucene/util/packed/Direct64.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Direct64.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Direct64.java (working copy) @@ -70,8 +70,7 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + - values.length * RamUsageEstimator.NUM_BYTES_LONG; + return RamUsageEstimator.sizeOf(values); } public void clear() { Index: lucene/core/src/java/org/apache/lucene/util/packed/Direct8.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Direct8.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Direct8.java (working copy) @@ -79,7 +79,7 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + values.length; + return RamUsageEstimator.sizeOf(values); } public void clear() { Index: lucene/core/src/java/org/apache/lucene/util/packed/Packed32.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Packed32.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Packed32.java (working copy) @@ -222,7 +222,6 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER - + blocks.length * RamUsageEstimator.NUM_BYTES_INT; + return RamUsageEstimator.sizeOf(blocks); } } Index: lucene/core/src/java/org/apache/lucene/util/packed/Packed64.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/packed/Packed64.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/packed/Packed64.java (working copy) @@ -208,8 +208,7 @@ } public long ramBytesUsed() { - return RamUsageEstimator.NUM_BYTES_ARRAY_HEADER - + blocks.length * RamUsageEstimator.NUM_BYTES_LONG; + return RamUsageEstimator.sizeOf(blocks); } public void clear() { Index: lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java =================================================================== --- lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java (revision 1302134) +++ lucene/core/src/java/org/apache/lucene/util/RamUsageEstimator.java (working copy) @@ -17,194 +17,519 @@ * limitations under the License. */ +import java.lang.management.ManagementFactory; import java.lang.reflect.*; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.*; /** - * Estimates the size of a given Object using a given MemoryModel for primitive - * size information. + * Estimates the size of Java objects using a simple memory model + * for primitive size information. * - * Resource Usage: + *

NOTE: Starting with Lucene 3.6, creating instances of this class + * is deprecated. If you still do this, please note, that instances of + * {@code RamUsageEstimator} are not thread-safe! + * It is also deprecated to enable checking of String intern-ness, + * the new static method no longer allow to do this. Interned strings + * will be counted as any other object and count for memory usage. * - * Internally uses a Map to temporally hold a reference to every - * object seen. + *

In Lucene 3.6, custom {@code MemoryModel}s were completely + * removed. The new implementation is now using Hotspot™ internals + * to get the correct scale factors and offsets for calculating + * memory usage. * - * If checkInterned, all Strings checked will be interned, but those - * that were not already interned will be released for GC when the - * estimate is complete. - * * @lucene.internal */ public final class RamUsageEstimator { + + // private RamUsageEstimator() {} // no instance in Lucene 4.0 + public final static int NUM_BYTES_BOOLEAN = 1; + public final static int NUM_BYTES_BYTE = 1; + public final static int NUM_BYTES_CHAR = 2; public final static int NUM_BYTES_SHORT = 2; public final static int NUM_BYTES_INT = 4; + public final static int NUM_BYTES_FLOAT = 4; public final static int NUM_BYTES_LONG = 8; - public final static int NUM_BYTES_FLOAT = 4; public final static int NUM_BYTES_DOUBLE = 8; - public final static int NUM_BYTES_CHAR = 2; - public final static int NUM_BYTES_OBJECT_HEADER = 8; - public final static int NUM_BYTES_OBJECT_REF = Constants.JRE_IS_64BIT ? 8 : 4; - public final static int NUM_BYTES_ARRAY_HEADER = NUM_BYTES_OBJECT_HEADER + NUM_BYTES_INT + NUM_BYTES_OBJECT_REF; - private MemoryModel memoryModel; + public final static int NUM_BYTES_OBJECT_REF; + + public final static int NUM_BYTES_OBJECT_HEADER; + public final static int NUM_BYTES_ARRAY_HEADER; + + /** + * A constant specifying the object alignment boundary inside the JVM. Objects will + * always take a full multiple of this constant, possibly wasting some space. + */ + public final static int NUM_BYTES_OBJECT_ALIGNMENT; - private final Map seen; + /** + * Sizes of primitive classes. + */ + private static final Map,Integer> primitiveSizes; + static { + primitiveSizes = new IdentityHashMap,Integer>(); + primitiveSizes.put(boolean.class, Integer.valueOf(NUM_BYTES_BOOLEAN)); + primitiveSizes.put(byte.class, Integer.valueOf(NUM_BYTES_BYTE)); + primitiveSizes.put(char.class, Integer.valueOf(NUM_BYTES_CHAR)); + primitiveSizes.put(short.class, Integer.valueOf(NUM_BYTES_SHORT)); + primitiveSizes.put(int.class, Integer.valueOf(NUM_BYTES_INT)); + primitiveSizes.put(float.class, Integer.valueOf(NUM_BYTES_FLOAT)); + primitiveSizes.put(double.class, Integer.valueOf(NUM_BYTES_DOUBLE)); + primitiveSizes.put(long.class, Integer.valueOf(NUM_BYTES_LONG)); + } - private int refSize; - private int arraySize; - private int classSize; + private final static Object theUnsafe; + private final static Method objectFieldOffsetMethod; + private final static boolean useUnsafe, isSupportedJVM; - private boolean checkInterned; - /** - * Constructs this object with an AverageGuessMemoryModel and - * checkInterned = true. + * Initialize constants and try to collect information about the JVM internals. */ - public RamUsageEstimator() { - this(new AverageGuessMemoryModel()); + static { + // Initialize empirically measured defaults. We'll modify them to the current + // JVM settings later on if possible. + int referenceSize = Constants.JRE_IS_64BIT ? 8 : 4; + int objectHeader = Constants.JRE_IS_64BIT ? 16 : 8; + // The following is objectHeader + NUM_BYTES_INT, but aligned (object alignment) + // 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; + try { + final Class 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; + } + + // 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; + } + } catch (Exception e) { + // ignore + supportedJvm = false; + } + + 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). Retrieval only works with Java 6. + int objectAlignment = 8; + if (Constants.JRE_IS_MINIMUM_JAVA6) { + try { + final Class beanClazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean"); + final Object hotSpotBean = ManagementFactory.newPlatformMXBeanProxy( + ManagementFactory.getPlatformMBeanServer(), + "com.sun.management:type=HotSpotDiagnostic", + beanClazz + ); + final Method getVMOptionMethod = beanClazz.getMethod("getVMOption", String.class); + try { + final Object vmOption = getVMOptionMethod.invoke(hotSpotBean, "ObjectAlignmentInBytes"); + objectAlignment = Integer.parseInt( + vmOption.getClass().getMethod("getValue").invoke(vmOption).toString() + ); + } 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; + } + } + NUM_BYTES_OBJECT_ALIGNMENT = objectAlignment; + + isSupportedJVM = supportedJvm; } - /** - * @param checkInterned check if Strings are interned and don't add to size - * if they are. Defaults to true but if you know the objects you are checking - * won't likely contain many interned Strings, it will be faster to turn off - * intern checking. + // Object with just one field to determine the object header size by getting the offset of the dummy field: + @SuppressWarnings("unused") + private static final class DummyOneFieldObject { + public byte base; + } + + // Another test object for checking, if the difference in offsets of dummy1 and dummy2 is 8 bytes. + // Only then we can be sure that those are real, unscaled offsets: + @SuppressWarnings("unused") + private static final class DummyTwoLongObject { + public long dummy1, dummy2; + } + + /** + * Returns true, if the current JVM is 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 RamUsageEstimator(boolean checkInterned) { - this(new AverageGuessMemoryModel(), checkInterned); + public static boolean isSupportedJVM() { + return isSupportedJVM; } - /** - * @param memoryModel MemoryModel to use for primitive object sizes. + /** + * Aligns an object size to be the next multiple of {@link #NUM_BYTES_OBJECT_ALIGNMENT}. */ - public RamUsageEstimator(MemoryModel memoryModel) { - this(memoryModel, true); + public static long alignObjectSize(long size) { + size += (long) NUM_BYTES_OBJECT_ALIGNMENT - 1L; + return size - (size % NUM_BYTES_OBJECT_ALIGNMENT); } + + /** Returns the size in bytes of the byte[] object. */ + public static long sizeOf(byte[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + arr.length); + } + + /** Returns the size in bytes of the boolean[] object. */ + public static long sizeOf(boolean[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + arr.length); + } + + /** Returns the size in bytes of the char[] object. */ + public static long sizeOf(char[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_CHAR * arr.length); + } - /** - * @param memoryModel MemoryModel to use for primitive object sizes. - * @param checkInterned check if Strings are interned and don't add to size - * if they are. Defaults to true but if you know the objects you are checking - * won't likely contain many interned Strings, it will be faster to turn off - * intern checking. + /** Returns the size in bytes of the short[] object. */ + public static long sizeOf(short[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_SHORT * arr.length); + } + + /** Returns the size in bytes of the int[] object. */ + public static long sizeOf(int[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_INT * arr.length); + } + + /** Returns the size in bytes of the float[] object. */ + public static long sizeOf(float[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_FLOAT * arr.length); + } + + /** Returns the size in bytes of the long[] object. */ + public static long sizeOf(long[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_LONG * arr.length); + } + + /** Returns the size in bytes of the double[] object. */ + public static long sizeOf(double[] arr) { + return alignObjectSize((long) NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_DOUBLE * arr.length); + } + + /** + * Estimates the RAM usage by the given object. It will + * walk the object tree and sum up all referenced objects. + * + *

Resource Usage: This method internally uses a set of + * every object seen during traversals so it does allocate memory + * (it isn't side-effect free). After the method exits, this memory + * should be GCed.

*/ - public RamUsageEstimator(MemoryModel memoryModel, boolean checkInterned) { - this.memoryModel = memoryModel; - this.checkInterned = checkInterned; - // Use Map rather than Set so that we can use an IdentityHashMap - not - // seeing an IdentityHashSet - seen = new IdentityHashMap(64); - this.refSize = memoryModel.getReferenceSize(); - this.arraySize = memoryModel.getArraySize(); - this.classSize = memoryModel.getClassSize(); + public static long sizeOf(Object obj) { + final Set seen = new MapBackedSet(new IdentityHashMap(64)); + try { + return measureObjectSize(obj, seen, false); + } finally { + // Help the GC. + seen.clear(); + } } - public long estimateRamUsage(Object obj) { - long size = size(obj); - seen.clear(); - return size; + /** + * Estimates a "shallow" memory usage of the given object. For arrays, this will be the + * memory taken by array storage (no subreferences will be followed). For objects, this + * will be the memory taken by the fields. + * + * JVM object alignments are also applied. + */ + public static long shallowSizeOf(Object obj) { + if (obj == null) return 0; + final Class clz = obj.getClass(); + if (clz.isArray()) { + return measureArraySize(obj, null, false); + } else { + return shallowSizeOfInstance(clz); + } } - private long size(Object obj) { + /** + * Returns the shallow instance size in bytes an instance of the given class would occupy. + * This works with all conventional classes and primitive types, but not with arrays + * (the size then depends on the number of elements and varies from object to object). + * Use the array-instance methods instead. + * + * @throws IllegalArgumentException if {@code clazz} is an array class. + */ + public static long shallowSizeOfInstance(Class clazz) { + if (clazz.isArray()) + throw new IllegalArgumentException("This method does not work with array classes."); + if (clazz.isPrimitive()) + return primitiveSizes.get(clazz); + + long size = NUM_BYTES_OBJECT_HEADER; + + // Walk type hierarchy + while (clazz != null) { + final Field[] fields = clazz.getDeclaredFields(); + boolean fieldFound = false; + for (final Field f : fields) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + + size = reflectFieldSize(size, f); + fieldFound = true; + } + if (useUnsafe && fieldFound) { + // no need to recurse to superclasses, as all fields are + // added at the end, so we won't find any larger offset + break; + } + clazz = clazz.getSuperclass(); + } + return alignObjectSize(size); + } + + /** + * Recursive descend into an object. + */ + private static long measureObjectSize(Object obj, Set seen, final boolean checkInterned) { if (obj == null) { return 0; } - // interned not part of this object - if (checkInterned && obj instanceof String - && obj == ((String) obj).intern()) { // interned string will be eligible - // for GC on - // estimateRamUsage(Object) return + + // *** BEGIN deprecation + if (checkInterned && obj instanceof String && obj == ((String) obj).intern()) { return 0; } + // *** END deprecation // skip if we have seen before - if (seen.containsKey(obj)) { + if (seen.contains(obj)) { return 0; } // add to seen - seen.put(obj, null); + seen.add(obj); Class clazz = obj.getClass(); if (clazz.isArray()) { - return sizeOfArray(obj); + return measureArraySize(obj, seen, checkInterned); } - long size = 0; + long size = NUM_BYTES_OBJECT_HEADER; + long innerSize = 0L; // walk type hierarchy while (clazz != null) { - Field[] fields = clazz.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - if (Modifier.isStatic(fields[i].getModifiers())) { + final Field[] fields = clazz.getDeclaredFields(); + for (final Field f : fields) { + if (Modifier.isStatic(f.getModifiers())) { continue; } - if (fields[i].getType().isPrimitive()) { - size += memoryModel.getPrimitiveSize(fields[i].getType()); - } else { - size += refSize; - fields[i].setAccessible(true); + size = reflectFieldSize(size, f); + + if (!f.getType().isPrimitive()) { try { - Object value = fields[i].get(obj); - if (value != null) { - size += size(value); - } + f.setAccessible(true); + innerSize += measureObjectSize(f.get(obj), seen, checkInterned); } catch (IllegalAccessException ex) { - // ignore for now? + // this should never happen as we enable setAccessible()! + throw new RuntimeException("Cannot reflect instance field: " + + f.getDeclaringClass().getName() + "#" + f.getName(), ex); } } - } clazz = clazz.getSuperclass(); } - size += classSize; - return size; + return alignObjectSize(size) + innerSize; } + + private static long reflectFieldSize(long size, final Field f) { + final Class type = f.getType(); + final int fsize = type.isPrimitive() ? primitiveSizes.get(type) : NUM_BYTES_OBJECT_REF; + if (useUnsafe) { + try { + final long offsetPlusSize = + ((Number) objectFieldOffsetMethod.invoke(theUnsafe, f)).longValue() + fsize; + return Math.max(size, offsetPlusSize); + } catch (IllegalAccessException ex) { + throw new RuntimeException("Access problem with sun.misc.Unsafe", ex); + } catch (InvocationTargetException ite) { + final Throwable cause = ite.getCause(); + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + if (cause instanceof Error) + throw (Error) cause; + // this should never happen (Unsafe does not declare + // checked Exceptions for this method), but who knows? + throw new RuntimeException("Call to Unsafe's objectFieldOffset() throwed "+ + "checked Exception when accessing field " + + f.getDeclaringClass().getName() + "#" + f.getName(), cause); + } + } else { + return size + fsize; + } + } - private long sizeOfArray(Object obj) { - int len = Array.getLength(obj); - if (len == 0) { - return 0; - } - long size = arraySize; - Class arrayElementClazz = obj.getClass().getComponentType(); - if (arrayElementClazz.isPrimitive()) { - size += len * memoryModel.getPrimitiveSize(arrayElementClazz); - } else { - for (int i = 0; i < len; i++) { - size += refSize + size(Array.get(obj, i)); + /** + * Return the deep size of an array, including + * sub-objects if there are any. + * + * @param seen A set of already seen objects. If null no references + * are followed and this method returns shallow size. + */ + private static long measureArraySize(Object array, Set seen, final boolean checkInterned) { + long size = NUM_BYTES_ARRAY_HEADER; + final int len = Array.getLength(array); + if (len > 0) { + Class arrayElementClazz = array.getClass().getComponentType(); + if (arrayElementClazz.isPrimitive()) { + size += (long) len * primitiveSizes.get(arrayElementClazz); + } else { + size += (long) NUM_BYTES_OBJECT_REF * len; + if (seen != null) { + for (int i = 0; i < len; i++) { + size += measureObjectSize(Array.get(array, i), seen, checkInterned); + } + } } } - return size; + return alignObjectSize(size); } - private static final long ONE_KB = 1024; - private static final long ONE_MB = ONE_KB * ONE_KB; - private static final long ONE_GB = ONE_KB * ONE_MB; + public static final long ONE_KB = 1024; + public static final long ONE_MB = ONE_KB * ONE_KB; + public static final long ONE_GB = ONE_KB * ONE_MB; /** - * Return good default units based on byte size. + * Returns size in human-readable units (GB, MB, KB or bytes). */ + public static String humanReadableUnits(long bytes) { + return humanReadableUnits(bytes, + new DecimalFormat("0.#", new DecimalFormatSymbols(Locale.ENGLISH))); + } + + /** + * Returns size in human-readable units (GB, MB, KB or bytes). + */ public static String humanReadableUnits(long bytes, DecimalFormat df) { - String newSizeAndUnits; - if (bytes / ONE_GB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_GB)) - + " GB"; + return df.format((float) bytes / ONE_GB) + " GB"; } else if (bytes / ONE_MB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_MB)) - + " MB"; + return df.format((float) bytes / ONE_MB) + " MB"; } else if (bytes / ONE_KB > 0) { - newSizeAndUnits = String.valueOf(df.format((float) bytes / ONE_KB)) - + " KB"; + return df.format((float) bytes / ONE_KB) + " KB"; } else { - newSizeAndUnits = String.valueOf(bytes) + " bytes"; + return bytes + " bytes"; } + } - return newSizeAndUnits; + // deprecated API (will be removed in 4.0): + private final boolean checkInterned; + private final Set seen = + new MapBackedSet(new IdentityHashMap(64)); + + /** Creates a new instance of {@code RamUsageEstimator} with intern checking + * enabled. Don't ever use this method, as intern checking is deprecated, + * because it is not free of side-effects and strains the garbage collector + * additionally. + * @deprecated Don't create instances of this class, instead use the static + * {@link #sizeOf(Object)} method that has no intern checking, too. + */ + @Deprecated + public RamUsageEstimator() { + this(true); } + + /** Creates a new instance of {@code RamUsageEstimator}. + * @param checkInterned check if Strings are interned and don't add to size + * if they are. Defaults to true but if you know the objects you are checking + * won't likely contain many interned Strings, it will be faster to turn off + * intern checking. Intern checking is deprecated altogether, as it is not free + * of side-effects and strains the garbage collector additionally. + * @deprecated Don't create instances of this class, instead use the static + * {@link #sizeOf(Object)} method. + */ + @Deprecated + public RamUsageEstimator(boolean checkInterned) { + this.checkInterned = checkInterned; + } + + /** Creates a new istance of {@code RamUsageEstimator}. + * @deprecated Don't create instances of this class, instead use the static + * {@link #sizeOf(Object)} method. + * @see #sizeOf(Object) + */ + @Deprecated + public long estimateRamUsage(Object obj) { + try { + return measureObjectSize(obj, seen, checkInterned); + } finally { + // Help the GC. + seen.clear(); + } + } } Index: lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java =================================================================== --- lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java (revision 0) +++ lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java (working copy) @@ -0,0 +1,149 @@ +package org.apache.lucene.util; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +import org.junit.Ignore; + +/** + * Estimates how {@link RamUsageEstimator} estimates physical memory consumption + * of Java objects. + */ +public class StressRamUsageEstimator extends LuceneTestCase { + static class Entry { + Object o; + Entry next; + + public Entry createNext(Object o) { + Entry e = new Entry(); + e.o = o; + e.next = next; + this.next = e; + return e; + } + } + + // This shows an easy stack overflow because we're counting recursively. + @Ignore + public void testChainedEstimation() { + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + Random rnd = random; + Entry first = new Entry(); + try { + while (true) { + // Check the current memory consumption and provide the estimate. + long jvmUsed = memoryMXBean.getHeapMemoryUsage().getUsed(); + long estimated = RamUsageEstimator.sizeOf(first); + System.out.println(String.format(Locale.ENGLISH, "%10d, %10d", + jvmUsed, estimated)); + + // Make a batch of objects. + for (int i = 0; i < 5000; i++) { + first.createNext(new byte[rnd.nextInt(1024)]); + } + } + } catch (OutOfMemoryError e) { + // Release and quit. + } + } + + volatile Object guard; + + // This shows an easy stack overflow because we're counting recursively. + public void testLargeSetOfByteArrays() { + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + causeGc(); + long before = memoryMXBean.getHeapMemoryUsage().getUsed(); + Object [] all = new Object [1000000]; + for (int i = 0; i < all.length; i++) { + all[i] = new byte[random.nextInt(3)]; + } + causeGc(); + long after = memoryMXBean.getHeapMemoryUsage().getUsed(); + System.out.println("mx: " + RamUsageEstimator.humanReadableUnits(after - before)); + System.out.println("rue: " + RamUsageEstimator.humanReadableUnits(shallowSizeOf(all))); + + guard = all; + } + + private long shallowSizeOf(Object[] all) { + long s = RamUsageEstimator.shallowSizeOf(all); + for (Object o : all) { + s+= RamUsageEstimator.shallowSizeOf(o); + } + return s; + } + + private long shallowSizeOf(Object[][] all) { + long s = RamUsageEstimator.shallowSizeOf(all); + for (Object[] o : all) { + s += RamUsageEstimator.shallowSizeOf(o); + for (Object o2 : o) { + s += RamUsageEstimator.shallowSizeOf(o2); + } + } + return s; + } + + public void testSimpleByteArrays() { + MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); + + Object [][] all = new Object [0][]; + try { + while (true) { + // Check the current memory consumption and provide the estimate. + causeGc(); + MemoryUsage mu = memoryMXBean.getHeapMemoryUsage(); + long estimated = shallowSizeOf(all); + if (estimated > 50 * RamUsageEstimator.ONE_MB) { + break; + } + + System.out.println(String.format(Locale.ENGLISH, "%10s\t%10s\t%10s", + RamUsageEstimator.humanReadableUnits(mu.getUsed()), + RamUsageEstimator.humanReadableUnits(mu.getMax()), + RamUsageEstimator.humanReadableUnits(estimated))); + + // Make another batch of objects. + Object[] seg = new Object[10000]; + Object[][] all2 = new Object [all.length + 1][]; + System.arraycopy(all, 0, all2, 0, all.length); + all = all2; + all[all.length - 1] = seg; + for (int i = 0; i < seg.length; i++) { + seg[i] = new byte[random.nextInt(7)]; + } + } + } catch (OutOfMemoryError e) { + // Release and quit. + } + } + + /** + * Very hacky, very crude, but (sometimes) works. + * Don't look, it will burn your eyes out. + */ + private void causeGc() { + List garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); + List ccounts = new ArrayList(); + for (GarbageCollectorMXBean g : garbageCollectorMXBeans) { + ccounts.add(g.getCollectionCount()); + } + List ccounts2 = new ArrayList(); + do { + System.gc(); + ccounts.clear(); + for (GarbageCollectorMXBean g : garbageCollectorMXBeans) { + ccounts2.add(g.getCollectionCount()); + } + } while (ccounts2.equals(ccounts)); + } +} Index: lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java =================================================================== --- lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java (revision 1302133) +++ lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java (working copy) Property changes on: lucene/core/src/test/org/apache/lucene/util/StressRamUsageEstimator.java ___________________________________________________________________ Added: svn:keywords ## -0,0 +1 ## +Date Author Id Revision HeadURL \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/core/src/test/org/apache/lucene/util/TestRamUsageEstimator.java =================================================================== --- lucene/core/src/test/org/apache/lucene/util/TestRamUsageEstimator.java (revision 1302134) +++ lucene/core/src/test/org/apache/lucene/util/TestRamUsageEstimator.java (working copy) @@ -1,5 +1,9 @@ package org.apache.lucene.util; +import static org.apache.lucene.util.RamUsageEstimator.*; + +import java.util.Random; + /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -17,28 +21,93 @@ * limitations under the License. */ -import org.apache.lucene.util.LuceneTestCase; - public class TestRamUsageEstimator extends LuceneTestCase { public void testBasic() { - RamUsageEstimator rue = new RamUsageEstimator(); - rue.estimateRamUsage("test str"); + assertTrue(sizeOf(new String("test strin")) > shallowSizeOfInstance(String.class)); - rue.estimateRamUsage("test strin"); - Holder holder = new Holder(); holder.holder = new Holder("string2", 5000L); - rue.estimateRamUsage(holder); + assertTrue(sizeOf(holder) > shallowSizeOfInstance(Holder.class)); + assertTrue(sizeOf(holder) > sizeOf(holder.holder)); + assertTrue(shallowSizeOfInstance(HolderSubclass.class) >= shallowSizeOfInstance(Holder.class)); + assertEquals(shallowSizeOfInstance(Holder.class), shallowSizeOfInstance(HolderSubclass2.class)); + String[] strings = new String[]{new String("test strin"), new String("hollow"), new String("catchmaster")}; - rue.estimateRamUsage(strings); + assertTrue(sizeOf(strings) > shallowSizeOf(strings)); } + + public void testStaticOverloads() { + Random rnd = random; + + { + byte[] array = new byte [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + boolean[] array = new boolean [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + char[] array = new char [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + short[] array = new short [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + int[] array = new int [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + float[] array = new float [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + long[] array = new long [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + + { + double[] array = new double [rnd.nextInt(1024)]; + assertEquals(sizeOf(array), sizeOf((Object) array)); + } + } + + public void testReferenceSize() { + if (!isSupportedJVM()) { + System.err.println("WARN: Your JVM does not support the Oracle/Sun extensions (sun.misc.Unsafe),"); + System.err.println("so the memory estimates may be inprecise."); + System.err.println("Please report this to the Lucene mailing list, noting your JVM version: " + + Constants.JAVA_VENDOR + " " + Constants.JAVA_VERSION); + } + if (VERBOSE) { + System.out.println("This JVM is 64bit: " + Constants.JRE_IS_64BIT); + System.out.println("Reference size in this JVM: " + NUM_BYTES_OBJECT_REF); + System.out.println("Object header size in this JVM: " + NUM_BYTES_OBJECT_HEADER); + System.out.println("Array header size in this JVM: " + NUM_BYTES_ARRAY_HEADER); + System.out.println("Object alignment in this JVM: " + NUM_BYTES_OBJECT_ALIGNMENT); + } + assertTrue(NUM_BYTES_OBJECT_REF == 4 || NUM_BYTES_OBJECT_REF == 8); + if (!Constants.JRE_IS_64BIT) { + assertEquals("For 32bit JVMs, reference size must always be 4", 4, NUM_BYTES_OBJECT_REF); + } + } - private static final class Holder { + @SuppressWarnings("unused") + private static class Holder { long field1 = 5000L; String name = "name"; Holder holder; + long field2, field3, field4; Holder() { } @@ -48,4 +117,14 @@ this.field1 = field1; } } + + @SuppressWarnings("unused") + private static class HolderSubclass extends Holder { + byte foo; + int bar; + } + + private static class HolderSubclass2 extends Holder { + // empty, only inherits all fields -> size should be identical to superclass + } }