/* * 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.ShortestDistanceVisitor; import com.android.tools.perflib.heap.analysis.TopologicalSort; import com.android.tools.perflib.heap.io.HprofBuffer; import com.google.common.collect.ImmutableList; import gnu.trove.THashSet; import java.util.*; /* * 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; // The set of all classes that are (sub)class(es) of java.lang.ref.Reference. private THashSet<ClassObj> mReferenceClasses = new THashSet<ClassObj>(); private int[] mTypeSizes; private long mIdSizeMask = 0x00000000ffffffffl; 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 setIdSize(int size) { int maxId = -1; for (int i = 0; i < Type.values().length; ++i) { maxId = Math.max(Type.values()[i].getTypeId(), maxId); } assert (maxId > 0) && (maxId <= Type.LONG.getTypeId()); // Update this if hprof format ever changes its supported types. mTypeSizes = new int[maxId + 1]; Arrays.fill(mTypeSizes, -1); for (int i = 0; i < Type.values().length; ++i) { mTypeSizes[Type.values()[i].getTypeId()] = Type.values()[i].getSize(); } mTypeSizes[Type.OBJECT.getTypeId()] = size; mIdSizeMask = 0xffffffffffffffffl >>> ((8 - size) * 8); } public final int getTypeSize(Type type) { return mTypeSizes[type.getTypeId()]; } public final long getIdSizeMask() { return mIdSizeMask; } 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 findInstance(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; } /** * Finds the first ClassObj with a class name that matches <code>name</code>. * * @param name of the class to find * @return the found <code>ClassObj</code>, or null if not found */ @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; } /** * Finds all <code>ClassObj</code>s with class name that match the given <code>name</code>. * * @param name of the class to find * @return a collection of the found <code>ClassObj</code>s, or empty collection if not found */ @NonNull public final Collection<ClassObj> findClasses(String name) { ArrayList<ClassObj> classObjs = new ArrayList<ClassObj>(); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < mHeaps.size(); i++) { classObjs.addAll(mHeaps.get(i).getClasses(name)); } return classObjs; } 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 += getTypeSize(f.getType()); } classObj.setSize(classSize); } for (Instance instance : heap.getInstances()) { ClassObj classObj = instance.getClassObj(); if (classObj != null) { classObj.addInstance(heap.getId(), instance); } } } } public void resolveReferences() { List<ClassObj> referenceDescendants = findAllDescendantClasses(ClassObj.getReferenceClassName()); for (ClassObj classObj : referenceDescendants) { classObj.setIsSoftReference(); mReferenceClasses.add(classObj); } } @NonNull public List<ClassObj> findAllDescendantClasses(@NonNull String className) { Collection<ClassObj> ancestorClasses = findClasses(className); List<ClassObj> descendants = new ArrayList<ClassObj>(); for (ClassObj ancestor : ancestorClasses) { descendants.addAll(ancestor.getDescendantClasses()); } return descendants; } // 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(); ShortestDistanceVisitor shortestDistanceVisitor = new ShortestDistanceVisitor(); shortestDistanceVisitor.doVisit(getGCRoots()); } } @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 ImmutableList<Instance> getTopologicalOrdering() { return mTopSort; } 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(); } } }