1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ahat.heapdump; 18 19 import com.android.tools.perflib.heap.ClassObj; 20 import com.android.tools.perflib.heap.Instance; 21 import com.android.tools.perflib.heap.RootObj; 22 import java.awt.image.BufferedImage; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.List; 28 29 public abstract class AhatInstance implements Diffable<AhatInstance> { 30 private long mId; 31 private long mSize; 32 private long mTotalRetainedSize; 33 private long mRetainedSizes[]; // Retained size indexed by heap index 34 private boolean mIsReachable; 35 private AhatHeap mHeap; 36 private AhatInstance mImmediateDominator; 37 private AhatInstance mNextInstanceToGcRoot; 38 private String mNextInstanceToGcRootField = "???"; 39 private AhatClassObj mClassObj; 40 private AhatInstance[] mHardReverseReferences; 41 private AhatInstance[] mSoftReverseReferences; 42 private Site mSite; 43 44 // If this instance is a root, mRootTypes contains a set of the root types. 45 // If this instance is not a root, mRootTypes is null. 46 private List<String> mRootTypes; 47 48 // List of instances this instance immediately dominates. 49 private List<AhatInstance> mDominated = new ArrayList<AhatInstance>(); 50 51 private AhatInstance mBaseline; 52 53 public AhatInstance(long id) { 54 mId = id; 55 mBaseline = this; 56 } 57 58 /** 59 * Initializes this AhatInstance based on the given perflib instance. 60 * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps. 61 * There is no guarantee that the AhatInstances returned by 62 * snapshot.findInstance have been initialized yet. 63 */ 64 void initialize(AhatSnapshot snapshot, Instance inst) { 65 mId = inst.getId(); 66 mSize = inst.getSize(); 67 mTotalRetainedSize = inst.getTotalRetainedSize(); 68 mIsReachable = inst.isReachable(); 69 70 List<AhatHeap> heaps = snapshot.getHeaps(); 71 mRetainedSizes = new long[heaps.size()]; 72 for (AhatHeap heap : heaps) { 73 mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex()); 74 } 75 76 mHeap = snapshot.getHeap(inst.getHeap().getName()); 77 78 Instance dom = inst.getImmediateDominator(); 79 if (dom == null || dom instanceof RootObj) { 80 mImmediateDominator = null; 81 } else { 82 mImmediateDominator = snapshot.findInstance(dom.getId()); 83 mImmediateDominator.mDominated.add(this); 84 } 85 86 ClassObj clsObj = inst.getClassObj(); 87 if (clsObj != null) { 88 mClassObj = snapshot.findClassObj(clsObj.getId()); 89 } 90 91 // A couple notes about reverse references: 92 // * perflib sometimes returns unreachable reverse references. If 93 // snapshot.findInstance returns null, it means the reverse reference is 94 // not reachable, so we filter it out. 95 // * We store the references as AhatInstance[] instead of 96 // ArrayList<AhatInstance> because it saves a lot of space and helps 97 // with performance when there are a lot of AhatInstances. 98 ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>(); 99 ahatRefs = new ArrayList<AhatInstance>(); 100 for (Instance ref : inst.getHardReverseReferences()) { 101 AhatInstance ahat = snapshot.findInstance(ref.getId()); 102 if (ahat != null) { 103 ahatRefs.add(ahat); 104 } 105 } 106 mHardReverseReferences = new AhatInstance[ahatRefs.size()]; 107 ahatRefs.toArray(mHardReverseReferences); 108 109 List<Instance> refs = inst.getSoftReverseReferences(); 110 ahatRefs.clear(); 111 if (refs != null) { 112 for (Instance ref : refs) { 113 AhatInstance ahat = snapshot.findInstance(ref.getId()); 114 if (ahat != null) { 115 ahatRefs.add(ahat); 116 } 117 } 118 } 119 mSoftReverseReferences = new AhatInstance[ahatRefs.size()]; 120 ahatRefs.toArray(mSoftReverseReferences); 121 } 122 123 /** 124 * Returns a unique identifier for the instance. 125 */ 126 public long getId() { 127 return mId; 128 } 129 130 /** 131 * Returns the shallow number of bytes this object takes up. 132 */ 133 public long getSize() { 134 return mSize; 135 } 136 137 /** 138 * Returns the number of bytes belonging to the given heap that this instance 139 * retains. 140 */ 141 public long getRetainedSize(AhatHeap heap) { 142 int index = heap.getIndex(); 143 return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0; 144 } 145 146 /** 147 * Returns the total number of bytes this instance retains. 148 */ 149 public long getTotalRetainedSize() { 150 return mTotalRetainedSize; 151 } 152 153 /** 154 * Returns whether this object is strongly-reachable. 155 */ 156 public boolean isReachable() { 157 return mIsReachable; 158 } 159 160 /** 161 * Returns the heap that this instance is allocated on. 162 */ 163 public AhatHeap getHeap() { 164 return mHeap; 165 } 166 167 /** 168 * Returns true if this instance is marked as a root instance. 169 */ 170 public boolean isRoot() { 171 return mRootTypes != null; 172 } 173 174 /** 175 * Marks this instance as being a root of the given type. 176 */ 177 void addRootType(String type) { 178 if (mRootTypes == null) { 179 mRootTypes = new ArrayList<String>(); 180 mRootTypes.add(type); 181 } else if (!mRootTypes.contains(type)) { 182 mRootTypes.add(type); 183 } 184 } 185 186 /** 187 * Returns a list of string descriptions of the root types of this object. 188 * Returns null if this object is not a root. 189 */ 190 public Collection<String> getRootTypes() { 191 return mRootTypes; 192 } 193 194 /** 195 * Returns the immediate dominator of this instance. 196 * Returns null if this is a root instance. 197 */ 198 public AhatInstance getImmediateDominator() { 199 return mImmediateDominator; 200 } 201 202 /** 203 * Returns a list of those objects immediately dominated by the given 204 * instance. 205 */ 206 public List<AhatInstance> getDominated() { 207 return mDominated; 208 } 209 210 /** 211 * Returns the site where this instance was allocated. 212 */ 213 public Site getSite() { 214 return mSite; 215 } 216 217 /** 218 * Sets the allocation site of this instance. 219 */ 220 void setSite(Site site) { 221 mSite = site; 222 } 223 224 /** 225 * Returns true if the given instance is a class object 226 */ 227 public boolean isClassObj() { 228 // Overridden by AhatClassObj. 229 return false; 230 } 231 232 /** 233 * Returns this as an AhatClassObj if this is an AhatClassObj. 234 * Returns null if this is not an AhatClassObj. 235 */ 236 public AhatClassObj asClassObj() { 237 // Overridden by AhatClassObj. 238 return null; 239 } 240 241 /** 242 * Returns the class object instance for the class of this object. 243 */ 244 public AhatClassObj getClassObj() { 245 return mClassObj; 246 } 247 248 /** 249 * Returns the name of the class this object belongs to. 250 */ 251 public String getClassName() { 252 AhatClassObj classObj = getClassObj(); 253 return classObj == null ? "???" : classObj.getName(); 254 } 255 256 /** 257 * Returns true if the given instance is an array instance 258 */ 259 public boolean isArrayInstance() { 260 // Overridden by AhatArrayInstance. 261 return false; 262 } 263 264 /** 265 * Returns this as an AhatArrayInstance if this is an AhatArrayInstance. 266 * Returns null if this is not an AhatArrayInstance. 267 */ 268 public AhatArrayInstance asArrayInstance() { 269 // Overridden by AhatArrayInstance. 270 return null; 271 } 272 273 /** 274 * Returns true if the given instance is a class instance 275 */ 276 public boolean isClassInstance() { 277 return false; 278 } 279 280 /** 281 * Returns this as an AhatClassInstance if this is an AhatClassInstance. 282 * Returns null if this is not an AhatClassInstance. 283 */ 284 public AhatClassInstance asClassInstance() { 285 return null; 286 } 287 288 /** 289 * Return the referent associated with this instance. 290 * This is relevent for instances of java.lang.ref.Reference. 291 * Returns null if the instance has no referent associated with it. 292 */ 293 public AhatInstance getReferent() { 294 // Overridden by AhatClassInstance. 295 return null; 296 } 297 298 /** 299 * Returns a list of objects with hard references to this object. 300 */ 301 public List<AhatInstance> getHardReverseReferences() { 302 return Arrays.asList(mHardReverseReferences); 303 } 304 305 /** 306 * Returns a list of objects with soft references to this object. 307 */ 308 public List<AhatInstance> getSoftReverseReferences() { 309 return Arrays.asList(mSoftReverseReferences); 310 } 311 312 /** 313 * Returns the value of a field of an instance. 314 * Returns null if the field value is null, the field couldn't be read, or 315 * there are multiple fields with the same name. 316 */ 317 public Value getField(String fieldName) { 318 // Overridden by AhatClassInstance. 319 return null; 320 } 321 322 /** 323 * Reads a reference field of this instance. 324 * Returns null if the field value is null, or if the field couldn't be read. 325 */ 326 public AhatInstance getRefField(String fieldName) { 327 // Overridden by AhatClassInstance. 328 return null; 329 } 330 331 /** 332 * Assuming inst represents a DexCache object, return the dex location for 333 * that dex cache. Returns null if the given instance doesn't represent a 334 * DexCache object or the location could not be found. 335 * If maxChars is non-negative, the returned location is truncated to 336 * maxChars in length. 337 */ 338 public String getDexCacheLocation(int maxChars) { 339 return null; 340 } 341 342 /** 343 * Return the bitmap instance associated with this object, or null if there 344 * is none. This works for android.graphics.Bitmap instances and their 345 * underlying Byte[] instances. 346 */ 347 public AhatInstance getAssociatedBitmapInstance() { 348 return null; 349 } 350 351 /** 352 * Read the string value from this instance. 353 * Returns null if this object can't be interpreted as a string. 354 * The returned string is truncated to maxChars characters. 355 * If maxChars is negative, the returned string is not truncated. 356 */ 357 public String asString(int maxChars) { 358 // By default instances can't be interpreted as a string. This method is 359 // overridden by AhatClassInstance and AhatArrayInstance for those cases 360 // when an instance can be interpreted as a string. 361 return null; 362 } 363 364 /** 365 * Reads the string value from an hprof Instance. 366 * Returns null if the object can't be interpreted as a string. 367 */ 368 public String asString() { 369 return asString(-1); 370 } 371 372 /** 373 * Return the bitmap associated with the given instance, if any. 374 * This is relevant for instances of android.graphics.Bitmap and byte[]. 375 * Returns null if there is no bitmap associated with the given instance. 376 */ 377 public BufferedImage asBitmap() { 378 return null; 379 } 380 381 /** 382 * Returns a sample path from a GC root to this instance. 383 * This instance is included as the last element of the path with an empty 384 * field description. 385 */ 386 public List<PathElement> getPathFromGcRoot() { 387 List<PathElement> path = new ArrayList<PathElement>(); 388 389 AhatInstance dom = this; 390 for (PathElement elem = new PathElement(this, ""); elem != null; 391 elem = getNextPathElementToGcRoot(elem.instance)) { 392 if (elem.instance.equals(dom)) { 393 elem.isDominator = true; 394 dom = dom.getImmediateDominator(); 395 } 396 path.add(elem); 397 } 398 Collections.reverse(path); 399 return path; 400 } 401 402 /** 403 * Returns the next instance to GC root from this object and a string 404 * description of which field of that object refers to the given instance. 405 * Returns null if the given instance has no next instance to the gc root. 406 */ 407 private static PathElement getNextPathElementToGcRoot(AhatInstance inst) { 408 AhatInstance parent = inst.mNextInstanceToGcRoot; 409 if (parent == null) { 410 return null; 411 } 412 return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField); 413 } 414 415 void setNextInstanceToGcRoot(AhatInstance inst, String field) { 416 mNextInstanceToGcRoot = inst; 417 mNextInstanceToGcRootField = field; 418 } 419 420 /** Returns a human-readable identifier for this object. 421 * For class objects, the string is the class name. 422 * For class instances, the string is the class name followed by '@' and the 423 * hex id of the instance. 424 * For array instances, the string is the array type followed by the size in 425 * square brackets, followed by '@' and the hex id of the instance. 426 */ 427 @Override public abstract String toString(); 428 429 /** 430 * Read the byte[] value from an hprof Instance. 431 * Returns null if the instance is not a byte array. 432 */ 433 byte[] asByteArray() { 434 return null; 435 } 436 437 public void setBaseline(AhatInstance baseline) { 438 mBaseline = baseline; 439 } 440 441 @Override public AhatInstance getBaseline() { 442 return mBaseline; 443 } 444 445 @Override public boolean isPlaceHolder() { 446 return false; 447 } 448 449 /** 450 * Returns a new place holder instance corresponding to this instance. 451 */ 452 AhatInstance newPlaceHolderInstance() { 453 return new AhatPlaceHolderInstance(this); 454 } 455 } 456