/* * Copyright (c) 2008, 2012, 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. * * 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 com.sun.max.tele.heap; import java.io.*; import java.lang.reflect.*; import java.math.*; import java.text.*; import java.util.*; import com.sun.max.lang.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.memory.*; import com.sun.max.tele.object.*; import com.sun.max.tele.reference.*; import com.sun.max.tele.type.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.heap.*; import com.sun.max.vm.hosted.*; import com.sun.max.vm.tele.*; /** * Singleton cache of information about heap storage in the VM. * <p> * Initialization between this class and {@link VmClassAccess} are mutually * dependent. The cycle is broken by creating this class in a partially initialized * state that only considers the boot heap region; this class is only made fully functional * with a call to {@link #initialize(long)}, which requires that {@link VmClassAccess} be * fully initialized. * <p> * Interesting heap state includes the list of memory regions allocated. * <p> * This class needs to be specialized by a helper class that * implements the interface {@link RemoteHeapScheme}, typically * a class that contains knowledge of the heap implementation * configured into the VM. * * @see InspectableHeapInfo * @see HeapScheme */ public final class VmHeapAccess extends AbstractVmHolder implements MaxHeap, VmAllocationHolder<MaxHeap> { private static final int STATS_NUM_TYPE_COUNTS = 10; /** * Name of system property that specifies the address where the heap is located, or where it should be relocated, depending * on the user of the class. */ public static final String HEAP_ADDRESS_PROPERTY = VmObjectAccess.HEAP_ADDRESS_PROPERTY; private static final int TRACE_VALUE = 1; private final TimedTrace updateTracer; private long lastUpdateEpoch = -1L; protected static VmHeapAccess vmHeap; /** * If the heap address is set explicitly, return it, else leave it to caller ti try to determine it. * @return zero if not set, else the address. */ public static long heapAddressOption() { long heap = 0L; String heapValue = System.getProperty(HEAP_ADDRESS_PROPERTY); if (heapValue != null) { try { int radix = 10; if (heapValue.startsWith("0x")) { radix = 16; heapValue = heapValue.substring(2); } // Use BigInteger to handle unsigned 64-bit values that are greater than Long.MAX_VALUE BigInteger bi = new BigInteger(heapValue, radix); heap = bi.longValue(); } catch (NumberFormatException e) { System.err.println("Error parsing value of " + HEAP_ADDRESS_PROPERTY + " system property: " + heapValue + ": " + e); } } return heap; } /** * Returns the singleton manager of cached information about the heap in the VM, * specialized for the particular implementation of {@link HeapScheme} in the VM. * <p> * This manager is not fully functional until after a call to {@link #initialize(long)}. * However, {@link #initialize(long)} must be called only * after the {@link VmClassAccess} is fully initialized; otherwise, a circular * dependency will cause breakage. */ public static VmHeapAccess make(TeleVM vm, VmAddressSpace addressSpace) { if (vmHeap == null) { vmHeap = new VmHeapAccess(vm, addressSpace); } return vmHeap; } private static final String entityName = "Heap"; private final String entityDescription; /** * Information about the heap is not fully initialized until, among other things, * the boot heap region is described uniformly in terms of the object in VM memory * that describes it. This is not possible during startup because of circular * dependencies, so in the pre-initialized phase the boot heap region must be known * for other parts of the startup sequence to work. This is done by representing * it with a faked "fixed" region whose dimensions are discovered specially. Once * enough services are available, this representation is replaced with a standard * one that refers to the VM object. */ private boolean isInitialized = false; private String bootHeapRegionName = "uninitialized"; /** * Surrogate for the object in VM memory that describes the memory region holding the boot heap. */ private TeleBootHeapRegion teleBootHeapMemoryRegion = null; /** * Description of the boot region holding objects in the VM. */ private VmHeapRegion bootHeapRegion = null; /** * Surrogate for the object in VM memory that describes the memory region holding the immortal heap. */ private TeleMemoryRegion teleImmortalHeapRegion = null; /** * Description of the immortal region holding objects in the Vm. */ private VmHeapRegion immortalHeapRegion = null; // Keep track of the heap regions we know about (by starting address) and the objects we use to model them. // Assume here that heap regions, once allocated, keep the same starting location. // It is critical to associate the same {@link VmHeapRegion} instance for each heap region in the VM, // since they have a lot of state used to manage references to objects in those regions. private final HashMap<Long, VmHeapRegion> addressToVmHeapRegion = new HashMap<Long, VmHeapRegion>(); /** * Unmodifiable list of all currently known heap regions. */ private volatile List<VmHeapRegion> allHeapRegions; private Pointer teleRuntimeMemoryRegionRegistrationPointer = Pointer.zero(); private RemoteHeapScheme remoteHeapScheme = null; private List<MaxCodeLocation> heapInspectableMethods = null; private int lastRegionCount = 0; private final Object statsPrinter = new Object() { @Override public String toString() { final StringBuilder msg = new StringBuilder(); final int size = allHeapRegions.size(); msg.append("#regions=(").append(size); msg.append(", new=").append(size - lastRegionCount).append(")"); lastRegionCount = size; return msg.toString(); } }; private VmHeapAccess(TeleVM vm, VmAddressSpace addressSpace) { super(vm); final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " creating"); tracer.begin(); this.entityDescription = "Heap allocation and management for the " + vm().entityName(); this.updateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " updating"); final List<VmHeapRegion> heapRegions = new ArrayList<VmHeapRegion>(); // Leverage specific knowledge of the whereabouts of the boot heap region to create // a preliminary ("fake") representation of the heap, needed for uniform treatment of objects // during the startup phase. This is specifically needed in order to create the // {@link VmClassRegistry}, which is needed for generalized treatment of objects. // This fake representation of the boot heap region eventually gets replaced with a standard // representation that is linked to the VM object that describes the boot heap. final Pointer bootHeapStart = vm().bootImageStart(); final int bootHeapSize = vm().bootImage().header.heapSize; bootHeapRegion = new VmHeapRegion(vm, "Fake Heap-boot region", bootHeapStart, bootHeapSize); addressSpace.add(bootHeapRegion.memoryRegion()); heapRegions.add(bootHeapRegion); final String heapSchemeName = vm.heapScheme().name(); final String thisClassName = VmHeapAccess.class.getName(); final String thisPackageName = thisClassName.substring(0, thisClassName.lastIndexOf(".")); try { final Class<?> remoteHeapSchemeClass = Class.forName(thisPackageName + ".Remote" + heapSchemeName); Constructor constructor = remoteHeapSchemeClass.getDeclaredConstructor(new Class[] {TeleVM.class}); this.remoteHeapScheme = (RemoteHeapScheme) constructor.newInstance(new Object[] {vm}); } catch (Exception e) { remoteHeapScheme = new UnknownRemoteHeapScheme(vm); TeleWarning.message("Unable to construct implementation of TeleHeapScheme for HeapScheme=" + heapSchemeName + ", using default"); e.printStackTrace(); } // In a normal session the dynamic heap will not have been allocated yet, but there might be when attaching // to a dumped image or running VM for (VmHeapRegion heapRegion : remoteHeapScheme.heapRegions()) { heapRegions.add(heapRegion); } this.allHeapRegions = Collections.unmodifiableList(heapRegions); tracer.end(statsPrinter); } /** * Lazy initialization; try to keep data reading out of constructor. * Note that the representation of the boot heap is a fake until the completion of this initializer. */ public void initialize(long epoch) { assert vm().lockHeldByCurrentThread(); final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " initializing"); tracer.begin(); // Get a copy of the string in the VM that holds the name of the boot heap final RemoteReference nameReference = fields().Heap_HEAP_BOOT_NAME.readRemoteReference(vm()); this.bootHeapRegionName = vm().getString(nameReference); // Get a local surrogate for the instance of {@link MemoryRegion} in the VM that describes the boot heap final RemoteReference bootHeapRegionReference = fields().Heap_bootHeapRegion.readRemoteReference(vm()); this.teleBootHeapMemoryRegion = (TeleBootHeapRegion) objects().makeTeleObject(bootHeapRegionReference); // Replace the faked representation of the boot heap with one represented uniformly via reference to the VM object vm().addressSpace().remove(this.bootHeapRegion.memoryRegion()); this.bootHeapRegion = new VmHeapRegion(vm(), teleBootHeapMemoryRegion); vm().addressSpace().add(this.bootHeapRegion.memoryRegion()); remoteHeapScheme.initialize(epoch); isInitialized = true; updateMemoryStatus(epoch); tracer.end(statsPrinter); } /** {@inheritDoc} * <p> * Updating the cache of information about <strong>heap regions</strong> is delicate because the descriptions * of those regions must be read, even though those descriptions are themselves heap objects. * Standard inspection machinery might fail to read those objects while the heap description * is in the process of being updated, so we dynamically suspend rejection of object origin * addresses based on heap containment. * <p> * <em>Circularity:</em> This update must take place before general update of all the {@link TeleObject}s, since we * need to know about any newly allocated heap regions. But the objects that describe existing heap regions * therefore won't have been updated yet. In order to be sure that we have the latest information about * every heap object, allocation marks for example, we have to force an early update of the objects holding * that information. */ public void updateMemoryStatus(long epoch) { // Replaces local cache of information about heap regions in the VM. // During this update, any method calls to check heap containment are handled specially. assert vm().lockHeldByCurrentThread(); if (!isInitialized) { Trace.line(TRACE_VALUE, tracePrefix() + "not initialized yet"); return; } if (epoch <= lastUpdateEpoch) { if (epoch == 0 && TeleVM.mode == MaxInspectionMode.ATTACH) { // Special case for attachment mode. The first update was performed while the remote heap scheme // wasn't initialized. This might cause missing remote-scheme specific regions whose descriptions is stored in // the boot region. Trace.line(TRACE_VALUE, tracePrefix() + "attach-mode forced udpate"); } else { Trace.line(TRACE_VALUE, tracePrefix() + "redundant udpate epoch=" + epoch); return; } } updateTracer.begin(); // Suspend checking for heap containment of object origin addresses. updatingHeapMemoryRegions = true; // Starting from scratch, locate all known heap regions; most of the time it won't change. final List<VmHeapRegion> discoveredHeapRegions = new ArrayList<VmHeapRegion>(allHeapRegions.size()); // We already know about the boot heap, and there's no reason to refresh its status discoveredHeapRegions.add(bootHeapRegion); // Check for the {@link ImmortalHeap} description if (teleImmortalHeapRegion == null) { final RemoteReference immortalHeapReference = fields().ImmortalHeap_immortalHeap.readRemoteReference(vm()); if (!immortalHeapReference.isZero()) { teleImmortalHeapRegion = (TeleMemoryRegion) objects().makeTeleObject(immortalHeapReference); immortalHeapRegion = new VmHeapRegion(vm(), teleImmortalHeapRegion); vm().addressSpace().add(immortalHeapRegion.memoryRegion()); } } else { // Force an update, in case it has just been allocated. teleImmortalHeapRegion.updateCache(epoch); } if (immortalHeapRegion != null) { discoveredHeapRegions.add(immortalHeapRegion); } // Update the specific scheme last, in case the immortal heap region has been allocated since the // last time we looked; it will be needed by the scheme update. remoteHeapScheme.updateMemoryStatus(epoch); discoveredHeapRegions.addAll(remoteHeapScheme.heapRegions()); allHeapRegions = Collections.unmodifiableList(discoveredHeapRegions); // Resume checking for heap containment of object origin addresses. updatingHeapMemoryRegions = false; lastUpdateEpoch = epoch; updateTracer.end(statsPrinter); } public String entityName() { return entityName; } public String entityDescription() { return entityDescription; } public MaxEntityMemoryRegion<MaxHeap> memoryRegion() { // The heap has no VM memory allocated, other than the regions allocated directly from the OS. return null; } public boolean contains(Address address) { if (updatingHeapMemoryRegions) { // The call is nested within a call to {@link #refresh}, assume all is well in order // avoid circularity problems while updating the heap region list. return true; } return findHeapRegion(address) != null; } public TeleObject representation() { // No distinguished object in VM runtime represents the heap. return null; } /** * @return description of the special heap region in the {@link BootImage} of the VM. */ public VmHeapRegion bootHeapRegion() { return bootHeapRegion; } /** * @return description of the immortal heap region of the VM. */ public VmHeapRegion immortalHeapRegion() { return immortalHeapRegion; } public List<MaxHeapRegion> heapRegions() { final List<MaxHeapRegion> heapRegions = new ArrayList<MaxHeapRegion>(allHeapRegions.size()); heapRegions.addAll(allHeapRegions); return heapRegions; } public VmHeapRegion findHeapRegion(Address address) { for (VmHeapRegion heapRegion : allHeapRegions) { if (heapRegion.memoryRegion().contains(address)) { return heapRegion; } } return null; } public boolean containsInDynamicHeap(Address address) { final MaxHeapRegion heapRegion = findHeapRegion(address); return heapRegion != null && !heapRegion.equals(bootHeapRegion) && !heapRegion.equals(immortalHeapRegion); } public boolean providesHeapRegionInfo() { return remoteHeapScheme instanceof RemoteRegionBasedHeapScheme; } /** * @param address a location in VM process memory * @return whatever information is known about the status of the location * with respect to memory management, non-null. */ public MaxMemoryManagementInfo getMemoryManagementInfo(Address address) { return remoteHeapScheme.getMemoryManagementInfo(address); } public boolean hasForwarders() { return remoteHeapScheme instanceof VmRelocatingHeap; } public boolean hasMarkBitmap() { return remoteHeapScheme instanceof VmMarkBitmapHeap; } public MaxMarkBitmap markBitmap() { if (hasMarkBitmap()) { final VmMarkBitmapHeap markBitmapHeap = (VmMarkBitmapHeap) remoteHeapScheme; return markBitmapHeap.markBitMap(); } return null; } public boolean hasCardTable() { return remoteHeapScheme instanceof VmCardTableHeap; } public MaxCardTable cardTable() { if (hasCardTable()) { final VmCardTableHeap cardTableHeap = (VmCardTableHeap) remoteHeapScheme; return cardTableHeap.cardTable(); } return null; } public boolean isBootHeapRefMapMarked(Address address) { return teleBootHeapMemoryRegion == null ? false : teleBootHeapMemoryRegion.isRefMapMarked(address); } public List<MaxEntityMemoryRegion<? extends MaxEntity> > memoryAllocations() { final List<MaxEntityMemoryRegion<? extends MaxEntity> > allocations = new ArrayList<MaxEntityMemoryRegion<? extends MaxEntity> >(); for (MaxHeapRegion heapRegion : allHeapRegions) { allocations.add(heapRegion.memoryRegion()); } return allocations; } /** * Avoid potential circularity problems by handling heap queries specially when we * know we are in a refresh cycle during which information about heap regions may not * be well formed. This variable is true during those periods. */ private boolean updatingHeapMemoryRegions = false; /** * Gets the name used by the VM to identify the distinguished * boot heap region, determined by static inspection of the field * that holds the value in the VM. * * @return the name assigned to the VM's boot heap memory region * @see Heap */ public String bootHeapRegionName() { return bootHeapRegionName; } /** * Gets the current GC phase for the heap. */ public HeapPhase phase() { return remoteHeapScheme.phase(); } /** * @see MaxVM#inspectableMethods() */ public List<MaxCodeLocation> heapInspectableMethods() { if (heapInspectableMethods == null) { final List<MaxCodeLocation> locations = new ArrayList<MaxCodeLocation>(); locations.add(vm().codeLocations().createMachineCodeLocation(methods().HeapScheme$Inspect_inspectableIncreaseMemoryRequested, "Increase heap memory")); locations.add(vm().codeLocations().createMachineCodeLocation(methods().HeapScheme$Inspect_inspectableDecreaseMemoryRequested, "Decrease heap memory")); // There may be implementation-specific methods of interest locations.addAll(remoteHeapScheme.inspectableMethods()); heapInspectableMethods = Collections.unmodifiableList(locations); } return heapInspectableMethods; } /** * @see MaxVM#inspectableObjects() */ public List<MaxObject> heapInspectableObjects() { return remoteHeapScheme.inspectableObjects(); } public void printSessionStats(PrintStream printStream, int indent, boolean verbose) { final String indentation = Strings.times(' ', indent); final NumberFormat formatter = NumberFormat.getInstance(); // Statistics about the whole heap long totalHeapSize = 0; for (MaxHeapRegion region : allHeapRegions) { totalHeapSize += region.memoryRegion().nBytes(); } printStream.println(indentation + "Total allocation: " + formatter.format(totalHeapSize) + " bytes"); // Print statistics from each manager, where managers may manage more than one of the regions. final Set<RemoteObjectReferenceManager> managers = new HashSet<RemoteObjectReferenceManager>(); for (VmHeapRegion region : allHeapRegions) { managers.add(region.objectReferenceManager()); } for (RemoteObjectReferenceManager manager : managers) { manager.printObjectSessionStats(printStream, indent, verbose); } } static { if (Trace.hasLevel(1)) { Runtime.getRuntime().addShutdownHook(new Thread("Reference counts") { @Override public void run() { // System.out.println("References(by type):"); // System.out.println(" " + "local = " + vmReferenceManager.localTeleReferenceManager.referenceCount()); } }); } } }