• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      return new ReferenceIterator();
108    }
109  
asString(int maxChars)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  
getReferent()131    @Override public AhatInstance getReferent() {
132      if (isInstanceOfClass("java.lang.ref.Reference")) {
133        return getRefField("referent");
134      }
135      return null;
136    }
137  
getDexCacheLocation(int maxChars)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  
getBinderProxyInterfaceName()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  
getBinderTokenDescriptor()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  
getBinderStubInterfaceName()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  
getAssociatedBitmapInstance()194    @Override public AhatInstance getAssociatedBitmapInstance() {
195      return getBitmapInfo() == null ? null : this;
196    }
197  
isClassInstance()198    @Override public boolean isClassInstance() {
199      return true;
200    }
201  
asClassInstance()202    @Override public AhatClassInstance asClassInstance() {
203      return this;
204    }
205  
toString()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     */
getBinderDescriptor()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     */
getByteArrayField(String fieldName)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  
BitmapInfo(int width, int height, byte[] buffer)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     */
getBitmapInfo()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  
asBitmap()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
asRegisteredNativeAllocation()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  
InstanceFieldIterator(Value[] values, AhatClassObj classObj)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
hasNext()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
next()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
iterator()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     */
getJavaLangRefType()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
hasNext()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
next()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
iterator()461      public Iterator<Reference> iterator() {
462        return this;
463      }
464    }
465  }
466