/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.mmtk.harness.vm; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Collection; import org.mmtk.harness.Collector; import org.mmtk.harness.Mutator; import org.mmtk.harness.lang.Trace; import org.mmtk.harness.lang.Trace.Item; import org.mmtk.harness.sanity.Sanity; import org.mmtk.plan.CollectorContext; import org.mmtk.plan.MutatorContext; import org.mmtk.plan.Plan; import org.mmtk.policy.Space; import org.mmtk.utility.alloc.Allocator; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.unboxed.*; import org.vmmagic.unboxed.harness.ArchitecturalWord; import org.vmmagic.unboxed.harness.MemoryConstants; import org.vmmagic.unboxed.harness.SimulatedMemory; /** * MMTk Harness implementation of MMTk object model * GC header (determined by MMTk) * Object id (age in allocations); (int) * Allocation site (int) * The size of the data section in words. (int) * The number of reference words. (int) * Status Word (includes GC) (WORD) * References * Data */ @Uninterruptible public final class ObjectModel extends org.mmtk.vm.ObjectModel { private static final boolean IS_32_BIT = ArchitecturalWord.getModel().bitsInWord() == 32; /** The total header size (including any requested GC words) */ public static final int HEADER_WORDS = (IS_32_BIT ? 5 : 3) + ActivePlan.constraints.gcHeaderWords(); /** The number of bytes in the header */ private static final int HEADER_SIZE = HEADER_WORDS << MemoryConstants.LOG_BYTES_IN_WORD; /** The number of bytes requested for GC in the header */ private static final int GC_HEADER_BYTES = ActivePlan.constraints.gcHeaderWords() << MemoryConstants.LOG_BYTES_IN_WORD; /** The offset of the first GC header word */ private static final Offset GC_OFFSET = Offset.zero(); /** The offset of the object ID */ private static final Offset ID_OFFSET = GC_OFFSET.plus(GC_HEADER_BYTES); /** The offset of the allocation site */ private static final Offset SITE_OFFSET = ID_OFFSET.plus(MemoryConstants.BYTES_IN_INT); /** The offset of the UInt32 storing the number of data fields */ private static final Offset DATACOUNT_OFFSET = SITE_OFFSET.plus(MemoryConstants.BYTES_IN_INT); /** The offset of the UInt32 storing the number of reference fields */ private static final Offset REFCOUNT_OFFSET = DATACOUNT_OFFSET.plus(MemoryConstants.BYTES_IN_INT); /** The offset of the status word */ private static final Offset STATUS_OFFSET = REFCOUNT_OFFSET.plus(MemoryConstants.BYTES_IN_INT); /** The offset of the first reference field. */ public static final Offset REFS_OFFSET = STATUS_OFFSET.plus(MemoryConstants.BYTES_IN_WORD); @SuppressWarnings("unused") private static void printObjectLayout(PrintStream wr) { wr.printf("GC_OFFSET=%s:%d, ID_OFFSET=%s, SITE_OFFSET=%s%n", GC_OFFSET, GC_HEADER_BYTES, ID_OFFSET, SITE_OFFSET); wr.printf("DATACOUNT_OFFSET=%s, REFCOUNT_OFFSET=%s, STATUS_OFFSET=%s%n", DATACOUNT_OFFSET, REFCOUNT_OFFSET, STATUS_OFFSET); wr.printf("REFS_OFFSET=%s, HEADER_SIZE=%d%n", REFS_OFFSET,HEADER_SIZE); wr.flush(); } /** Max data fields in an object */ public static final int MAX_DATA_FIELDS = Integer.MAX_VALUE; /** Max pointer fields in an object */ public static final int MAX_REF_FIELDS = Integer.MAX_VALUE; /** Has this object been hashed? */ private static final int HASHED = 0x1 << (3 * MemoryConstants.BITS_IN_BYTE); /** Has this object been moved since it was hashed? */ private static final int HASHED_AND_MOVED = 0x3 << (3 * MemoryConstants.BITS_IN_BYTE); /** Is this object 8 byte aligned */ private static final int DOUBLE_ALIGN = 0x1 << (2 * MemoryConstants.BITS_IN_BYTE); /** The value placed in alignment holes */ public static final int ALIGNMENT_VALUE = 1; /* * Object identifiers. Objects are allocated a sequential identifier. */ /** The next object id that will be allocated */ private static int nextObjectId = 1; /** Allocate a new (sequential) object id */ private static synchronized int allocateObjectId() { return nextObjectId++; } /** * @return the last object ID allocated - for error reporting: NOT THREAD SAFE!!! */ public static int lastObjectId() { return nextObjectId; } /* * Watchpoints by object identifier. */ private static final Set<Integer> watchSet = new HashSet<Integer>(); /** * @param object The object * @return True if this object is being watched */ public static boolean isWatched(ObjectReference object) { return watchSet.contains(getId(object)); } /** * Add the specified object (numbered in allocation order) to the watch set. * @param id Object ID */ public static void watchObject(int id) { watchSet.add(id); } static { //printObjectLayout(System.out); // Sometimes helps debug the object header layout assert REFS_OFFSET.EQ(Offset.fromIntSignExtend(HEADER_SIZE)); } /** * Allocate an object and return the ObjectReference. * * @param context The MMTk MutatorContext to use. * @param refCount The number of reference fields. * @param dataCount The number of data fields. * @param doubleAlign Align the object at an 8 byte boundary? * @param site The allocation site * @return The new ObjectReference. */ public static ObjectReference allocateObject(MutatorContext context, int refCount, int dataCount, boolean doubleAlign, int site) { int bytes = (HEADER_WORDS + refCount + dataCount) << MemoryConstants.LOG_BYTES_IN_WORD; int align = ArchitecturalWord.getModel().bitsInWord() == 64 ? MemoryConstants.BYTES_IN_WORD : (doubleAlign ? 2 : 1) * MemoryConstants.BYTES_IN_INT; int allocator = context.checkAllocator(bytes, align, refCount == 0 ? Plan.ALLOC_NON_REFERENCE : Plan.ALLOC_DEFAULT); // if (allocator == Plan.ALLOC_LOS) { // System.out.printf("Allocating %d bytes in LOS%n",bytes); // } // Allocate the raw memory Address region = context.alloc(bytes, align, 0, allocator, 0); // Create an object reference. ObjectReference ref = region.toObjectReference(); if (SimulatedMemory.isWatched(region,bytes)) { Trace.printf("%4d alloc %s [%s-%s]%n", Thread.currentThread().getId(), region.toObjectReference(), region, region.plus(bytes)); } if (doubleAlign) region.store(DOUBLE_ALIGN, STATUS_OFFSET); setId(ref, allocateObjectId()); setSite(ref, site); setRefCount(ref, refCount); setDataCount(ref, dataCount); Sanity.getObjectTable().alloc(region, bytes); if (isWatched(ref)) { System.err.printf("WATCH: Object %s created%n",objectIdString(ref)); } // Call MMTk postAlloc context.postAlloc(ref, null, bytes, allocator); return ref; } /** * Get the number of references in an object. * @param object The object * @return number of reference fields in the object. */ public static int getRefs(ObjectReference object) { return object.toAddress().loadInt(REFCOUNT_OFFSET); } /** * Get the object identifier. * @param object The object * @return The object identifier */ public static int getId(ObjectReference object) { return object.toAddress().loadInt(ID_OFFSET) >>> 2; } /** * Set the object identifier. */ private static void setId(ObjectReference object, int value) { object.toAddress().store(value << 2, ID_OFFSET); } public static boolean hasValidId(ObjectReference object) { return getId(object) > 0 && getId(object) < nextObjectId; } /** * Set the call site identifier. */ private static void setSite(ObjectReference object, int site) { object.toAddress().store(site, SITE_OFFSET); } /** * Get the call site identifier. * @param object The object * @return The call site */ public static int getSite(ObjectReference object) { return object.toAddress().loadInt(SITE_OFFSET); } /** * Get the number of data words in the object. * @param object The object * @return The data count */ public static int getDataCount(ObjectReference object) { return object.toAddress().loadInt(DATACOUNT_OFFSET); } /** * Set the number of data words in the object. */ private static void setDataCount(ObjectReference object, int count) { assert count < MAX_DATA_FIELDS && count >= 0 : "Too many data fields, "+count; object.toAddress().store(count, DATACOUNT_OFFSET); } /** * Set the number of references in the object. */ private static void setRefCount(ObjectReference object, int count) { assert count < MAX_REF_FIELDS && count >= 0 : "Too many reference fields, "+count; object.toAddress().store(count, REFCOUNT_OFFSET); } /** * Get the current size of an object. * @param object The object * @return The object size in bytes */ public static int getSize(ObjectReference object) { int refs = getRefs(object); int data = getDataCount(object); boolean includesHash = (object.toAddress().loadInt(STATUS_OFFSET) & HASHED_AND_MOVED) == HASHED_AND_MOVED; return getSize(refs, data) + (includesHash ? MemoryConstants.BYTES_IN_WORD : 0); } /** * Get the size this object will require when copied. * @param object The object * @return The object size in bytes after copying */ public static int getCopiedSize(ObjectReference object) { int refs = getRefs(object); int data = getDataCount(object); boolean needsHash = (object.toAddress().loadInt(STATUS_OFFSET) & HASHED) == HASHED; return getSize(refs, data) + (needsHash ? MemoryConstants.BYTES_IN_WORD : 0); } /** * Return the address of a reference field in an object. * * @param object The object with the references. * @param index The reference index. * @return The address of the specified reference field */ public static Address getRefSlot(ObjectReference object, int index) { return object.toAddress().plus(REFS_OFFSET).plus(index << MemoryConstants.LOG_BYTES_IN_WORD); } /** * Return the address of the specified data slot. * * @param object The object with the data slot. * @param index The data slot index. * @return The address of the specified data field */ public static Address getDataSlot(ObjectReference object, int index) { return getRefSlot(object, index + getRefs(object)); } /** * Calculate the size of an object. * @param refs Number of reference fields * @param data Number of data fields * @return Size in bytes of the object */ public static int getSize(int refs, int data) { return (HEADER_WORDS + refs + data) << MemoryConstants.LOG_BYTES_IN_WORD; } /** * Return the next object id to be allocated. For debugging/formatting * purposes - not thread safe. * @return The next object ID */ public static int nextObjectId() { return nextObjectId; } /** * Return the hash code for this object. * * @param ref The object. * @return The hash code */ public static int getHashCode(ObjectReference ref) { Sanity.assertValid(ref); Address addr = ref.toAddress(); int status = addr.loadInt(STATUS_OFFSET) & HASHED_AND_MOVED; if (status == 0) { // Set status to be HASHED int old; do { old = addr.prepareInt(STATUS_OFFSET); } while (!addr.attempt(old, old | HASHED, STATUS_OFFSET)); } else if (status == HASHED_AND_MOVED) { // Load stored hash code return addr.loadInt(Offset.fromIntZeroExtend(getSize(ref) - MemoryConstants.BYTES_IN_WORD)); } return addr.toInt() >>> MemoryConstants.LOG_BYTES_IN_WORD; } /** * Copy an object using a plan's allocCopy to get space and install * the forwarding pointer. On entry, <code>from</code> must have * been reserved for copying by the caller. This method calls the * plan's <code>getStatusForCopy()</code> method to establish a new * status word for the copied object and <code>postCopy()</code> to * allow the plan to perform any post copy actions. * * @param from the address of the object to be copied * @param allocator The allocator to use. * @return the address of the new object */ @Override public ObjectReference copy(ObjectReference from, int allocator) { int oldBytes = getSize(from); int newBytes = getCopiedSize(from); int align = getAlignWhenCopied(from); CollectorContext c = Collector.current().getContext(); allocator = c.copyCheckAllocator(from, newBytes, align, allocator); Address toRegion = c.allocCopy(from, newBytes, align, getAlignOffsetWhenCopied(from), allocator); ObjectReference to = toRegion.toObjectReference(); if (isWatched(from) || Trace.isEnabled(Item.COLLECT)) { Trace.printf(Item.COLLECT,"Copying object %s from %s to %s%n", objectIdString(from), getString(from), getString(to)); } Sanity.assertValid(from); Sanity.getObjectTable().copy(from, to); Address fromRegion = from.toAddress(); for(int i=0; i < oldBytes; i += MemoryConstants.BYTES_IN_INT) { toRegion.plus(i).store(fromRegion.plus(i).loadInt()); } int status = toRegion.loadInt(STATUS_OFFSET); if ((status & HASHED_AND_MOVED) == HASHED) { toRegion.store(status | HASHED_AND_MOVED, STATUS_OFFSET); toRegion.store(getHashCode(from), Offset.fromIntZeroExtend(oldBytes)); } c.postCopy(to, null, newBytes, allocator); if (isWatched(from)) { System.err.printf("WATCH: Object %d copied from %s to %s%n",getId(from), addressAndSpaceString(from),addressAndSpaceString(to)); dumpObjectHeader("after copy: ", to); } return to; } /** * Copy an object to be pointer to by the to address. This is required * for delayed-copy collectors such as compacting collectors. During the * collection, MMTk reserves a region in the heap for an object as per * requirements found from ObjectModel and then asks ObjectModel to * determine what the object's reference will be post-copy. * * @param from the address of the object to be copied * @param to The target location. * @param toRegion The start of the region that was reserved for this object * @return Address The address past the end of the copied object */ @Override public Address copyTo(ObjectReference from, ObjectReference to, Address toRegion) { boolean traceThisObject = Trace.isEnabled(Item.COLLECT) || isWatched(from); if (traceThisObject) { Trace.printf(Item.COLLECT,"Copying object %s explicitly from %s/%s to %s/%s, region %s%n", objectIdString(from), from,Space.getSpaceForObject(from).getName(), to,Space.getSpaceForObject(to).getName(), toRegion); dumpObjectHeader("Before copy: ", from); } Sanity.assertValid(from); Sanity.getObjectTable().copy(from, to); boolean doCopy = !from.equals(to); int bytes = getSize(from); if (doCopy) { Address srcRegion = from.toAddress(); Address dstRegion = to.toAddress(); for(int i=0; i < bytes; i += MemoryConstants.BYTES_IN_INT) { int before = srcRegion.plus(i).loadInt(); dstRegion.plus(i).store(before); int after = dstRegion.plus(i).loadInt(); if (traceThisObject) { System.err.printf("copy %s/%08x -> %s/%08x%n", srcRegion.plus(i),before,dstRegion.plus(i),after); } } int status = dstRegion.loadInt(STATUS_OFFSET); if ((status & HASHED_AND_MOVED) == HASHED) { dstRegion.store(status | HASHED_AND_MOVED, STATUS_OFFSET); dstRegion.store(getHashCode(from), Offset.fromIntZeroExtend(bytes)); bytes += MemoryConstants.BYTES_IN_WORD; } Allocator.fillAlignmentGap(toRegion, dstRegion); } else { if (traceThisObject) { Trace.printf(Item.COLLECT,"%s: no copy required%n", getString(from)); } } Address objectEndAddress = getObjectEndAddress(to); if (traceThisObject) { dumpObjectHeader("After copy: ", to); } Sanity.assertValid(to); return objectEndAddress; } /** * Return the reference that an object will be refered to after it is copied * to the specified region. Used in delayed-copy collectors such as compacting * collectors. * * @param from The object to be copied. * @param to The region to be copied to. * @return The resulting reference. */ @Override public ObjectReference getReferenceWhenCopiedTo(ObjectReference from, Address to) { return to.toObjectReference(); } /** * Return the size required to copy an object * * @param object The object whose size is to be queried * @return The size required to copy <code>obj</code> */ @Override public int getSizeWhenCopied(ObjectReference object) { return getCopiedSize(object); } /** * Return the alignment requirement for a copy of this object * * @param object The object whose size is to be queried * @return The alignment required for a copy of <code>obj</code> */ @Override public int getAlignWhenCopied(ObjectReference object) { boolean doubleAlign = (object.toAddress().loadInt(STATUS_OFFSET) & DOUBLE_ALIGN) == DOUBLE_ALIGN; return (doubleAlign ? 2 : 1) * MemoryConstants.BYTES_IN_WORD; } /** * Return the alignment offset requirements for a copy of this object * * @param object The object whose size is to be queried * @return The alignment offset required for a copy of <code>obj</code> */ @Override public int getAlignOffsetWhenCopied(ObjectReference object) { return 0; } /** * Return the size used by an object * * @param object The object whose size is to be queried * @return The size of <code>obj</code> */ @Override public int getCurrentSize(ObjectReference object) { return getSize(object); } /** * Return the next object in the heap under contiguous allocation */ @Override public ObjectReference getNextObject(ObjectReference object) { Address nextAddress = object.toAddress().plus(getSize(object)); if (nextAddress.loadInt() == ALIGNMENT_VALUE) { nextAddress = nextAddress.plus(MemoryConstants.BYTES_IN_WORD); } if (nextAddress.loadWord().isZero()) { return ObjectReference.nullReference(); } return nextAddress.toObjectReference(); } /** * Return an object reference from knowledge of the low order word */ @Override public ObjectReference getObjectFromStartAddress(Address start) { if ((start.loadInt() & ALIGNMENT_VALUE) != 0) { start = start.plus(MemoryConstants.BYTES_IN_WORD); } return start.toObjectReference(); } /** * Return the start address from an object reference */ public static Address getStartAddressFromObject(ObjectReference object) { return object.toAddress(); } /** * Gets a pointer to the address just past the end of the object. * * @param object The object. */ @Override public Address getObjectEndAddress(ObjectReference object) { return object.toAddress().plus(getSize(object)); } /** * Get the type descriptor for an object. * * @param ref address of the object * @return byte array with the type descriptor */ @Override public byte[] getTypeDescriptor(ObjectReference ref) { return getString(ref).getBytes(); } /** * Is the passed object an array? * * @param object address of the object */ @Override public boolean isArray(ObjectReference object) { Assert.notImplemented(); return false; } /** * Is the passed object a primitive array? * * @param object address of the object */ @Override public boolean isPrimitiveArray(ObjectReference object) { Assert.notImplemented(); return false; } /** * Get the length of an array object. * * @param object address of the object * @return The array length, in elements */ @Override public int getArrayLength(ObjectReference object) { Assert.notImplemented(); return 0; } /** * Attempts to set the bits available for memory manager use in an * object. The attempt will only be successful if the current value * of the bits matches <code>oldVal</code>. The comparison with the * current value and setting are atomic with respect to other * allocators. * * @param object the address of the object * @param oldVal the required current value of the bits * @param newVal the desired new value of the bits * @return <code>true</code> if the bits were set, * <code>false</code> otherwise */ @Override public boolean attemptAvailableBits(ObjectReference object, Word oldVal, Word newVal) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { Word actual = object.toAddress().loadWord(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.status:%s=%s ? ->%s%n", getString(object),actual,oldVal,newVal); } return object.toAddress().attempt(oldVal, newVal, STATUS_OFFSET); } /** * Gets the value of bits available for memory manager use in an * object, in preparation for setting those bits. * * @param object the address of the object * @return the value of the bits */ @Override public Word prepareAvailableBits(ObjectReference object) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { Word old = object.toAddress().loadWord(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.gcword=%s (prepare)%n", getString(object),old); } return object.toAddress().prepareWord(STATUS_OFFSET); } /** * Sets the byte available for memory manager use in an object. * * @param object the address of the object * @param val the new value of the byte */ @Override public void writeAvailableByte(ObjectReference object, byte val) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { byte old = object.toAddress().loadByte(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.gcbyte:%d->%d%n", getString(object),old,val); } object.toAddress().store(val, STATUS_OFFSET); } /** * Read the byte available for memory manager use in an object. * * @param object the address of the object * @return the value of the byte */ @Override public byte readAvailableByte(ObjectReference object) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { byte old = object.toAddress().loadByte(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.gcbyte=%d%n", getString(object),old); } return object.toAddress().loadByte(STATUS_OFFSET); } /** * Sets the bits available for memory manager use in an object. * * @param object the address of the object * @param val the new value of the bits */ @Override public void writeAvailableBitsWord(ObjectReference object, Word val) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { Word old = object.toAddress().loadWord(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.gcword:%s->%s%n", getString(object),old,val); } object.toAddress().store(val, STATUS_OFFSET); } /** * Read the bits available for memory manager use in an object. * * @param object the address of the object * @return the value of the bits */ @Override public Word readAvailableBitsWord(ObjectReference object) { if (Trace.isEnabled(Item.AVBYTE) || isWatched(object)) { Word old = object.toAddress().loadWord(STATUS_OFFSET); Trace.printf(Item.AVBYTE,"%s.gcword=%s%n", getString(object),old); } return object.toAddress().loadWord(STATUS_OFFSET); } /** * Gets the offset of the memory management header from the object * reference address. XXX The object model / memory manager * interface should be improved so that the memory manager does not * need to know this. * * @return the offset, relative the object reference address */ @Override public Offset GC_HEADER_OFFSET() { return GC_OFFSET; } /** * Returns the lowest address of the storage associated with an object. * * @param object the reference address of the object * @return the lowest address of the object */ @Override public Address objectStartRef(ObjectReference object) { return object.toAddress(); } /** * Returns an address guaranteed to be inside the storage assocatied * with and object. * * @param object the reference address of the object * @return an address inside the object */ @Override public Address refToAddress(ObjectReference object) { return object.toAddress(); } /** * Checks if a reference of the given type in another object is * inherently acyclic. The type is given as a TIB. * * @return <code>true</code> if a reference of the type is * inherently acyclic */ @Override public boolean isAcyclic(ObjectReference typeRef) { return false; } /** @return The offset from array reference to element zero */ @Override protected Offset getArrayBaseOffset() { return REFS_OFFSET; } /* * String representations of objects and logging/dump methods */ /** * Dump debugging information for an object. * * @param object The object whose information is to be dumped */ @Override public void dumpObject(ObjectReference object) { System.err.println("==================================="); System.err.println(getString(object)); System.err.println("==================================="); SimulatedMemory.dumpMemory(object.toAddress(), 0, getSize(object)); System.err.println("==================================="); } /** * Format the object for dumping, and trim to a max width. * @param width Max output width * @param object The object to dump * @return The formatted object */ public static String formatObject(int width, ObjectReference object) { String base = getString(object); return base.substring(Math.max(base.length() - width,0)); } /** * Print an object's header * @param prefix TODO * @param object The object reference */ public static void dumpObjectHeader(String prefix, ObjectReference object) { int gcWord = object.toAddress().loadInt(GC_OFFSET); int statusWord = object.toAddress().loadInt(STATUS_OFFSET); System.err.printf("%sObject %s[%d@%s]<%x,%x>%n",prefix,object,getId(object),Mutator.getSiteName(object),gcWord,statusWord); } /** * Dump (logical) information for an object. Returns the non-null pointers * in the object. * @param width Output width * @param object The object whose information is to be dumped * @return The non-null references in the object */ public static Collection<ObjectReference> dumpLogicalObject(int width, ObjectReference object) { int refCount = getRefs(object); int dataCount = getDataCount(object); boolean hashed = (object.toAddress().loadInt(STATUS_OFFSET) & HASHED) == HASHED; List<ObjectReference> pointers = new ArrayList<ObjectReference>(refCount); System.err.printf(" Object %s <%d %d %1s> [", ObjectModel.formatObject(width, object), refCount, dataCount, (hashed ? "H" : "")); if (refCount > 0) { for(int i=0; i < refCount; i++) { ObjectReference ref = ActivePlan.plan.loadObjectReference(getRefSlot(object, i)); System.err.print(" "); System.err.print(ObjectModel.formatObject(width, ref)); if (!ref.isNull()) { pointers.add(ref); } } } System.err.println(" ]"); return pointers; } /** * String description of an object. * * @param ref address of the object * @return "Object[size b nR/mD] */ public static String getString(ObjectReference ref) { if (ref.isNull()) return "<null>"; int refs = getRefs(ref); int data = getDataCount(ref); return addressAndSpaceString(ref)+objectIdString(ref)+refs + "R" + data + "D"; } /** * Brief string description of an object * @param ref The object * @return "[id@line:col]" */ private static String objectIdString(ObjectReference ref) { return String.format("[%d@%s]", getId(ref),Mutator.getSiteName(ref)); } /** * Address and space name (eg 0x45678900/ms) * @param ref The object * @return "address/space" */ public static String addressAndSpaceString(ObjectReference ref) { return String.format("%s/%s",ref, Space.getSpaceForObject(ref).getName()); } /** * Address and space name (eg 0x45678900/ms) * @param addr The object * @return "address/space" */ public static String addressAndSpaceString(Address addr) { return String.format("%s/%s",addr, Space.getSpaceForAddress(addr).getName()); } }