/* * 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.memory; import java.io.*; import java.util.*; import com.sun.max.*; import com.sun.max.lang.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.object.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; /** * Maintains a map of memory allocated in the VM's address space. It is updated * incrementally during the refresh cycle. * <p> * Managers for other entities in the VM are expected to register/unregister * top level memory allocations when they are identified during the refresh cycle. */ public class VmAddressSpace extends AbstractVmHolder implements MaxAddressSpace { private static final int TRACE_VALUE = 1; private static final String ADDRESS_SPACE_REGION_NAME = "Address Space"; private static VmAddressSpace vmAddressSpace; public static VmAddressSpace make(TeleVM vm) { if (vmAddressSpace == null) { vmAddressSpace = new VmAddressSpace(vm); } return vmAddressSpace; } private final MaxEntityMemoryRegion<MaxAddressSpace> addressSpaceMemoryRegion; private volatile SortedMemoryRegionSet<MaxEntityMemoryRegion<? extends MaxEntity>> map = new SortedMemoryRegionSet<MaxEntityMemoryRegion<? extends MaxEntity>>(); private VmAddressSpace(TeleVM vm) { super(vm); addressSpaceMemoryRegion = new VmAddressSpaceMemoryRegion(vm); } public String entityName() { return "Address Space"; } public String entityDescription() { return "A map of the memory regions allocated in the VM's address space"; } public MaxEntityMemoryRegion<MaxAddressSpace> memoryRegion() { return addressSpaceMemoryRegion; } /** * {@inheritDoc} * <p> * Does any of the top level memory allocations contain the address? */ public boolean contains(Address address) { return find(address) != null; } public TeleObject representation() { // TODO (mlvdv) is there any equivalent implementation object in the VM? Will there be? return null; } /** * All known memory allocations, both those allocated by the VM and * those discovered from the OS, for example thread allocation areas and loaded * native libraries. * <p> * This collection is updated incrementally during the VM refresh cycle. This means * that it may be inconsistent (unlike the list of regions recorded in the current * {@link MaxVMState}, which is not assigned until the conclusion of the refresh cycle. * However, it always reflects the most current information available, which is necessary * to have available <em>within</em> the refresh cycle. * <p> * Should never contain {@code null} * * @return an unmodifiable list of the current allocations, sorted by start location */ public List<MaxEntityMemoryRegion<? extends MaxEntity> > allocations() { return map.regions(); } /** * Gets the top level allocated memory region, if any, that includes the * specified memory location in the VM. * <p> * This method relies on the collection of allocated regions that is * updated incrementally during the update cycle. It therefore has * the latest information, but may not be consistent. For example, during * the update cycle the VM's direct allocations may have been refreshed, but * the allocations corresponding to threads may have not yet been refreshed. */ public MaxEntityMemoryRegion<? extends MaxEntity> find(Address address) { return map.find(address); } /** * Registers a previously unknown top level memory allocation. * <p> * It is permitted for the location and size of an already registered memory region to change. * <p> * Registration of an <em>unallocated</em> (i.e. with {@code start=0}) is permitted. * An unallocated memory region will be treated as <em>empty</em> regardless of * the specified {@code size}, which should be 0. * <p> * Adding an already registered memory region (based on <em>object identity</em>, not location) has no effect. * <p> * Adding a registered region that overlaps an already existing region will succeed, but a * warning message will be generated the next time that the registered regions are * iterated. * <p> * Should the location and size of a registered memory region change in such a way that there * is an overlap between it an another registered memory region, a warning message will be * generated the next time that the registered regions are iterated. */ public void add(MaxEntityMemoryRegion<? extends MaxEntity> allocation) { assert allocation != null; final SortedMemoryRegionSet<MaxEntityMemoryRegion< ? extends MaxEntity>> mapCopy = map.copy(); mapCopy.add(allocation); map = mapCopy; } /** * De-registers a previously registered top level memory allocation, matched based * on <em>object identity</em>, not location. * * @param allocation a previously registered top level memory allocation * @return {@code true} if removed, {@code false} if not found */ public boolean remove(MaxEntityMemoryRegion<? extends MaxEntity> allocation) { assert allocation != null; final SortedMemoryRegionSet<MaxEntityMemoryRegion< ? extends MaxEntity>> mapCopy = map.copy(); final boolean result = mapCopy.remove(allocation); map = mapCopy; return result; } public void printSessionStats(PrintStream printStream, int indent, boolean verbose) { final String indentation = Strings.times(' ', indent); for (MaxEntityMemoryRegion<? extends MaxEntity> memoryRegion : this.map) { final StringBuilder line = new StringBuilder(); if (memoryRegion.isAllocated()) { line.append(memoryRegion.start().to0xHexString()); line.append(" - "); line.append((memoryRegion.end().minus(1)).to0xHexString()); } else { line.append("<Unallocated>"); } line.append(": " + memoryRegion.owner().entityName()); printStream.println(indentation + line.toString()); } } /** * Description of the address space occupied by the VM. * This region has no parent. * <p> * Children of this region are the top level extents of memory allocated from the OS, both * by the VM explicitly, and by native OS behavior: thread stacks, native libraries, etc. */ private final class VmAddressSpaceMemoryRegion extends TeleFixedMemoryRegion implements MaxEntityMemoryRegion<MaxAddressSpace> { protected VmAddressSpaceMemoryRegion(MaxVM vm) { super(vm, ADDRESS_SPACE_REGION_NAME, Address.fromInt(1), Long.MAX_VALUE); } public MaxEntityMemoryRegion< ? extends MaxEntity> parent() { // This is the root of the memory hierarchy return null; } public List<MaxEntityMemoryRegion< ? extends MaxEntity>> children() { final List<MaxEntityMemoryRegion< ? extends MaxEntity>> children = new ArrayList<MaxEntityMemoryRegion< ? extends MaxEntity>>(); for (MaxEntityMemoryRegion<? extends MaxEntity> region : map) { children.add(region); } return children; } public MaxAddressSpace owner() { return VmAddressSpace.this; } } /** * A set of memory regions sorted by location, with some special properties. * <ul> * <li>Adding a duplicate memory region (by object identity) will have no effect</li> * <li>Adding a region that overlaps with another will succeed with a warning message, although this is not likely to be discovered immediately</li> * <li>Any region with {@code start() == Address.zero())} is considered <em>unallocated</em> and <em>empty</em></li> * <li>Any number of unallocated regions may be in the list; they will sort by name at the beginning of the list</li> * <li>Adding an unallocated region with {@code size > 0} will succeed with a warning message, but it will be treated as <em>empty</em></li> * <li>The memory location of a set member might on occasion change, most typically when an <em>unallocated</em> region becomes <em>allocated</em></li> * <ul> * This is a very simple implementation, not designed for large sets. */ private final class SortedMemoryRegionSet<Region_Type extends MaxEntityMemoryRegion> implements Iterable<Region_Type> { /** * Compares first by starting address, then by entity name (which should only be needed for unallocated regions). */ final Comparator<Region_Type> regionComparator = new Comparator<Region_Type>() { @Override public int compare(Region_Type o1, Region_Type o2) { final int result = o1.start().compareTo(o2.start()); return result == 0 ? o1.owner().entityName().compareTo(o2.owner().entityName()) : result; } }; /** * The set of regions, sorted lazily by starting address. * <p> * Checking for overlaps takes place at sort time. */ private Region_Type[] regions; private int size; private boolean sorted; private SortedMemoryRegionSet() { Class<Region_Type[]> type = null; regions = Utils.cast(type, new MaxEntityMemoryRegion[20]); size = 0; sorted = true; } private SortedMemoryRegionSet<Region_Type> copy() { final SortedMemoryRegionSet<Region_Type> newSet = new SortedMemoryRegionSet<Region_Type>(); final Region_Type[] newRegions = Arrays.copyOf(regions, size); for (Region_Type region : newRegions) { newSet.add(region); } return newSet; } int size() { return size; } List<Region_Type> regions() { return Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(regions, size))); } /** * Searches for an element in this list based on an address. */ Region_Type find(Address address) { for (Region_Type region : this) { if (region.contains(address)) { return region; } } return null; } /** * Adds an element to this list. */ void add(Region_Type newRegion) { // Linear search to discover duplicates; not necessarily sorted. for (int index = 0; index < size; index++) { if (regions[index] == newRegion) { Trace.line(TRACE_VALUE, tracePrefix() + "Add ignoring duplicate: " + newRegion.owner().entityName()); return; } } if (regions.length == size) { int newCapacity = (regions.length * 3) / 2 + 1; regions = Arrays.copyOf(regions, newCapacity); } regions[size] = newRegion; sorted = false; size++; Trace.line(TRACE_VALUE, tracePrefix() + "Adding new: " + newRegion.owner().entityName()); } boolean remove(Region_Type oldRegion) { // Linear search to discover duplicates; not necessarily sorted. for (int index = 0; index < size; index++) { if (regions[index] == oldRegion) { if (index < size - 1) { System.arraycopy(regions, index + 1, regions, index, size - (index + 1)); } size--; Trace.line(TRACE_VALUE, tracePrefix() + "Removing: " + oldRegion.owner().entityName()); return true; } } Trace.line(TRACE_VALUE, tracePrefix() + "Removal failed: " + oldRegion.owner().entityName()); return false; } @Override public synchronized Iterator<Region_Type> iterator() { if (!sorted) { Arrays.sort(regions, 0, size, regionComparator); sorted = true; } Region_Type[] listCopy = Arrays.copyOf(regions, size); return Arrays.asList(listCopy).iterator(); } /** * Sort by starting address. */ private void ensureSorted() { if (!sorted) { Arrays.sort(regions, 0, size, regionComparator); sorted = true; for (Region_Type region : regions) { Region_Type previous = null; if (previous != null && previous.start().isNotZero() && previous.end().greaterThan(region.start())) { TeleWarning.message("Overlapping regions discovered: " + previous.owner().entityName()); } } } } } }