blob: b6acd65abf2960cbcb0d6ea064c9a262a4712f15 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ddmlib;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.text.ParseException;
/**
* Describes the types and locations of objects in a segment of a heap.
*/
public final class HeapSegment implements Comparable<HeapSegment> {
/**
* Describes an object/region encoded in the HPSG data.
*/
public static class HeapSegmentElement implements Comparable<HeapSegmentElement> {
/*
* Solidity values, which must match the values in
* the HPSG data.
*/
/** The element describes a free block. */
public static final int SOLIDITY_FREE = 0;
/** The element is strongly-reachable. */
public static final int SOLIDITY_HARD = 1;
/** The element is softly-reachable. */
public static final int SOLIDITY_SOFT = 2;
/** The element is weakly-reachable. */
public static final int SOLIDITY_WEAK = 3;
/** The element is phantom-reachable. */
public static final int SOLIDITY_PHANTOM = 4;
/** The element is pending finalization. */
public static final int SOLIDITY_FINALIZABLE = 5;
/** The element is not reachable, and is about to be swept/freed. */
public static final int SOLIDITY_SWEEP = 6;
/** The reachability of the object is unknown. */
public static final int SOLIDITY_INVALID = -1;
/*
* Kind values, which must match the values in
* the HPSG data.
*/
/** The element describes a data object. */
public static final int KIND_OBJECT = 0;
/** The element describes a class object. */
public static final int KIND_CLASS_OBJECT = 1;
/** The element describes an array of 1-byte elements. */
public static final int KIND_ARRAY_1 = 2;
/** The element describes an array of 2-byte elements. */
public static final int KIND_ARRAY_2 = 3;
/** The element describes an array of 4-byte elements. */
public static final int KIND_ARRAY_4 = 4;
/** The element describes an array of 8-byte elements. */
public static final int KIND_ARRAY_8 = 5;
/** The element describes an unknown type of object. */
public static final int KIND_UNKNOWN = 6;
/** The element describes a native object. */
public static final int KIND_NATIVE = 7;
/** The object kind is unknown or unspecified. */
public static final int KIND_INVALID = -1;
/**
* A bit in the HPSG data that indicates that an element should
* be combined with the element that follows, typically because
* an element is too large to be described by a single element.
*/
private static final int PARTIAL_MASK = 1 << 7;
/**
* Describes the reachability/solidity of the element. Must
* be set to one of the SOLIDITY_* values.
*/
private int mSolidity;
/**
* Describes the type/kind of the element. Must be set to one
* of the KIND_* values.
*/
private int mKind;
/**
* Describes the length of the element, in bytes.
*/
private int mLength;
/**
* Creates an uninitialized element.
*/
public HeapSegmentElement() {
setSolidity(SOLIDITY_INVALID);
setKind(KIND_INVALID);
setLength(-1);
}
/**
* Create an element describing the entry at the current
* position of hpsgData.
*
* @param hs The heap segment to pull the entry from.
* @throws BufferUnderflowException if there is not a whole entry
* following the current position
* of hpsgData.
* @throws ParseException if the provided data is malformed.
*/
public HeapSegmentElement(HeapSegment hs)
throws BufferUnderflowException, ParseException {
set(hs);
}
/**
* Replace the element with the entry at the current position of
* hpsgData.
*
* @param hs The heap segment to pull the entry from.
* @return this object.
* @throws BufferUnderflowException if there is not a whole entry
* following the current position of
* hpsgData.
* @throws ParseException if the provided data is malformed.
*/
public HeapSegmentElement set(HeapSegment hs)
throws BufferUnderflowException, ParseException {
/* TODO: Maybe keep track of the virtual address of each element
* so that they can be examined independently.
*/
ByteBuffer data = hs.mUsageData;
int eState = data.get() & 0x000000ff;
int eLen = (data.get() & 0x000000ff) + 1;
while ((eState & PARTIAL_MASK) != 0) {
/* If the partial bit was set, the next byte should describe
* the same object as the current one.
*/
int nextState = data.get() & 0x000000ff;
if ((nextState & ~PARTIAL_MASK) != (eState & ~PARTIAL_MASK)) {
throw new ParseException("State mismatch", data.position());
}
eState = nextState;
eLen += (data.get() & 0x000000ff) + 1;
}
setSolidity(eState & 0x7);
setKind((eState >> 3) & 0x7);
setLength(eLen * hs.mAllocationUnitSize);
return this;
}
public int getSolidity() {
return mSolidity;
}
public void setSolidity(int solidity) {
this.mSolidity = solidity;
}
public int getKind() {
return mKind;
}
public void setKind(int kind) {
this.mKind = kind;
}
public int getLength() {
return mLength;
}
public void setLength(int length) {
this.mLength = length;
}
@Override
public int compareTo(HeapSegmentElement other) {
if (mLength != other.mLength) {
return mLength < other.mLength ? -1 : 1;
}
return 0;
}
}
//* The ID of the heap that this segment belongs to.
protected int mHeapId;
//* The size of an allocation unit, in bytes. (e.g., 8 bytes)
protected int mAllocationUnitSize;
//* The virtual address of the start of this segment.
protected long mStartAddress;
//* The offset of this pices from mStartAddress, in bytes.
protected int mOffset;
//* The number of allocation units described in this segment.
protected int mAllocationUnitCount;
//* The raw data that describes the contents of this segment.
protected ByteBuffer mUsageData;
//* mStartAddress is set to this value when the segment becomes invalid.
private static final long INVALID_START_ADDRESS = -1;
/**
* Create a new HeapSegment based on the raw contents
* of an HPSG chunk.
*
* @param hpsgData The raw data from an HPSG chunk.
* @throws BufferUnderflowException if hpsgData is too small
* to hold the HPSG chunk header data.
*/
public HeapSegment(ByteBuffer hpsgData) throws BufferUnderflowException {
/* Read the HPSG chunk header.
* These get*() calls may throw a BufferUnderflowException
* if the underlying data isn't big enough.
*/
hpsgData.order(ByteOrder.BIG_ENDIAN);
mHeapId = hpsgData.getInt();
mAllocationUnitSize = hpsgData.get();
mStartAddress = hpsgData.getInt() & 0x00000000ffffffffL;
mOffset = hpsgData.getInt();
mAllocationUnitCount = hpsgData.getInt();
// Hold onto the remainder of the data.
mUsageData = hpsgData.slice();
mUsageData.order(ByteOrder.BIG_ENDIAN); // doesn't actually matter
// Validate the data.
//xxx do it
//xxx make sure the number of elements matches mAllocationUnitCount.
//xxx make sure the last element doesn't have P set
}
/**
* See if this segment still contains data, and has not been
* appended to another segment.
*
* @return true if this segment has not been appended to
* another segment.
*/
public boolean isValid() {
return mStartAddress != INVALID_START_ADDRESS;
}
/**
* See if <code>other</code> comes immediately after this segment.
*
* @param other The HeapSegment to check.
* @return true if <code>other</code> comes immediately after this
* segment.
*/
public boolean canAppend(HeapSegment other) {
return isValid() && other.isValid() && mHeapId == other.mHeapId &&
mAllocationUnitSize == other.mAllocationUnitSize &&
getEndAddress() == other.getStartAddress();
}
/**
* Append the contents of <code>other</code> to this segment
* if it describes the segment immediately after this one.
*
* @param other The segment to append to this segment, if possible.
* If appended, <code>other</code> will be invalid
* when this method returns.
* @return true if <code>other</code> was successfully appended to
* this segment.
*/
public boolean append(HeapSegment other) {
if (canAppend(other)) {
/* Preserve the position. The mark is not preserved,
* but we don't use it anyway.
*/
int pos = mUsageData.position();
// Guarantee that we have enough room for the new data.
if (mUsageData.capacity() - mUsageData.limit() <
other.mUsageData.limit()) {
/* Grow more than necessary in case another append()
* is about to happen.
*/
int newSize = mUsageData.limit() + other.mUsageData.limit();
ByteBuffer newData = ByteBuffer.allocate(newSize * 2);
mUsageData.rewind();
newData.put(mUsageData);
mUsageData = newData;
}
// Copy the data from the other segment and restore the position.
other.mUsageData.rewind();
mUsageData.put(other.mUsageData);
mUsageData.position(pos);
// Fix this segment's header to cover the new data.
mAllocationUnitCount += other.mAllocationUnitCount;
// Mark the other segment as invalid.
other.mStartAddress = INVALID_START_ADDRESS;
other.mUsageData = null;
return true;
} else {
return false;
}
}
public long getStartAddress() {
return mStartAddress + mOffset;
}
public int getLength() {
return mAllocationUnitSize * mAllocationUnitCount;
}
public long getEndAddress() {
return getStartAddress() + getLength();
}
public void rewindElements() {
if (mUsageData != null) {
mUsageData.rewind();
}
}
public HeapSegmentElement getNextElement(HeapSegmentElement reuse) {
try {
if (reuse != null) {
return reuse.set(this);
} else {
return new HeapSegmentElement(this);
}
} catch (BufferUnderflowException ex) {
/* Normal "end of buffer" situation.
*/
} catch (ParseException ex) {
/* Malformed data.
*/
//TODO: we should catch this in the constructor
}
return null;
}
/*
* Method overrides for Comparable
*/
@Override
public boolean equals(Object o) {
if (o instanceof HeapSegment) {
return compareTo((HeapSegment) o) == 0;
}
return false;
}
@Override
public int hashCode() {
return mHeapId * 31 +
mAllocationUnitSize * 31 +
(int) mStartAddress * 31 +
mOffset * 31 +
mAllocationUnitCount * 31 +
mUsageData.hashCode();
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("HeapSegment { heap ").append(mHeapId)
.append(", start 0x")
.append(Integer.toHexString((int) getStartAddress()))
.append(", length ").append(getLength())
.append(" }");
return str.toString();
}
@Override
public int compareTo(HeapSegment other) {
if (mHeapId != other.mHeapId) {
return mHeapId < other.mHeapId ? -1 : 1;
}
if (getStartAddress() != other.getStartAddress()) {
return getStartAddress() < other.getStartAddress() ? -1 : 1;
}
/* If two segments have the same start address, the rest of
* the fields should be equal. Go through the motions, though.
* Note that we re-check the components of getStartAddress()
* (mStartAddress and mOffset) to make sure that all fields in
* an equal segment are equal.
*/
if (mAllocationUnitSize != other.mAllocationUnitSize) {
return mAllocationUnitSize < other.mAllocationUnitSize ? -1 : 1;
}
if (mStartAddress != other.mStartAddress) {
return mStartAddress < other.mStartAddress ? -1 : 1;
}
if (mOffset != other.mOffset) {
return mOffset < other.mOffset ? -1 : 1;
}
if (mAllocationUnitCount != other.mAllocationUnitCount) {
return mAllocationUnitCount < other.mAllocationUnitCount ? -1 : 1;
}
if (mUsageData != other.mUsageData) {
return mUsageData.compareTo(other.mUsageData);
}
return 0;
}
}