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 java.awt.image.BufferedImage; 20 import java.util.Iterator; 21 import java.util.NoSuchElementException; 22 23 /** 24 * A typical Java object from a parsed heap dump. 25 * Note that this is used for Java objects that are instances of classes (as 26 * opposed to arrays), not for class objects themselves. 27 * See {@link AhatClassObj } for the representation of class objects. 28 * <p> 29 * This class provides a method for iterating over the instance fields of the 30 * object in addition to those methods inherited from {@link AhatInstance}. 31 */ 32 public class AhatClassInstance extends AhatInstance { 33 // Instance fields of the object. These are stored in order of the instance 34 // field descriptors from the class object, starting with this class first, 35 // followed by the super class, and so on. We store the values separate from 36 // the field types and names to save memory. 37 private Value[] mFields; 38 AhatClassInstance(long id)39 AhatClassInstance(long id) { 40 super(id); 41 } 42 initialize(Value[] fields)43 void initialize(Value[] fields) { 44 mFields = fields; 45 } 46 47 @Override getExtraJavaSize()48 long getExtraJavaSize() { 49 return 0; 50 } 51 getField(String fieldName)52 @Override public Value getField(String fieldName) { 53 for (FieldValue field : getInstanceFields()) { 54 if (fieldName.equals(field.name)) { 55 return field.value; 56 } 57 } 58 return null; 59 } 60 getRefField(String fieldName)61 @Override public AhatInstance getRefField(String fieldName) { 62 Value value = getField(fieldName); 63 return value == null ? null : value.asAhatInstance(); 64 } 65 66 /** 67 * Read an int field of an instance. 68 * The field is assumed to be an int type. 69 * Returns <code>def</code> if the field value is not an int or could not be 70 * read. 71 */ getIntField(String fieldName, Integer def)72 private Integer getIntField(String fieldName, Integer def) { 73 Value value = getField(fieldName); 74 if (value == null || !value.isInteger()) { 75 return def; 76 } 77 return value.asInteger(); 78 } 79 80 /** 81 * Read a long field of this instance. 82 * The field is assumed to be a long type. 83 * Returns <code>def</code> if the field value is not an long or could not 84 * be read. 85 */ getLongField(String fieldName, Long def)86 private Long getLongField(String fieldName, Long def) { 87 Value value = getField(fieldName); 88 if (value == null || !value.isLong()) { 89 return def; 90 } 91 return value.asLong(); 92 } 93 94 /** 95 * Returns the list of class instance fields for this instance. 96 * Includes values of field inherited from the superclass of this instance. 97 * The fields are returned in no particular order. 98 * 99 * @return Iterable over the instance field values. 100 */ getInstanceFields()101 public Iterable<FieldValue> getInstanceFields() { 102 return new InstanceFieldIterator(mFields, getClassObj()); 103 } 104 105 @Override getReferences()106 Iterable<Reference> getReferences() { 107 if (isInstanceOfClass("java.lang.ref.Reference")) { 108 return new WeakReferentReferenceIterator(); 109 } 110 return new StrongReferenceIterator(); 111 } 112 113 /** 114 * Returns true if this is an instance of a (subclass of a) class with the 115 * given name. 116 */ isInstanceOfClass(String className)117 private boolean isInstanceOfClass(String className) { 118 AhatClassObj cls = getClassObj(); 119 while (cls != null) { 120 if (className.equals(cls.getName())) { 121 return true; 122 } 123 cls = cls.getSuperClassObj(); 124 } 125 return false; 126 } 127 asString(int maxChars)128 @Override public String asString(int maxChars) { 129 if (!isInstanceOfClass("java.lang.String")) { 130 return null; 131 } 132 133 Value value = getField("value"); 134 if (value == null || !value.isAhatInstance()) { 135 return null; 136 } 137 138 AhatInstance inst = value.asAhatInstance(); 139 if (inst.isArrayInstance()) { 140 AhatArrayInstance chars = inst.asArrayInstance(); 141 int numChars = chars.getLength(); 142 int count = getIntField("count", numChars); 143 int offset = getIntField("offset", 0); 144 return chars.asMaybeCompressedString(offset, count, maxChars); 145 } 146 return null; 147 } 148 getReferent()149 @Override public AhatInstance getReferent() { 150 if (isInstanceOfClass("java.lang.ref.Reference")) { 151 return getRefField("referent"); 152 } 153 return null; 154 } 155 getDexCacheLocation(int maxChars)156 @Override public String getDexCacheLocation(int maxChars) { 157 if (isInstanceOfClass("java.lang.DexCache")) { 158 AhatInstance location = getRefField("location"); 159 if (location != null) { 160 return location.asString(maxChars); 161 } 162 } 163 return null; 164 } 165 getAssociatedBitmapInstance()166 @Override public AhatInstance getAssociatedBitmapInstance() { 167 return getBitmapInfo() == null ? null : this; 168 } 169 isClassInstance()170 @Override public boolean isClassInstance() { 171 return true; 172 } 173 asClassInstance()174 @Override public AhatClassInstance asClassInstance() { 175 return this; 176 } 177 toString()178 @Override public String toString() { 179 return String.format("%s@%08x", getClassName(), getId()); 180 } 181 182 /** 183 * Read the given field from the given instance. 184 * The field is assumed to be a byte[] field. 185 * Returns null if the field value is null, not a byte[] or could not be read. 186 */ getByteArrayField(String fieldName)187 private byte[] getByteArrayField(String fieldName) { 188 AhatInstance field = getRefField(fieldName); 189 return field == null ? null : field.asByteArray(); 190 } 191 192 private static class BitmapInfo { 193 public final int width; 194 public final int height; 195 public final byte[] buffer; 196 BitmapInfo(int width, int height, byte[] buffer)197 public BitmapInfo(int width, int height, byte[] buffer) { 198 this.width = width; 199 this.height = height; 200 this.buffer = buffer; 201 } 202 } 203 204 /** 205 * Return bitmap info for this object, or null if no appropriate bitmap 206 * info is available. 207 */ getBitmapInfo()208 private BitmapInfo getBitmapInfo() { 209 if (!isInstanceOfClass("android.graphics.Bitmap")) { 210 return null; 211 } 212 213 Integer width = getIntField("mWidth", null); 214 if (width == null) { 215 return null; 216 } 217 218 Integer height = getIntField("mHeight", null); 219 if (height == null) { 220 return null; 221 } 222 223 byte[] buffer = getByteArrayField("mBuffer"); 224 if (buffer == null) { 225 return null; 226 } 227 228 if (buffer.length < 4 * height * width) { 229 return null; 230 } 231 232 return new BitmapInfo(width, height, buffer); 233 234 } 235 asBitmap()236 @Override public BufferedImage asBitmap() { 237 BitmapInfo info = getBitmapInfo(); 238 if (info == null) { 239 return null; 240 } 241 242 // Convert the raw data to an image 243 // Convert BGRA to ABGR 244 int[] abgr = new int[info.height * info.width]; 245 for (int i = 0; i < abgr.length; i++) { 246 abgr[i] = ( 247 (((int) info.buffer[i * 4 + 3] & 0xFF) << 24) 248 + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16) 249 + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8) 250 + ((int) info.buffer[i * 4 + 2] & 0xFF)); 251 } 252 253 BufferedImage bitmap = new BufferedImage( 254 info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR); 255 bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width); 256 return bitmap; 257 } 258 259 @Override asRegisteredNativeAllocation()260 RegisteredNativeAllocation asRegisteredNativeAllocation() { 261 if (!isInstanceOfClass("sun.misc.Cleaner")) { 262 return null; 263 } 264 265 Value vthunk = getField("thunk"); 266 if (vthunk == null || !vthunk.isAhatInstance()) { 267 return null; 268 } 269 270 AhatClassInstance thunk = vthunk.asAhatInstance().asClassInstance(); 271 if (thunk == null 272 || !thunk.isInstanceOfClass("libcore.util.NativeAllocationRegistry$CleanerThunk")) { 273 return null; 274 } 275 276 Value vregistry = thunk.getField("this$0"); 277 if (vregistry == null || !vregistry.isAhatInstance()) { 278 return null; 279 } 280 281 AhatClassInstance registry = vregistry.asAhatInstance().asClassInstance(); 282 if (registry == null || !registry.isInstanceOfClass("libcore.util.NativeAllocationRegistry")) { 283 return null; 284 } 285 286 Value size = registry.getField("size"); 287 if (!size.isLong()) { 288 return null; 289 } 290 291 Value referent = getField("referent"); 292 if (referent == null || !referent.isAhatInstance()) { 293 return null; 294 } 295 296 RegisteredNativeAllocation rna = new RegisteredNativeAllocation(); 297 rna.referent = referent.asAhatInstance(); 298 rna.size = size.asLong(); 299 return rna; 300 } 301 302 private static class InstanceFieldIterator implements Iterable<FieldValue>, 303 Iterator<FieldValue> { 304 // The complete list of instance field values to iterate over, including 305 // superclass field values. 306 private Value[] mValues; 307 private int mValueIndex; 308 309 // The list of field descriptors specific to the current class in the 310 // class hierarchy, not including superclass field descriptors. 311 // mFields and mFieldIndex are reset each time we walk up to the next 312 // superclass in the call hierarchy. 313 private Field[] mFields; 314 private int mFieldIndex; 315 private AhatClassObj mNextClassObj; 316 InstanceFieldIterator(Value[] values, AhatClassObj classObj)317 public InstanceFieldIterator(Value[] values, AhatClassObj classObj) { 318 mValues = values; 319 mFields = classObj.getInstanceFields(); 320 mValueIndex = 0; 321 mFieldIndex = 0; 322 mNextClassObj = classObj.getSuperClassObj(); 323 } 324 325 @Override hasNext()326 public boolean hasNext() { 327 // If we have reached the end of the fields in the current class, 328 // continue walking up the class hierarchy to get superclass fields as 329 // well. 330 while (mFieldIndex == mFields.length && mNextClassObj != null) { 331 mFields = mNextClassObj.getInstanceFields(); 332 mFieldIndex = 0; 333 mNextClassObj = mNextClassObj.getSuperClassObj(); 334 } 335 return mFieldIndex < mFields.length; 336 } 337 338 @Override next()339 public FieldValue next() { 340 if (!hasNext()) { 341 throw new NoSuchElementException(); 342 } 343 Field field = mFields[mFieldIndex++]; 344 Value value = mValues[mValueIndex++]; 345 return new FieldValue(field.name, field.type, value); 346 } 347 348 @Override iterator()349 public Iterator<FieldValue> iterator() { 350 return this; 351 } 352 } 353 354 /** 355 * A Reference iterator that iterates over the fields of this instance 356 * assuming all field references are strong references. 357 */ 358 private class StrongReferenceIterator implements Iterable<Reference>, 359 Iterator<Reference> { 360 private Iterator<FieldValue> mIter = getInstanceFields().iterator(); 361 private Reference mNext = null; 362 363 @Override hasNext()364 public boolean hasNext() { 365 while (mNext == null && mIter.hasNext()) { 366 FieldValue field = mIter.next(); 367 if (field.value != null && field.value.isAhatInstance()) { 368 AhatInstance ref = field.value.asAhatInstance(); 369 mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true); 370 } 371 } 372 return mNext != null; 373 } 374 375 @Override next()376 public Reference next() { 377 if (!hasNext()) { 378 throw new NoSuchElementException(); 379 } 380 Reference next = mNext; 381 mNext = null; 382 return next; 383 } 384 385 @Override iterator()386 public Iterator<Reference> iterator() { 387 return this; 388 } 389 } 390 391 /** 392 * A Reference iterator that iterates over the fields of a subclass of 393 * java.lang.ref.Reference, where the 'referent' field is considered weak. 394 */ 395 private class WeakReferentReferenceIterator implements Iterable<Reference>, 396 Iterator<Reference> { 397 private Iterator<FieldValue> mIter = getInstanceFields().iterator(); 398 private Reference mNext = null; 399 400 @Override hasNext()401 public boolean hasNext() { 402 while (mNext == null && mIter.hasNext()) { 403 FieldValue field = mIter.next(); 404 if (field.value != null && field.value.isAhatInstance()) { 405 boolean strong = !field.name.equals("referent"); 406 AhatInstance ref = field.value.asAhatInstance(); 407 mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong); 408 } 409 } 410 return mNext != null; 411 } 412 413 @Override next()414 public Reference next() { 415 if (!hasNext()) { 416 throw new NoSuchElementException(); 417 } 418 Reference next = mNext; 419 mNext = null; 420 return next; 421 } 422 423 @Override iterator()424 public Iterator<Reference> iterator() { 425 return this; 426 } 427 } 428 } 429