/*
* 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 static com.sun.max.vm.heap.HeapPhase.*;
import java.io.*;
import java.lang.management.*;
import java.text.*;
import java.util.*;
import com.sun.max.lang.*;
import com.sun.max.memory.*;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.tele.TeleVM.InitializationListener;
import com.sun.max.tele.debug.*;
import com.sun.max.tele.memory.*;
import com.sun.max.tele.object.*;
import com.sun.max.tele.reference.*;
import com.sun.max.tele.reference.semispace.*;
import com.sun.max.tele.reference.semispace.SemiSpaceRemoteReference.RefStateCount;
import com.sun.max.tele.util.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.heap.gcx.ms.*;
import com.sun.max.vm.heap.sequential.semiSpace.*;
import com.sun.max.vm.runtime.*;
/**
* <p>
* Inspection support specialized for the basic {@linkplain MSHeapScheme SemiSpaceHeapScheme semispace implementation}
* of {@link HeapScheme} in the VM.
* </p>
* <p>
* This support will function correctly whether or not the VM is built in DEBUG mode. When built in DEBUG mode, the GC
* will {@linkplain Memory#ZAPPED_MARKER zap} all memory unallocated space.
* </p>
* <p>
* This implementation maintains two <em>maps</em> from VM memory locations to {@link RemoteReference}s: one for each of
* the two memory regions used by the scheme(<em>To-Space</em> and <em>From-Space</em>). When the collector
* <em>swaps</em> at the beginning of {@linkplain HeapPhase#ANALYZING analyzing}, the two maps are likewise swapped.
* Which is to say, the From-Space Map always contains references only into From-Space, wherever From-Space is located.
* </p>
* <p>
* References in this implementation can refer to {@linkplain ObjectStatus#LIVE LIVE} objects in the VM heap or to
* {@linkplain ObjectStatus#FORWARDER FORWARDER} quasi-objects. The term <em>quasi-object</em> refers to an area of
* memory formatted as if it were an ordinary Maxine VM object, but which is
* <ul>
* <li>only known to the GC implementation;</li>
* <li>is never given ordinary object behavior; and</li>
* <li>is never reachable from any object roots.</li>
* </ul>
* </p>
* <p>
* A {@link RemoteReference} represents the <em>identity</em> of an object (whether legitimate or quasi-) in the VM.
* Note that this perspective differs somewhat from that of the GC implementation.
* </p>
* <p>
* General map invariants:
* <ul>
* <li>The <em>To-Space Map</em> only holds references that are {@linkplain ObjectStatus#LIVE LIVE}.</li>
* <li>The <em>From-Space Map</em> only holds references that are {@linkplain ObjectStatus#LIVE LIVE} or
* {@linkplain ObjectStatus#FORWARDER FORWARD}.</li>
* <li>The maps never contain {@linkplain ObjectStatus#DEAD DEAD} references.</li>
* <li>The maps always refer only to locations in disjoint regions of memory, so memory location never appears
* simultaneously in both maps, which is to say there can only be one reference (and one kind of object) at a heap
* location.</li>
* <li>There are situations where a reference moves from one map to the other.</li>
* </ul>
* </p>
* <p>
* The following description enumerates more specifically what remote inspection can observe during each
* {@link HeapPhase}. The <em>canonical</em> relationship between references and VM objects allows the distinction
* between the two to be blurred for brevity in this description.
* </p>
* <p>
* <b>{@link HeapPhase#MUTATING} Summary</b>
* <p>
* During this phase new objects are allocated linearly in To-Space; From-Space contains nothing.
* </p>
* <ul>
* <li><b>To-Space Map:</b>
* <ol>
* <li>The To-Space Map contains only {@linkplain ObjectStatus#LIVE LIVE} object references.</li>
* <li>New {@linkplain ObjectStatus#LIVE LIVE} objects may be discovered and added to the To-Space Map.</li>
* <li>No other changes to the To-Space Map take place during the {@linkplain HeapPhase#MUTATING mutating} phase.</li>
* </ol>
* </li>
* <li><b>From-Space Map:</b>
* <ol>
* <li>The From-Space Map is empty.</li>
* <li>No objects appear in From-Space during the {@linkplain HeapPhase#MUTATING mutating} phase.</li>
* <li>When build in DEBUG mode, the GC {@linkplain Memory#ZAPPED_MARKER zaps} the entire From-Space.</li>
* </ol>
* </li>
* </ul>
* <p>
* <b>{@link HeapPhase#ANALYZING} Summary</b>
* <p>
* This phase begins by swapping the two spaces, so that From-Space contains all allocated objects, reachable or not.
* The GC traces reachable objects, and as as they are discovered they are <em>copied</em> and stored by linear
* allocation into To-Space. A <em>forwarding pointer</em> is stored into the old copy so that all references to the
* object can eventually be redirected to the new copy by the end of the phase, at which time the contents of From-Space
* are forgotten (and zapped if in DEBUG mode).
* <ul>
* <li><b>To-Space Map:</b>
* <ol>
* <li>The To-Space Map is empty at the beginning of the phase.</li>
* <li>A previously unseen {@linkplain ObjectStatus#LIVE LIVE} object may be discovered in To-Space and added
* to the To-Space Map. Such an object is known to be the new copy of an object in From-Space, but in this event
* the location of the old copy (now a <em>Forwarder</em> quasi-object) is unknown.</li>
* <li>A previously unseen {@linkplain ObjectStatus#FORWARDER FORWARDER} might be discovered in From-Space that is
* the old copy of a previously seen {@linkplain ObjectStatus#LIVE LIVE} object in the To-Space map. In this event
* the location of the old copy (a <em>Forwarder</em> quasi-object) is recorded in the To-Space reference.</li>
* <li>A previously unseen {@linkplain ObjectStatus#FORWARDER FORWARDER} might be discovered in From-Space that is
* the old copy of a previously unseen {@linkplain ObjectStatus#LIVE LIVE} object in To-Space. In this event a new
* {@linkplain ObjectStatus#LIVE LIVE} reference is added to the To-Space map, in which the location of its old copy
* is recorded.</li>
* <li>No other changes to the To-Space Map take place during the {@linkplain HeapPhase#ANALYZING analyzing} phase.</li>
* </ol>
* </li>
* <li><b>From-Space Map:</b>
* <ol>
* <li>The From-Space Map at the beginning of the phase contains precisely the contents of the To-Space map just before
* the beginning of the phase, at which point all contained references are {@linkplain ObjectStatus#LIVE LIVE}. This
* reflects the assumption that all objects are live until proven otherwise, which can only be done at the end of the phase.</li>
* <li>A previously unseen {@linkplain ObjectStatus#LIVE LIVE} object may be discovered in From-Space and added
* to the From-Space Map.
* <li>A previously unseen {@linkplain ObjectStatus#FORWARDER FORWARDER} might be discovered in From-Space and added
* to the From-Space Map. If the new copy of forwarded object has already been seen (and thus is in the To-Space Map),
* then the location of this old copy is recorded in that reference; if the new copy has not been seen, then a new
* {@linkplain ObjectStatus#LIVE LIVE} reference is created, the location of the old copy is recorded, and it is added
* to the To-Space Map.</li>
* <li>A previously unseen {@linkplain ObjectStatus#LIVE LIVE} object in From-Space may be discovered to have been forwarded.
* In this event, the reference is removed from the From-Space Map, assigned its new memory location, the location of this old
* copy recorded in it, and the reference is added to the To-Space Map. A <em>new</em> quasi-object
* {@linkplain ObjectStatus#FORWARDER FORWARDER} is then created and added back into the From-Space Map.
* <li>No other changes to the From-Space Map take place during the {@linkplain HeapPhase#ANALYZING analyzing} phase.</li>
* </ol>
* </li>
* </ul>
* <p>
* <b>{@link HeapPhase#RECLAIMING} Summary</b>
* <p>
* At the beginning of this phase all reachable objects have been copied into To-Space and all references to them
* revised to the new location. The contents of From-Space (unreachable objects and forwarders) are forgotten. In
* DEBUG mode, the GC {@linkplain Memory#ZAPPED_MARKER zaps} the entire From-Space. This phase is extremely brief.</p>
* <ul>
* <li><b>To-Space Map:</b>
* <ol>
* <li>The To-Space Map contains only {@linkplain ObjectStatus#LIVE LIVE} object references.</li>
* <li>No changes to the To-Space Map take place during the {@linkplain HeapPhase#RECLAIMING reclaiming} phase.</li>
* </ol>
* </li>
* <li><b>From-Space Map:</b>
* <ol>
* <li>At the beginning of the phase, every entry in the From-Space map is removed and made {@linkplain ObjectStatus#DEAD DEAD}.
* This includes {@linkplain ObjectStatus#LIVE LIVE} references, which are now known to be unreachable, and
* {@linkplain ObjectStatus#FORWARDER FORWARDER} quasi-objects, which play no further role.</li>
* <li>The From-Space Map remains empty during the (brief) remainder of the {@linkplain HeapPhase#RECLAIMING reclaiming} phase.</li>
* </ol>
* </li>
* </ul>
* @see SemiSpaceHeapScheme
* @see Memory#ZAPPED_MARKER
* @see SemiSpaceRemoteReference
*/
public class RemoteSemiSpaceHeapScheme extends AbstractRemoteHeapScheme implements RemoteObjectReferenceManager, VmRelocatingHeap {
private static final int TRACE_VALUE = 1;
private final TimedTrace heapUpdateTracer;
private long lastUpdateEpoch = -1L;
private long lastAnalyzingPhaseCount = 0L;
private long lastReclaimingPhaseCount = 0L;
/**
* The VM object that implements the {@link HeapScheme} in the current configuration.
*/
private TeleSemiSpaceHeapScheme scheme;
/**
* The VM object that describes the location of the collector's To-Space; the location changes when the spaces get swapped.
*/
private TeleLinearAllocationMemoryRegion toSpaceMemoryRegion = null;
/**
* Map: VM address in To-Space --> a {@link SemiSpaceRemoteReference} that refers to the object whose origin is at that location.
* There may be only live references in the map.
*/
private WeakRemoteReferenceMap<SemiSpaceRemoteReference> toSpaceRefMap = new WeakRemoteReferenceMap<SemiSpaceRemoteReference>();
/**
* The VM object that describes the location of the collector's From-Space; the location changes when the spaces get swapped.
*/
private TeleLinearAllocationMemoryRegion fromSpaceMemoryRegion = null;
/**
* Map: VM address in From-Space --> a {@link SemiSpaceRemoteReference} that refers to the object whose origin is at that location.
* There may be live or quasi references in the map, but never dead.
*/
private WeakRemoteReferenceMap<SemiSpaceRemoteReference> fromSpaceRefMap = new WeakRemoteReferenceMap<SemiSpaceRemoteReference>();
private long collected = 0;
private long forwarded = 0;
private final ReferenceUpdateTracer referenceUpdateTracer;
private final List<VmHeapRegion> heapRegions = new ArrayList<VmHeapRegion>(2);
protected RemoteSemiSpaceHeapScheme(TeleVM vm) {
super(vm);
this.heapUpdateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + "updating");
this.referenceUpdateTracer = new ReferenceUpdateTracer();
final VmAddressSpace addressSpace = vm().addressSpace();
// There might already be dynamically allocated regions in a dumped image or when attaching to a running VM
// TODO (mlvdv) Update; won't work now; important for attach mode
for (MaxMemoryRegion dynamicHeapRegion : getDynamicHeapRegionsUnsafe()) {
final VmHeapRegion fakeDynamicHeapRegion =
new VmHeapRegion(vm, dynamicHeapRegion.regionName(), dynamicHeapRegion.start(), dynamicHeapRegion.nBytes());
heapRegions.add(fakeDynamicHeapRegion);
addressSpace.add(fakeDynamicHeapRegion.memoryRegion());
}
}
public Class heapSchemeClass() {
return SemiSpaceHeapScheme.class;
}
@Override
public List<MaxObject> inspectableObjects() {
List<MaxObject> inspectableObjects = new ArrayList<MaxObject>();
inspectableObjects.addAll(super.inspectableObjects());
if (fromSpaceMemoryRegion != null) {
inspectableObjects.add(fromSpaceMemoryRegion);
}
if (toSpaceMemoryRegion != null) {
inspectableObjects.add(toSpaceMemoryRegion);
}
return inspectableObjects;
}
public void initialize(long epoch) {
vm().addInitializationListener(new InitializationListener() {
public void initialiationComplete(final long initializationEpoch) {
objects().registerTeleObjectType(SemiSpaceHeapScheme.class, TeleSemiSpaceHeapScheme.class);
// Get the VM object that represents the heap implementation; can't do this any sooner during startup.
scheme = (TeleSemiSpaceHeapScheme) teleHeapScheme();
assert scheme != null;
updateMemoryStatus(initializationEpoch);
/*
* Add a heap phase listener that will force the VM to stop any time the heap transitions from
* analysis to the reclaiming phase of a GC. This is exactly the moment in a GC cycle when reference
* information must be updated. Unfortunately, the handler supplied with this listener will only be
* called after the VM state refresh cycle is complete. That would be too late since so many other parts
* of the refresh cycle depend on references. Consequently, the needed updates take place when this
* manager gets refreshed (early in the refresh cycle), not when this handler eventually gets called.
*/
try {
vm().addGCPhaseListener(RECLAIMING, new MaxGCPhaseListener() {
public void gcPhaseChange(HeapPhase phase) {
// Dummy handler; the actual updates must be done early during the refresh cycle.
final long phaseChangeEpoch = vm().teleProcess().epoch();
Trace.line(TRACE_VALUE, tracePrefix() + " VM stopped for reference updates, epoch=" + phaseChangeEpoch + ", gc cycle=" + gcStartedCount());
Trace.line(TRACE_VALUE, tracePrefix() + " Note: updates have long since been done by the time this (dummy) handler is called");
}
});
} catch (MaxVMBusyException e) {
TeleError.unexpected(tracePrefix() + "Unable to add GC Phase Listener");
}
}
});
}
public List<VmHeapRegion> heapRegions() {
return heapRegions;
}
/**
* {@inheritDoc}
* <p>
* It is unnecessary to check for heap regions that disappear for this collector.
* Once allocated, the two regions never change, but we force a refresh on the
* memory region descriptions, since their locations change when swapped.
* <p>
* This gets called more than once during the startup sequence.
*/
@Override
public void updateMemoryStatus(long epoch) {
super.updateMemoryStatus(epoch);
// Can't do anything until we have the VM object that represents the scheme implementation
if (scheme == null) {
return;
}
if (toSpaceMemoryRegion == null || fromSpaceMemoryRegion == null) {
Trace.begin(TRACE_VALUE, tracePrefix() + "looking for heap regions");
/*
* The two heap regions have not yet been discovered. Don't check the epoch, since this check may
* need to be run more than once during the startup sequence, as the information needed for access to
* the information is incrementally established. Information about the two heap regions, other then
* their locations (subject to change when swapped) need not be checked once established.
*/
if (toSpaceMemoryRegion == null) {
toSpaceMemoryRegion = scheme.readTeleToRegion();
if (toSpaceMemoryRegion != null) {
final VmHeapRegion toVmHeapRegion = new VmHeapRegion(vm(), toSpaceMemoryRegion, this);
heapRegions.add(toVmHeapRegion);
vm().addressSpace().add(toVmHeapRegion.memoryRegion());
}
}
if (fromSpaceMemoryRegion == null) {
fromSpaceMemoryRegion = scheme.readTeleFromRegion();
if (fromSpaceMemoryRegion != null) {
final VmHeapRegion fromVmHeapRegion = new VmHeapRegion(vm(), fromSpaceMemoryRegion, this);
heapRegions.add(fromVmHeapRegion);
vm().addressSpace().add(fromVmHeapRegion.memoryRegion());
}
}
Trace.end(TRACE_VALUE, tracePrefix() + "looking for heap regions, " + heapRegions.size() + " found");
if (heapRegions.size() < 2) {
// don't bother with the rest.
return;
}
}
if (epoch > lastUpdateEpoch) {
heapUpdateTracer.begin();
/*
* This is a normal refresh. Immediately update information about the location of the heap regions; this
* update must be forced because remote objects are otherwise not refreshed until later in the update
* cycle.
*/
toSpaceMemoryRegion.updateCache(epoch);
fromSpaceMemoryRegion.updateCache(epoch);
/*
* For this collector, we only need an overall review of reference state when we're actually collecting.
*/
if (phase().isCollecting()) {
// Has a GC cycle has started since the last time we looked?
if (lastAnalyzingPhaseCount < gcStartedCount()) {
/*
* A GC cycle has started since the last time we checked. That means that the spaces have been swapped by the GC
* and we must account for that first, before examining what else has happened since the swap.
*/
Trace.begin(TRACE_VALUE, tracePrefix() + "first halt in GC cycle=" + gcStartedCount() + ", swapping semispace heap regions");
assert lastAnalyzingPhaseCount == gcStartedCount() - 1;
assert fromSpaceRefMap.isEmpty();
// Swap the maps to reflect the swapped locations of the two regions in the heap
final WeakRemoteReferenceMap<SemiSpaceRemoteReference> tempRefMap = toSpaceRefMap;
toSpaceRefMap = fromSpaceRefMap;
fromSpaceRefMap = tempRefMap;
// Transition the state of all references that are now in From-Space
final List<SemiSpaceRemoteReference> refs = fromSpaceRefMap.values();
for (SemiSpaceRemoteReference fromSpaceRef : refs) {
// A former To-Space reference that is now in From-Space: transition state
fromSpaceRef.beginAnalyzing();
}
Trace.end(TRACE_VALUE, tracePrefix() + "first halt in GC cycle=" + gcStartedCount() + ", total refs=" + refs.size());
lastAnalyzingPhaseCount = gcStartedCount();
}
/*
* Check now to see if any From-Space references have been forwarded since we last looked. This can happen
* at any time during the analyzing phase, but we have to check one more time when we hit the reclaiming
* phase.
*/
if (lastReclaimingPhaseCount < gcStartedCount()) {
// The transition to reclaiming hasn't yet been processed, so check for any newly forwarded references.
Trace.begin(TRACE_VALUE, tracePrefix() + "checking From-Space refs, GC cycle=" + gcStartedCount());
int newForwarded = 0;
int oldForwarded = 0;
for (SemiSpaceRemoteReference fromSpaceRef : fromSpaceRefMap.values()) {
switch (fromSpaceRef.status()) {
case LIVE:
final Address fromSpaceOrigin = fromSpaceRef.origin();
if (objects().hasForwardingAddressUnsafe(fromSpaceOrigin)) {
// A known origin in From-Space that has been forwarded since the last time we checked.
// The object is now live in To-Space; transition the existing reference to reflect this.
// Remove the reference from the From-Space map.
fromSpaceRefMap.remove(fromSpaceOrigin);
// Get new address in To-Space
final Address toSpaceOrigin = objects().getForwardingAddressUnsafe(fromSpaceOrigin);
// Relocate the existing reference and add it to the To-Space Map
fromSpaceRef.discoverForwarded(toSpaceOrigin);
toSpaceRefMap.put(toSpaceOrigin, fromSpaceRef);
// Create a forwarder quasi reference to retain information about the old location for the duration of the analysis phase
final SemiSpaceRemoteReference forwarderReference = SemiSpaceRemoteReference.createForwarder(this, fromSpaceOrigin, toSpaceOrigin);
fromSpaceRefMap.put(fromSpaceOrigin, forwarderReference);
newForwarded++;
}
break;
case FORWARDER:
// Do nothing; already forwarded and a separate reference is in the To-Space map
oldForwarded++;
break;
case DEAD:
TeleError.unexpected(tracePrefix() + "DEAD reference found in From-Space map");
break;
default:
TeleError.unknownCase();
}
}
Trace.end(TRACE_VALUE, tracePrefix() + "checking From-Space refs, GC cycle=" + gcStartedCount()
+ " forwarded=" + (oldForwarded + newForwarded) + "(old=" + oldForwarded + ", new=" + newForwarded + ")");
}
if (phase().isReclaiming() && lastReclaimingPhaseCount < gcStartedCount()) {
/*
* The heap is in a GC cycle, and this is the first VM halt during that GC cycle where we know
* analysis is complete. This halt will usually be caused by the special breakpoint we've set at
* entry to the {@linkplain #RECLAIMING} phase.
*/
Trace.begin(TRACE_VALUE, tracePrefix() + "first halt in GC RECLAIMING, cycle=" + gcStartedCount() + "; clearing From-Space references");
assert lastReclaimingPhaseCount == gcStartedCount() - 1;
lastReclaimingPhaseCount = gcStartedCount();
for (SemiSpaceRemoteReference toSpaceRef : toSpaceRefMap.values()) {
switch (toSpaceRef.status()) {
case LIVE:
toSpaceRef.endAnalyzing();
break;
default:
TeleError.unexpected(tracePrefix() + toSpaceRef.status().name() + " reference found in To-Space map");
break;
}
}
int objectsDied = 0;
int forwardersDied = 0;
for (SemiSpaceRemoteReference fromSpaceRef : fromSpaceRefMap.values()) {
switch (fromSpaceRef.status()) {
case LIVE:
fromSpaceRef.endAnalyzing();
objectsDied++;
break;
case FORWARDER:
fromSpaceRef.endAnalyzing();
forwardersDied++;
break;
default:
TeleError.unexpected(tracePrefix() + fromSpaceRef.status().name() + " reference found in From-Space map");
break;
}
}
fromSpaceRefMap.clear();
Trace.end(TRACE_VALUE, tracePrefix() + "first halt in GC RECLAIMING, cycle=" + gcStartedCount() + ", died=(objects=" + objectsDied + ", fowarders=" + forwardersDied + ")");
}
}
lastUpdateEpoch = epoch;
heapUpdateTracer.end(heapUpdateStatsPrinter);
}
}
public MaxMemoryManagementInfo getMemoryManagementInfo(final Address address) {
return new MaxMemoryManagementInfo() {
public MaxMemoryManagementStatus status() {
final MaxHeapRegion heapRegion = heap().findHeapRegion(address);
final HeapPhase phase = heap().phase();
if (heapRegion == null) {
// The location is not in any memory region allocated by the heap.
return MaxMemoryManagementStatus.NONE;
}
if (heapRegion.entityName().equals(SemiSpaceHeapScheme.FROM_REGION_NAME)) {
// From-Space is generally DEAD, unless we're collecting, in which case we treat
// the allocated portion of From-Space as LIVE for the time being.
if (phase.isCollecting() && fromSpaceMemoryRegion.containsInAllocated(address)) {
return MaxMemoryManagementStatus.LIVE;
}
return MaxMemoryManagementStatus.DEAD;
}
if (heapRegion.entityName().equals(SemiSpaceHeapScheme.TO_REGION_NAME)) {
if (phase.isCollecting()) {
// While collecting treat the whole allocated part of To-Space as LIVE
if (toSpaceMemoryRegion.containsInAllocated(address)) {
return MaxMemoryManagementStatus.LIVE;
}
return MaxMemoryManagementStatus.FREE;
}
// phase is MUTATING; look more closely at the allocations in each TLAB
if (!toSpaceMemoryRegion.containsInAllocated(address)) {
// everything in to-space after the global allocation mark is dead
return MaxMemoryManagementStatus.FREE;
}
// TODO (mlvdv) tighten up this loop to return LIVE when it finds the TLAB
// containing the address and it is before the mark. I'm not sure of the exact math.
for (TeleNativeThread teleNativeThread : vm().teleProcess().threads()) { // iterate over threads in check in case of tlabs if objects are dead or live
TeleThreadLocalsArea teleThreadLocalsArea = teleNativeThread.localsBlock().tlaFor(SafepointPoll.State.ENABLED);
if (teleThreadLocalsArea != null) {
Word tlabDisabledWord = teleThreadLocalsArea.getWord(HeapSchemeWithTLAB.TLAB_DISABLED_THREAD_LOCAL_NAME);
Word tlabMarkWord = teleThreadLocalsArea.getWord(HeapSchemeWithTLAB.TLAB_MARK_THREAD_LOCAL_NAME);
Word tlabTopWord = teleThreadLocalsArea.getWord(HeapSchemeWithTLAB.TLAB_TOP_THREAD_LOCAL_NAME);
if (tlabDisabledWord.isNotZero() && tlabMarkWord.isNotZero() && tlabTopWord.isNotZero()) {
if (address.greaterEqual(tlabMarkWord.asAddress()) && tlabTopWord.asAddress().greaterThan(address)) {
return MaxMemoryManagementStatus.FREE;
}
}
}
}
// Everything else should be live.
return MaxMemoryManagementStatus.LIVE;
}
// Some other heap region such as boot or immortal; use the simple test
if (heapRegion.memoryRegion().containsInAllocated(address)) {
return MaxMemoryManagementStatus.LIVE;
}
return MaxMemoryManagementStatus.FREE;
}
public String terseInfo() {
// Provide text to be displayed in display cell
return "";
}
public String shortDescription() {
// More information could be added here
return vm().heapScheme().name();
}
public Address address() {
return address;
}
public TeleObject tele() {
return null;
}
};
}
/**
* Finds an existing heap region, if any, that has been created using the
* remote object describing it.
*/
private VmHeapRegion find(TeleMemoryRegion runtimeMemoryRegion) {
for (VmHeapRegion heapRegion : heapRegions) {
if (runtimeMemoryRegion == heapRegion.representation()) {
return heapRegion;
}
}
return null;
}
public ObjectStatus objectStatusAt(Address origin) throws TeleError {
TeleError.check(contains(origin), "Location is outside semispace heap regions");
switch(phase()) {
case MUTATING:
case RECLAIMING:
if (toSpaceMemoryRegion.containsInAllocated(origin)) {
final SemiSpaceRemoteReference knownToSpaceReference = toSpaceRefMap.get(origin);
if (knownToSpaceReference != null) {
return knownToSpaceReference.status();
}
if (objects().isPlausibleOriginUnsafe(origin)) {
return ObjectStatus.LIVE;
}
}
break;
case ANALYZING:
if (toSpaceMemoryRegion.containsInAllocated(origin)) {
final SemiSpaceRemoteReference knownToSpaceReference = toSpaceRefMap.get(origin);
if (knownToSpaceReference != null) {
return knownToSpaceReference.status();
}
if (objects().isPlausibleOriginUnsafe(origin)) {
return ObjectStatus.LIVE;
}
} else if (fromSpaceMemoryRegion.containsInAllocated(origin)) {
final SemiSpaceRemoteReference knownFromSpaceReference = fromSpaceRefMap.get(origin);
if (knownFromSpaceReference != null) {
return knownFromSpaceReference.status();
}
if (objects().isPlausibleOriginUnsafe(origin)) {
// An object in From-Space that hasn't been forwarded
return ObjectStatus.LIVE;
}
if (objects().hasForwardingAddressUnsafe(origin)) {
final Address forwardAddress = objects().getForwardingAddressUnsafe(origin);
if (toSpaceMemoryRegion.containsInAllocated(forwardAddress) && objectStatusAt(forwardAddress).isLive()) {
return ObjectStatus.FORWARDER;
}
}
}
break;
default:
TeleError.unknownCase();
}
return ObjectStatus.DEAD;
}
public boolean isForwardingAddress(Address forwardingAddress) {
if (phase() == HeapPhase.ANALYZING && toSpaceMemoryRegion.contains(forwardingAddress)) {
final Address possibleOrigin = objects().forwardingPointerToOriginUnsafe(forwardingAddress);
if (possibleOrigin.isNotZero() && objectStatusAt(possibleOrigin).isLive()) {
return true;
}
}
return false;
}
public RemoteReference makeReference(Address origin) throws TeleError {
assert vm().lockHeldByCurrentThread();
TeleError.check(contains(origin), "Location is outside of " + heapSchemeClass().getSimpleName() + " heap");
final RemoteReference reference = internalMakeRef(origin);
if (reference == null) {
return null;
}
if (reference.status().isLive()) {
return reference;
}
return reference.status().isForwarder() && phase() == HeapPhase.ANALYZING ? reference : null;
}
public RemoteReference makeQuasiReference(Address origin) throws TeleError {
assert vm().lockHeldByCurrentThread();
TeleError.check(contains(origin), "Location is outside of " + heapSchemeClass().getSimpleName() + " heap");
final RemoteReference reference = internalMakeRef(origin);
return reference != null && reference.status().isQuasi() ? reference : null;
}
/**
* Creates a reference of the appropriate kind if there is an object or <em>quasi</em>
* object at the specified origin in VM memory.
* <p>
* Using this shared internal method has advantage that the logic is all in one place.
* The disadvantage is that references will be created unnecessarily. That effect should
* be small, though. The most common use case is to look for a live object, and the number
* of live objects dominates the number of <em>quasi</em> objects. Looking for <em>quasi</em>
* objects only will probably succeed most of the time anyway, because callers are likely to
* have checked the object's status before making the reference.
*/
private RemoteReference internalMakeRef(Address origin) {
SemiSpaceRemoteReference remoteReference = null;
switch(phase()) {
case MUTATING:
case RECLAIMING:
/*
* In this phase there are only live objects in To-Space. There are no quasi objects.
*/
remoteReference = toSpaceRefMap.get(origin);
if (remoteReference != null) {
// An object origin in To-Space already seen.
TeleError.check(remoteReference.status().isLive());
} else if (toSpaceMemoryRegion.containsInAllocated(origin) && objects().isPlausibleOriginUnsafe(origin)) {
// An object origin in To-Space not yet seen.
remoteReference = SemiSpaceRemoteReference.createLive(this, origin);
toSpaceRefMap.put(origin, remoteReference);
}
break;
case ANALYZING:
/*
* In this heap phase, there can be objects and references in both maps: live objects in the To-Space map,
* and live objects (not yet forwarded) or quasi objects (forwarders) in the From-Space map.
*/
if (toSpaceMemoryRegion.containsInAllocated(origin)) {
// A location in the allocated area of To-Space
final Address toSpaceOrigin = origin;
remoteReference = toSpaceRefMap.get(toSpaceOrigin);
if (remoteReference != null) {
// A known origin in To-Space
TeleError.check(remoteReference.status().isLive());
} else if (objects().isPlausibleOriginUnsafe(toSpaceOrigin)) {
/*
* An object origin in To-Space not yet seen.
* This must be the new copy of a forwarded object, but we don't know the location of its the old
* copy, which is now a forwarder. If we had previously seen the forwarder, then we would also have
* seen this new object as well, in which case a reference for it would already be in the To-Space Map.
*/
remoteReference = SemiSpaceRemoteReference.createInToOnly(this, toSpaceOrigin);
toSpaceRefMap.put(toSpaceOrigin, remoteReference);
}
} else if (fromSpaceMemoryRegion.containsInAllocated(origin)) {
// A location in the allocated area of From-Space
final Address fromSpaceOrigin = origin;
remoteReference = fromSpaceRefMap.get(fromSpaceOrigin);
if (remoteReference != null) {
// A known object origin in From-Space
if (!remoteReference.status().isForwarder() && objects().hasForwardingAddressUnsafe(fromSpaceOrigin)) {
// LD: shouldn't we move the ref to the to-space map and create a forwarder if the reference has a forwarding address ?
// Added this check in case it happens.
TeleError.unexpected("Must not happen");
}
TeleError.check(remoteReference.status().isLive() || remoteReference.status().isForwarder());
} else if (objects().isPlausibleOriginUnsafe(fromSpaceOrigin)) {
// An origin in From-Space not yet seen and not forwarded.
// This will be treated a live reference in From-Space for the time being.
remoteReference = SemiSpaceRemoteReference.createInFromOnly(this, fromSpaceOrigin);
fromSpaceRefMap.put(fromSpaceOrigin, remoteReference);
} else if (objects().hasForwardingAddressUnsafe(fromSpaceOrigin)) {
// A possible forwarder origin in From-Space not yet seen.
// Check to see if the forwarder really points to an object in To-Space
final Address toSpaceOrigin = objects().getForwardingAddressUnsafe(fromSpaceOrigin);
SemiSpaceRemoteReference toSpaceReference = toSpaceRefMap.get(toSpaceOrigin);
if (toSpaceReference != null) {
if (toSpaceReference.forwardedFrom().isZero()) {
// A forwarder in From-Space, not yet seen, whose new copy is already known
// Update the existing To-Space reference with newly discovered location of its old copy
toSpaceReference.discoverOldOrigin(fromSpaceOrigin);
} else {
// This may occur when the forwarder ref previously stored in the from map was collected by the GC, leaving
// only the weak ref with no referent.
TeleError.check(toSpaceReference.forwardedFrom().equals(fromSpaceOrigin));
}
// Create a forwarder quasi reference to retain information about the old location for the duration of the analysis phase
remoteReference = SemiSpaceRemoteReference.createForwarder(this, fromSpaceOrigin, toSpaceOrigin);
fromSpaceRefMap.put(fromSpaceOrigin, remoteReference);
} else if (toSpaceMemoryRegion.contains(toSpaceOrigin) && objectStatusAt(toSpaceOrigin).isLive()) {
// A forwarder in From-Space, not yet seen, whose new copy in To-Space has not yet been seen
// Add a reference for the new copy to the To-Space map
final SemiSpaceRemoteReference newCopyReference = SemiSpaceRemoteReference.createInFromTo(this, fromSpaceOrigin, toSpaceOrigin);
toSpaceRefMap.put(toSpaceOrigin, newCopyReference);
// Create a forwarder quasi reference and add to the From-Space map
remoteReference = SemiSpaceRemoteReference.createForwarder(this, fromSpaceOrigin, toSpaceOrigin);
fromSpaceRefMap.put(fromSpaceOrigin, remoteReference);
}
}
}
break;
default:
TeleError.unknownCase();
}
return remoteReference == null ? vm().referenceManager().zeroReference() : remoteReference;
}
/**
* Do either of the heap regions contain the address anywhere in their extents (ignoring the allocation marker)?
*/
private boolean contains(Address address) {
return (toSpaceMemoryRegion != null && toSpaceMemoryRegion.contains(address))
|| (fromSpaceMemoryRegion != null && fromSpaceMemoryRegion.contains(address));
}
/**
* Is the address in an area where an object could be
* {@linkplain ObjectStatus#LIVE LIVE}.
*/
private boolean inLiveArea(Address address) {
switch(phase()) {
case MUTATING:
case RECLAIMING:
return toSpaceMemoryRegion.containsInAllocated(address);
case ANALYZING:
return fromSpaceMemoryRegion.containsInAllocated(address) || toSpaceMemoryRegion.containsInAllocated(address);
default:
TeleError.unknownCase();
}
return false;
}
private void printRegionObjectStats(PrintStream printStream, int indent, boolean verbose, TeleLinearAllocationMemoryRegion region, WeakRemoteReferenceMap<SemiSpaceRemoteReference> map) {
final NumberFormat formatter = NumberFormat.getInstance();
int totalRefs = 0;
int liveRefs = 0;
int deadRefs = 0;
for (SemiSpaceRemoteReference ref : map.values()) {
switch(ref.status()) {
case LIVE:
liveRefs++;
break;
case DEAD:
deadRefs++;
break;
}
}
totalRefs = liveRefs + deadRefs;
// Line 0
String indentation = Strings.times(' ', indent);
final StringBuilder sb0 = new StringBuilder();
sb0.append("heap region: ");
sb0.append(find(region).entityName());
if (verbose) {
sb0.append(", ref. mgr=").append(getClass().getSimpleName());
}
printStream.println(indentation + sb0.toString());
// increase indentation
indentation += Strings.times(' ', 4);
// Line 1
final StringBuilder sb1 = new StringBuilder();
sb1.append("memory: ");
final MemoryUsage usage = region.getUsage();
final long size = usage.getCommitted();
if (size > 0) {
sb1.append("size=" + formatter.format(size));
final long used = usage.getUsed();
sb1.append(", used=" + formatter.format(used) + " (" + (Long.toString(100 * used / size)) + "%)");
} else {
sb1.append(" <unallocated>");
}
printStream.println(indentation + sb1.toString());
// Line 2, indented
final StringBuilder sb2 = new StringBuilder();
sb2.append("mapped object refs=").append(formatter.format(totalRefs));
if (totalRefs > 0) {
sb2.append(", object status: ");
sb2.append(ObjectStatus.LIVE.label()).append("=").append(formatter.format(liveRefs)).append(", ");
}
printStream.println(indentation + sb2.toString());
if (deadRefs > 0) {
printStream.println(indentation + "ERROR: " + formatter.format(deadRefs) + " DEAD refs in map");
}
// Line 3, optional
if (verbose && totalRefs > 0) {
printStream.println(indentation + "Mapped object ref states:");
final String stateIndentation = indentation + Strings.times(' ', 4);
for (RefStateCount refStateCount : SemiSpaceRemoteReference.getStateCounts(map.values())) {
if (refStateCount.count > 0) {
printStream.println(stateIndentation + refStateCount.stateName + ": " + formatter.format(refStateCount.count));
}
}
}
}
@Override
public void printObjectSessionStats(PrintStream printStream, int indent, boolean verbose) {
if (!heapRegions.isEmpty()) {
int toSpaceRefs = toSpaceRefMap.values().size();
int fromSpaceRefs = fromSpaceRefMap.values().size();
printObjectSessionStatsHeader(printStream, indent, verbose, toSpaceRefs + fromSpaceRefs);
printRegionObjectStats(printStream, indent + 4, verbose, toSpaceMemoryRegion, toSpaceRefMap);
printRegionObjectStats(printStream, indent + 4, verbose, fromSpaceMemoryRegion, fromSpaceRefMap);
}
}
/**
* Surrogate object for the scheme instance in the VM.
*/
public static class TeleSemiSpaceHeapScheme extends TeleHeapScheme {
public TeleSemiSpaceHeapScheme(TeleVM vm, RemoteReference reference) {
super(vm, reference);
}
/**
* @return surrogate for the semispace collector's "from" region
*/
public TeleLinearAllocationMemoryRegion readTeleFromRegion() {
final RemoteReference fromReference = fields().SemiSpaceHeapScheme_fromSpace.readRemoteReference(reference());
return (TeleLinearAllocationMemoryRegion) objects().makeTeleObject(fromReference);
}
/**
* @return surrogate for the semispace collector's "to" region
*/
public TeleLinearAllocationMemoryRegion readTeleToRegion() {
final RemoteReference toReference = fields().SemiSpaceHeapScheme_toSpace.readRemoteReference(reference());
return (TeleLinearAllocationMemoryRegion) objects().makeTeleObject(toReference);
}
}
/**
* Delayed evaluation of a trace message.
*/
private class ReferenceUpdateTracer {
final NumberFormat formatter = NumberFormat.getInstance();
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("Phase=" + phase().label());
sb.append(": forwarded=").append(formatter.format(forwarded));
sb.append(", collected=").append(formatter.format(collected));
return sb.toString();
}
}
}