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