Home | History | Annotate | Download | only in heapdump
      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 
     39   AhatClassInstance(long id) {
     40     super(id);
     41   }
     42 
     43   void initialize(Value[] fields) {
     44     mFields = fields;
     45   }
     46 
     47   @Override
     48   long getExtraJavaSize() {
     49     return 0;
     50   }
     51 
     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 
     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    */
     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    */
     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    */
    101   public Iterable<FieldValue> getInstanceFields() {
    102     return new InstanceFieldIterator(mFields, getClassObj());
    103   }
    104 
    105   @Override
    106   Iterable<Reference> getReferences() {
    107     return new ReferenceIterator();
    108   }
    109 
    110   @Override public String asString(int maxChars) {
    111     if (!isInstanceOfClass("java.lang.String")) {
    112       return null;
    113     }
    114 
    115     Value value = getField("value");
    116     if (value == null || !value.isAhatInstance()) {
    117       return null;
    118     }
    119 
    120     AhatInstance inst = value.asAhatInstance();
    121     if (inst.isArrayInstance()) {
    122       AhatArrayInstance chars = inst.asArrayInstance();
    123       int numChars = chars.getLength();
    124       int count = getIntField("count", numChars);
    125       int offset = getIntField("offset", 0);
    126       return chars.asMaybeCompressedString(offset, count, maxChars);
    127     }
    128     return null;
    129   }
    130 
    131   @Override public AhatInstance getReferent() {
    132     if (isInstanceOfClass("java.lang.ref.Reference")) {
    133       return getRefField("referent");
    134     }
    135     return null;
    136   }
    137 
    138   @Override public String getDexCacheLocation(int maxChars) {
    139     if (isInstanceOfClass("java.lang.DexCache")) {
    140       AhatInstance location = getRefField("location");
    141       if (location != null) {
    142         return location.asString(maxChars);
    143       }
    144     }
    145     return null;
    146   }
    147 
    148   @Override public String getBinderProxyInterfaceName() {
    149     if (isInstanceOfClass("android.os.BinderProxy")) {
    150       for (AhatInstance inst : getReverseReferences()) {
    151         String className = inst.getClassName();
    152         if (className.endsWith("$Stub$Proxy")) {
    153           Value value = inst.getField("mRemote");
    154           if (value != null && value.asAhatInstance() == this) {
    155             return className.substring(0, className.lastIndexOf("$Stub$Proxy"));
    156           }
    157         }
    158       }
    159     }
    160     return null;
    161   }
    162 
    163   @Override public String getBinderTokenDescriptor() {
    164     String descriptor = getBinderDescriptor();
    165     if (descriptor == null) {
    166       return null;
    167     }
    168 
    169     if (isInstanceOfClass(descriptor + "$Stub")) {
    170       // This is an instance of an auto-generated interface class, and
    171       // therefore not a binder token.
    172       return null;
    173     }
    174 
    175     return descriptor;
    176   }
    177 
    178   @Override public String getBinderStubInterfaceName() {
    179     String descriptor = getBinderDescriptor();
    180     if (descriptor == null || descriptor.isEmpty()) {
    181       // Binder interface stubs always have a non-empty descriptor
    182       return null;
    183     }
    184 
    185     // We only consider something a binder service if it's an instance of the
    186     // auto-generated descriptor$Stub class.
    187     if (isInstanceOfClass(descriptor + "$Stub")) {
    188       return descriptor;
    189     }
    190 
    191     return null;
    192   }
    193 
    194   @Override public AhatInstance getAssociatedBitmapInstance() {
    195     return getBitmapInfo() == null ? null : this;
    196   }
    197 
    198   @Override public boolean isClassInstance() {
    199     return true;
    200   }
    201 
    202   @Override public AhatClassInstance asClassInstance() {
    203     return this;
    204   }
    205 
    206   @Override public String toString() {
    207     return String.format("%s@%08x", getClassName(), getId());
    208   }
    209 
    210   /**
    211    * Returns the descriptor of an android.os.Binder object.
    212    * If no descriptor is set, returns an empty string.
    213    * If the object is not an android.os.Binder object, returns null.
    214    */
    215   private String getBinderDescriptor() {
    216     if (isInstanceOfClass("android.os.Binder")) {
    217       Value value = getField("mDescriptor");;
    218 
    219       if (value == null) {
    220         return "";
    221       } else {
    222         return value.asAhatInstance().asString();
    223       }
    224     } else {
    225       return null;
    226     }
    227   }
    228 
    229   /**
    230    * Read the given field from the given instance.
    231    * The field is assumed to be a byte[] field.
    232    * Returns null if the field value is null, not a byte[] or could not be read.
    233    */
    234   private byte[] getByteArrayField(String fieldName) {
    235     AhatInstance field = getRefField(fieldName);
    236     return field == null ? null : field.asByteArray();
    237   }
    238 
    239   private static class BitmapInfo {
    240     public final int width;
    241     public final int height;
    242     public final byte[] buffer;
    243 
    244     public BitmapInfo(int width, int height, byte[] buffer) {
    245       this.width = width;
    246       this.height = height;
    247       this.buffer = buffer;
    248     }
    249   }
    250 
    251   /**
    252    * Return bitmap info for this object, or null if no appropriate bitmap
    253    * info is available.
    254    */
    255   private BitmapInfo getBitmapInfo() {
    256     if (!isInstanceOfClass("android.graphics.Bitmap")) {
    257       return null;
    258     }
    259 
    260     Integer width = getIntField("mWidth", null);
    261     if (width == null) {
    262       return null;
    263     }
    264 
    265     Integer height = getIntField("mHeight", null);
    266     if (height == null) {
    267       return null;
    268     }
    269 
    270     byte[] buffer = getByteArrayField("mBuffer");
    271     if (buffer == null) {
    272       return null;
    273     }
    274 
    275     if (buffer.length < 4 * height * width) {
    276       return null;
    277     }
    278 
    279     return new BitmapInfo(width, height, buffer);
    280 
    281   }
    282 
    283   @Override public BufferedImage asBitmap() {
    284     BitmapInfo info = getBitmapInfo();
    285     if (info == null) {
    286       return null;
    287     }
    288 
    289     // Convert the raw data to an image
    290     // Convert BGRA to ABGR
    291     int[] abgr = new int[info.height * info.width];
    292     for (int i = 0; i < abgr.length; i++) {
    293       abgr[i] = (
    294           (((int) info.buffer[i * 4 + 3] & 0xFF) << 24)
    295           + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16)
    296           + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8)
    297           + ((int) info.buffer[i * 4 + 2] & 0xFF));
    298     }
    299 
    300     BufferedImage bitmap = new BufferedImage(
    301         info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR);
    302     bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
    303     return bitmap;
    304   }
    305 
    306   @Override
    307   RegisteredNativeAllocation asRegisteredNativeAllocation() {
    308     if (!isInstanceOfClass("sun.misc.Cleaner")) {
    309       return null;
    310     }
    311 
    312     Value vthunk = getField("thunk");
    313     if (vthunk == null || !vthunk.isAhatInstance()) {
    314       return null;
    315     }
    316 
    317     AhatClassInstance thunk = vthunk.asAhatInstance().asClassInstance();
    318     if (thunk == null
    319         || !thunk.isInstanceOfClass("libcore.util.NativeAllocationRegistry$CleanerThunk")) {
    320       return null;
    321     }
    322 
    323     Value vregistry = thunk.getField("this$0");
    324     if (vregistry == null || !vregistry.isAhatInstance()) {
    325       return null;
    326     }
    327 
    328     AhatClassInstance registry = vregistry.asAhatInstance().asClassInstance();
    329     if (registry == null || !registry.isInstanceOfClass("libcore.util.NativeAllocationRegistry")) {
    330       return null;
    331     }
    332 
    333     Value size = registry.getField("size");
    334     if (!size.isLong()) {
    335       return null;
    336     }
    337 
    338     Value referent = getField("referent");
    339     if (referent == null || !referent.isAhatInstance()) {
    340       return null;
    341     }
    342 
    343     RegisteredNativeAllocation rna = new RegisteredNativeAllocation();
    344     rna.referent = referent.asAhatInstance();
    345     rna.size = size.asLong();
    346     return rna;
    347   }
    348 
    349   private static class InstanceFieldIterator implements Iterable<FieldValue>,
    350                                                         Iterator<FieldValue> {
    351     // The complete list of instance field values to iterate over, including
    352     // superclass field values.
    353     private Value[] mValues;
    354     private int mValueIndex;
    355 
    356     // The list of field descriptors specific to the current class in the
    357     // class hierarchy, not including superclass field descriptors.
    358     // mFields and mFieldIndex are reset each time we walk up to the next
    359     // superclass in the call hierarchy.
    360     private Field[] mFields;
    361     private int mFieldIndex;
    362     private AhatClassObj mNextClassObj;
    363 
    364     public InstanceFieldIterator(Value[] values, AhatClassObj classObj) {
    365       mValues = values;
    366       mFields = classObj.getInstanceFields();
    367       mValueIndex = 0;
    368       mFieldIndex = 0;
    369       mNextClassObj = classObj.getSuperClassObj();
    370     }
    371 
    372     @Override
    373     public boolean hasNext() {
    374       // If we have reached the end of the fields in the current class,
    375       // continue walking up the class hierarchy to get superclass fields as
    376       // well.
    377       while (mFieldIndex == mFields.length && mNextClassObj != null) {
    378         mFields = mNextClassObj.getInstanceFields();
    379         mFieldIndex = 0;
    380         mNextClassObj = mNextClassObj.getSuperClassObj();
    381       }
    382       return mFieldIndex < mFields.length;
    383     }
    384 
    385     @Override
    386     public FieldValue next() {
    387       if (!hasNext()) {
    388         throw new NoSuchElementException();
    389       }
    390       Field field = mFields[mFieldIndex++];
    391       Value value = mValues[mValueIndex++];
    392       return new FieldValue(field.name, field.type, value);
    393     }
    394 
    395     @Override
    396     public Iterator<FieldValue> iterator() {
    397       return this;
    398     }
    399   }
    400 
    401   /**
    402    * Returns the reachability type associated with this instance.
    403    * For example, returns Reachability.WEAK for an instance of
    404    * java.lang.ref.WeakReference.
    405    */
    406   private Reachability getJavaLangRefType() {
    407     AhatClassObj cls = getClassObj();
    408     while (cls != null) {
    409       switch (cls.getName()) {
    410         case "java.lang.ref.PhantomReference": return Reachability.PHANTOM;
    411         case "java.lang.ref.WeakReference": return Reachability.WEAK;
    412         case "java.lang.ref.FinalizerReference": return Reachability.FINALIZER;
    413         case "java.lang.ref.Finalizer": return Reachability.FINALIZER;
    414         case "java.lang.ref.SoftReference": return Reachability.SOFT;
    415       }
    416       cls = cls.getSuperClassObj();
    417     }
    418     return Reachability.STRONG;
    419   }
    420 
    421   /**
    422    * A Reference iterator that iterates over the fields of this instance.
    423    */
    424   private class ReferenceIterator implements Iterable<Reference>,
    425                                              Iterator<Reference> {
    426     private final Iterator<FieldValue> mIter = getInstanceFields().iterator();
    427     private Reference mNext = null;
    428 
    429     // If we are iterating over a subclass of java.lang.ref.Reference, the
    430     // 'referent' field doesn't have strong reachability. mJavaLangRefType
    431     // describes what type of java.lang.ref.Reference subinstance this is.
    432     private final Reachability mJavaLangRefType = getJavaLangRefType();
    433 
    434     @Override
    435     public boolean hasNext() {
    436       while (mNext == null && mIter.hasNext()) {
    437         FieldValue field = mIter.next();
    438         if (field.value != null && field.value.isAhatInstance()) {
    439           Reachability reachability = Reachability.STRONG;
    440           if (mJavaLangRefType != Reachability.STRONG && "referent".equals(field.name)) {
    441             reachability = mJavaLangRefType;
    442           }
    443           AhatInstance ref = field.value.asAhatInstance();
    444           mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, reachability);
    445         }
    446       }
    447       return mNext != null;
    448     }
    449 
    450     @Override
    451     public Reference next() {
    452       if (!hasNext()) {
    453         throw new NoSuchElementException();
    454       }
    455       Reference next = mNext;
    456       mNext = null;
    457       return next;
    458     }
    459 
    460     @Override
    461     public Iterator<Reference> iterator() {
    462       return this;
    463     }
    464   }
    465 }
    466