/*
* Copyright (c) 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.text.*;
import java.util.*;
import com.sun.max.lang.*;
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.heap.gcx.*;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.tele.*;
public abstract class AbstractRemoteHeapScheme extends AbstractVmHolder implements RemoteHeapScheme {
protected HeapPhase phase = HeapPhase.MUTATING;
protected long gcStartedCount = 0;
protected long gcCompletedCount = 0;
/**
* The absolute address of the dynamic hub for the class {@link HeapFreeChunk}, stored
* on the assumption that it is in the boot heap and never changes. This gets used for
* quick testing on possible free space chunk origins.
* Remote heap scheme making use of HeapFreeChunk should use {@link #updateFreeHubOrigins()} to
* initialize this field.
*/
protected Address heapFreeChunkHubOrigin = Address.zero();
/**
* The absolute address of the dynamic hub for the class {@link DarkMatter}, stored
* on the assumption that it is in the boot heap and never changes. This gets used for
* quick testing on possible free space chunk origins.
* Remote heap scheme making use of HeapFreeChunk should use {@link #updateFreeHubOrigins()} to
* initialize this field.
*/
protected Address darkMatterHubOrigin = Address.zero();
/**
* The absolute address of the dynamic hub for the class {@link DarkMatter.SmallestDarkMatter}, stored
* on the assumption that it is in the boot heap and never changes. This gets used for
* quick testing on possible free space chunk origins.
* Remote heap scheme making use of HeapFreeChunk should use {@link #updateFreeHubOrigins()} to
* initialize this field.
*/
protected Address smallestDarkMatterHubOrigin = Address.zero();
/**
* A printer for statistics at the end of each update.
*/
protected final Object heapUpdateStatsPrinter = new Object() {
@Override
public String toString() {
final StringBuilder msg = new StringBuilder();
msg.append("GC phase=").append(phase.label());
msg.append(" #starts=").append(gcStartedCount);
msg.append(", #complete=").append(gcCompletedCount);
return msg.toString();
}
};
protected AbstractRemoteHeapScheme(TeleVM vm) {
super(vm);
}
protected void printObjectSessionStatsHeader(PrintStream printStream, int indent, boolean verbose, int totalObjectRefsMapped) {
final NumberFormat formatter = NumberFormat.getInstance();
// Line 0
String indentation = Strings.times(' ', indent);
final StringBuilder sb0 = new StringBuilder();
sb0.append("Dynamic Heap:");
if (verbose) {
sb0.append(" VMScheme=").append(vm().heapScheme().name());
}
printStream.println(indentation + sb0.toString());
// increase indentation
indentation += Strings.times(' ', 4);
// Line 1
final StringBuilder sb1 = new StringBuilder();
sb1.append("phase=").append(phase().label());
sb1.append(", collections completed=").append(formatter.format(gcCompletedCount));
sb1.append(", total object refs mapped=").append(formatter.format(totalObjectRefsMapped));
printStream.println(indentation + sb1.toString());
}
public Class schemeClass() {
return HeapScheme.class;
}
public List<MaxCodeLocation> inspectableMethods() {
return Collections.emptyList();
}
public List<MaxObject> inspectableObjects() {
return Collections.emptyList();
}
/**
* @return surrogate for the VM object that implements the {@link HeapScheme} interface
*/
public TeleHeapScheme teleHeapScheme() {
final TeleVMConfiguration vmConfiguration = vm().teleVMConfiguration();
return vmConfiguration == null ? null : vmConfiguration.heapScheme();
}
public void updateMemoryStatus(long epoch) {
// Check what phase the heap is in with respect to GC.
phase = HeapPhase.values()[fields().InspectableHeapInfo_heapPhaseOrdinal.readInt(vm())];
// Check GC status and update references if a GC has completed since last time we checked
final long oldGcStartedCount = gcStartedCount;
gcStartedCount = fields().InspectableHeapInfo_gcStartedCounter.readLong(vm());
gcCompletedCount = fields().InspectableHeapInfo_gcCompletedCounter.readLong(vm());
// Invariant: oldGcStartedCount <= gcCompletedCount <= gcStartedCount
if (gcStartedCount != gcCompletedCount) {
// A GC is in progress, local cache is out of date by definition but can't update yet
// Sanity check; collection count increases monotonically
assert gcCompletedCount < gcStartedCount;
} else if (oldGcStartedCount != gcStartedCount) {
// GC is not in progress, but a GC has completed since the last time
// we checked, so cached reference data is out of date
// Sanity check; collection count increases monotonically
assert oldGcStartedCount < gcStartedCount;
// vm().referenceManager().updateCache(epoch);
} else {
// oldGcStartedCount == gcStartedCount == gcCompletedCount
// GC is not in progress, and no new GCs have happened, so cached reference data is up to date
}
}
public final HeapPhase phase() {
return phase;
}
/**
* @return the number of heap collections that have started
*/
protected final long gcStartedCount() {
return gcStartedCount;
}
/**
* @return the number of heap collections that have completed
*/
protected final long gcCompletedCount() {
return gcCompletedCount;
}
/**
* Ensure that we have discovered the hubs, presumed to be at fixed locations in the Boot Heap, of quasi-objects
* used by GC to format various kinds of free space in the heap. Once these have been located, the origins of
* such quasi-objects can be recognized with a simple hub address comparison.
*/
protected final void updateFreeHubOrigins() {
if (heapFreeChunkHubOrigin.isZero()) {
// Assume this never changes, once located.
final TeleClassActor hfcClassActor = classes().findTeleClassActor(HeapFreeChunk.class);
if (hfcClassActor != null) {
final TeleDynamicHub freeChunkDynamicHub = hfcClassActor.getTeleDynamicHub();
if (freeChunkDynamicHub != null) {
heapFreeChunkHubOrigin = freeChunkDynamicHub.origin();
}
}
}
if (darkMatterHubOrigin.isZero()) {
// Assume this never changes, once located.
final TeleReferenceClassActor darkMatterClassActor = (TeleReferenceClassActor) objects().makeTeleObject(fields().DarkMatter_DARK_MATTER_ARRAY.readRemoteReference(vm()));
if (darkMatterClassActor != null) {
final TeleDynamicHub darkMatterDynamicHub = darkMatterClassActor.getTeleDynamicHub();
if (darkMatterDynamicHub != null) {
darkMatterHubOrigin = darkMatterDynamicHub.origin();
}
// The dark matter class is not registered in the VM; handle specially so the local instance can be found.
classes().registerUnregisteredClass(DarkMatter.DARK_MATTER_CLASS_NAME, DarkMatter.DARK_MATTER_ARRAY);
}
}
if (smallestDarkMatterHubOrigin.isZero()) {
// Assume this never changes, once located.
final TeleClassActor smallestDarkMatterClassActor = classes().findTeleClassActor(DarkMatter.SmallestDarkMatter.class);
if (smallestDarkMatterClassActor != null) {
final TeleDynamicHub freeChunkDynamicHub = smallestDarkMatterClassActor.getTeleDynamicHub();
if (freeChunkDynamicHub != null) {
smallestDarkMatterHubOrigin = freeChunkDynamicHub.origin();
}
}
}
}
/**
* Determines whether the object at a memory location in the VM is an instance of {@link HeapFreeChunk},
* using a test that depends on the class's dynamic hub object never being collected and never relocated.
*/
protected final boolean isHeapFreeChunkOrigin(Address origin) throws TeleError {
final Address hubOrigin = referenceManager().makeTemporaryRemoteReference(origin).readHubAsWord().asAddress();
return hubOrigin.isNotZero() && hubOrigin.equals(heapFreeChunkHubOrigin);
}
/**
* Determines whether the object at a memory location in the VM is an instance of <em>dark matter,</em>, instances
* of classes {@link DarkMatter} and {@link DarkMatter.SmallestDarkMatter}. The test depends on the classes'
* dynamic hubs never being collected and never relocated.
*/
protected final boolean isDarkMatterOrigin(Address origin) throws TeleError {
final Address hubOrigin = referenceManager().makeTemporaryRemoteReference(origin).readHubAsWord().asAddress();
return hubOrigin.isNotZero() && (hubOrigin.equals(darkMatterHubOrigin) || hubOrigin.equals(smallestDarkMatterHubOrigin));
}
// TODO (mlvdv) Update; won't work now; important for attach mode
/**
* Creates a representation of the contents of the {@linkplain InspectableHeapInfo inspectable list} of dynamic heap
* regions in the VM, using low level mechanisms and performing no checking that the location or objects are valid.
* <p>
* The intention is to provide a way to read this data without needing any of the usual type-based mechanisms for
* reading data, all of which rely on a populated {@link VmClassAccess}. This is needed when attaching to a process
* or reading a dump, where a description of the dynamic heap must be determined before the {@link VmClassAccess}
* can be built. Once those parts of the inspection state are in place, safer methods should be used.
* <p>
* <strong>Unsafe:</strong> this method depends on knowledge of the implementation of arrays.
*
* @return a list of objects, each of which describes a dynamically allocated heap region in the VM, empty array if
* no such heap regions
*
* @see InspectableHeapInfo
*/
protected List<MaxMemoryRegion> getDynamicHeapRegionsUnsafe() {
// Work only with temporary references that are unsafe across GC
// Do no testing to determine if the reference points to a valid object in live memory of the correct types.
final List<MaxMemoryRegion> regions = new ArrayList<MaxMemoryRegion>();
// Location of the inspectable field that might point to an array of dynamically allocated heap regions
final Pointer dynamicHeapRegionsArrayFieldPointer = vm().bootImageStart().plus(vm().bootImage().header.dynamicHeapRegionsArrayFieldOffset);
// Value of the field, possibly a pointer to an array of dynamically allocated heap regions
final Word fieldValue = memory().readWord(dynamicHeapRegionsArrayFieldPointer.asAddress());
if (fieldValue.isNotZero()) {
// Assert that this points to an array of references, read as words
final RemoteReference wordArrayRef = referenceManager().makeTemporaryRemoteReference(fieldValue.asAddress());
final int wordArrayLength = objects().unsafeReadArrayLength(wordArrayRef);
// Read the references as words to avoid using too much machinery
for (int index = 0; index < wordArrayLength; index++) {
// Read an entry from the array
final Word regionReferenceWord = Layout.getWord(wordArrayRef, index);
// Assert that this points to an object of type {@link MemoryRegion} in the VM
RemoteReference memoryRegionRef = referenceManager().makeTemporaryRemoteReference(regionReferenceWord.asAddress());
// Read the field MemoryRegion.start
final Address regionStartAddress = memoryRegionRef.readWord(fields().MemoryRegion_start.fieldActor().offset()).asAddress();
// Read the field MemoryRegion.size
final int regionSize = memoryRegionRef.readInt(fields().MemoryRegion_size.fieldActor().offset());
regions.add(new TeleFixedMemoryRegion(vm(), "Fake", regionStartAddress, regionSize));
}
}
return regions;
}
}