/*
* Copyright (C) 2008 Google Inc.
*
* 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.tools.perflib.heap;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.perflib.heap.analysis.Dominators;
import com.android.tools.perflib.heap.analysis.TopologicalSort;
import com.android.tools.perflib.heap.io.HprofBuffer;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/*
* A snapshot of all of the heaps, and related meta-data, for the runtime at a given instant.
*
* There are three possible heaps: default, app and zygote. GC roots are always reported in the
* default heap, and they are simply references to objects living in the zygote or the app heap.
* During parsing of the HPROF file HEAP_DUMP_INFO chunks change which heap is being referenced.
*/
public class Snapshot {
private static final String JAVA_LANG_CLASS = "java.lang.Class";
// Special root object used in dominator computation for objects reachable via multiple roots.
public static final Instance SENTINEL_ROOT = new RootObj(RootType.UNKNOWN);
private static final int DEFAULT_HEAP_ID = 0;
@NonNull
final HprofBuffer mBuffer;
@NonNull
ArrayList<Heap> mHeaps = new ArrayList<Heap>();
@NonNull
Heap mCurrentHeap;
private ImmutableList<Instance> mTopSort;
private Dominators mDominators;
public Snapshot(@NonNull HprofBuffer buffer) {
mBuffer = buffer;
setToDefaultHeap();
}
@NonNull
public Heap setToDefaultHeap() {
return setHeapTo(DEFAULT_HEAP_ID, "default");
}
@NonNull
public Heap setHeapTo(int id, @NonNull String name) {
Heap heap = getHeap(id);
if (heap == null) {
heap = new Heap(id, name);
heap.mSnapshot = this;
mHeaps.add(heap);
}
mCurrentHeap = heap;
return mCurrentHeap;
}
public int getHeapIndex(@NonNull Heap heap) {
return mHeaps.indexOf(heap);
}
@Nullable
public Heap getHeap(int id) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < mHeaps.size(); i++) {
if (mHeaps.get(i).getId() == id) {
return mHeaps.get(i);
}
}
return null;
}
@Nullable
public Heap getHeap(@NonNull String name) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < mHeaps.size(); i++) {
if (name.equals(mHeaps.get(i).getName())) {
return mHeaps.get(i);
}
}
return null;
}
@NonNull
public Collection<Heap> getHeaps() {
return mHeaps;
}
@NonNull
public Collection<RootObj> getGCRoots() {
// Roots are always in the default heap.
return mHeaps.get(DEFAULT_HEAP_ID).mRoots;
}
public final void addStackFrame(@NonNull StackFrame theFrame) {
mCurrentHeap.addStackFrame(theFrame);
}
public final StackFrame getStackFrame(long id) {
return mCurrentHeap.getStackFrame(id);
}
public final void addStackTrace(@NonNull StackTrace theTrace) {
mCurrentHeap.addStackTrace(theTrace);
}
public final StackTrace getStackTrace(int traceSerialNumber) {
return mCurrentHeap.getStackTrace(traceSerialNumber);
}
public final StackTrace getStackTraceAtDepth(int traceSerialNumber,
int depth) {
return mCurrentHeap.getStackTraceAtDepth(traceSerialNumber, depth);
}
public final void addRoot(@NonNull RootObj root) {
mCurrentHeap.addRoot(root);
root.setHeap(mCurrentHeap);
}
public final void addThread(ThreadObj thread, int serialNumber) {
mCurrentHeap.addThread(thread, serialNumber);
}
public final ThreadObj getThread(int serialNumber) {
return mCurrentHeap.getThread(serialNumber);
}
public final void addInstance(long id, @NonNull Instance instance) {
mCurrentHeap.addInstance(id, instance);
instance.setHeap(mCurrentHeap);
}
public final void addClass(long id, @NonNull ClassObj theClass) {
mCurrentHeap.addClass(id, theClass);
theClass.setHeap(mCurrentHeap);
}
@Nullable
public final Instance findReference(long id) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < mHeaps.size(); i++) {
Instance instance = mHeaps.get(i).getInstance(id);
if (instance != null) {
return instance;
}
}
// Couldn't find an instance of a class, look for a class object
return findClass(id);
}
@Nullable
public final ClassObj findClass(long id) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < mHeaps.size(); i++) {
ClassObj theClass = mHeaps.get(i).getClass(id);
if (theClass != null) {
return theClass;
}
}
return null;
}
@Nullable
public final ClassObj findClass(String name) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < mHeaps.size(); i++) {
ClassObj theClass = mHeaps.get(i).getClass(name);
if (theClass != null) {
return theClass;
}
}
return null;
}
public void resolveClasses() {
ClassObj clazz = findClass(JAVA_LANG_CLASS);
int javaLangClassSize = clazz != null ? clazz.getInstanceSize() : 0;
for (Heap heap : mHeaps) {
for (ClassObj classObj : heap.getClasses()) {
ClassObj superClass = classObj.getSuperClassObj();
if (superClass != null) {
superClass.addSubclass(classObj);
}
// We under-approximate the size of the class by including the size of Class.class
// and the size of static fields, and omitting padding, vtable and imtable sizes.
int classSize = javaLangClassSize;
for (Field f : classObj.mStaticFields) {
classSize += f.getType().getSize();
}
classObj.setSize(classSize);
}
for (Instance instance : heap.getInstances()) {
ClassObj classObj = instance.getClassObj();
if (classObj != null) {
classObj.addInstance(instance);
// Now is a good time to set the size of this instance
if (instance instanceof ClassInstance) {
instance.setSize(classObj.getInstanceSize());
}
}
}
}
}
// TODO: Break dominator computation into fixed chunks, because it can be unbounded/expensive.
public void computeDominators() {
if (mDominators == null) {
mTopSort = TopologicalSort.compute(getGCRoots());
mDominators = new Dominators(this, mTopSort);
mDominators.computeRetainedSizes();
}
}
@NonNull
public List<Instance> getReachableInstances() {
List<Instance> result = new ArrayList<Instance>(mTopSort.size());
for (Instance node : mTopSort) {
if (node.getImmediateDominator() != null) {
result.add(node);
}
}
return result;
}
public final void dumpInstanceCounts() {
for (Heap heap : mHeaps) {
System.out.println(
"+------------------ instance counts for heap: " + heap.getName());
heap.dumpInstanceCounts();
}
}
public final void dumpSizes() {
for (Heap heap : mHeaps) {
System.out.println(
"+------------------ sizes for heap: " + heap.getName());
heap.dumpSizes();
}
}
public final void dumpSubclasses() {
for (Heap heap : mHeaps) {
System.out.println(
"+------------------ subclasses for heap: " + heap.getName());
heap.dumpSubclasses();
}
}
}