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.nio.charset.StandardCharsets; 20 import java.util.AbstractList; 21 import java.util.Collections; 22 import java.util.List; 23 24 /** 25 * An array instance from a parsed heap dump. 26 * It is used for both object and primitive arrays. The class provides methods 27 * for accessing the length and elements of the array in addition to those 28 * methods inherited from {@link AhatInstance}. 29 */ 30 public class AhatArrayInstance extends AhatInstance { 31 // To save space, we store arrays as primitive arrays or AhatInstance arrays 32 // and provide a wrapper over the arrays to expose a list of Values. 33 // This is especially important for large byte arrays, such as bitmaps. 34 // We keep a separate pointer to the underlying array in the case of byte or 35 // char arrays because they are sometimes useful to have. 36 // TODO: Have different subtypes of AhatArrayInstance to avoid the overhead 37 // of these extra pointers and cost in getReferences when the array type is 38 // not relevant? 39 private List<Value> mValues; 40 private byte[] mByteArray; // null if not a byte array. 41 private char[] mCharArray; // null if not a char array. 42 private final int mRefSize; 43 AhatArrayInstance(long id, int refSize)44 AhatArrayInstance(long id, int refSize) { 45 super(id); 46 mRefSize = refSize; 47 } 48 49 /** 50 * Initialize the array elements for a primitive boolean array. 51 */ initialize(final boolean[] bools)52 void initialize(final boolean[] bools) { 53 mValues = new AbstractList<Value>() { 54 @Override public int size() { 55 return bools.length; 56 } 57 58 @Override public Value get(int index) { 59 return Value.pack(bools[index]); 60 } 61 }; 62 } 63 64 /** 65 * Initialize the array elements for a primitive char array. 66 */ initialize(final char[] chars)67 void initialize(final char[] chars) { 68 mCharArray = chars; 69 mValues = new AbstractList<Value>() { 70 @Override public int size() { 71 return chars.length; 72 } 73 74 @Override public Value get(int index) { 75 return Value.pack(chars[index]); 76 } 77 }; 78 } 79 80 /** 81 * Initialize the array elements for a primitive float array. 82 */ initialize(final float[] floats)83 void initialize(final float[] floats) { 84 mValues = new AbstractList<Value>() { 85 @Override public int size() { 86 return floats.length; 87 } 88 89 @Override public Value get(int index) { 90 return Value.pack(floats[index]); 91 } 92 }; 93 } 94 95 /** 96 * Initialize the array elements for a primitive double array. 97 */ initialize(final double[] doubles)98 void initialize(final double[] doubles) { 99 mValues = new AbstractList<Value>() { 100 @Override public int size() { 101 return doubles.length; 102 } 103 104 @Override public Value get(int index) { 105 return Value.pack(doubles[index]); 106 } 107 }; 108 } 109 110 /** 111 * Initialize the array elements for a primitive byte array. 112 */ initialize(final byte[] bytes)113 void initialize(final byte[] bytes) { 114 mByteArray = bytes; 115 mValues = new AbstractList<Value>() { 116 @Override public int size() { 117 return bytes.length; 118 } 119 120 @Override public Value get(int index) { 121 return Value.pack(bytes[index]); 122 } 123 }; 124 } 125 126 /** 127 * Initialize the array elements for a primitive short array. 128 */ initialize(final short[] shorts)129 void initialize(final short[] shorts) { 130 mValues = new AbstractList<Value>() { 131 @Override public int size() { 132 return shorts.length; 133 } 134 135 @Override public Value get(int index) { 136 return Value.pack(shorts[index]); 137 } 138 }; 139 } 140 141 /** 142 * Initialize the array elements for a primitive int array. 143 */ initialize(final int[] ints)144 void initialize(final int[] ints) { 145 mValues = new AbstractList<Value>() { 146 @Override public int size() { 147 return ints.length; 148 } 149 150 @Override public Value get(int index) { 151 return Value.pack(ints[index]); 152 } 153 }; 154 } 155 156 /** 157 * Initialize the array elements for a primitive long array. 158 */ initialize(final long[] longs)159 void initialize(final long[] longs) { 160 mValues = new AbstractList<Value>() { 161 @Override public int size() { 162 return longs.length; 163 } 164 165 @Override public Value get(int index) { 166 return Value.pack(longs[index]); 167 } 168 }; 169 } 170 171 /** 172 * Initialize the array elements for an instance array. 173 */ initialize(final AhatInstance[] insts)174 void initialize(final AhatInstance[] insts) { 175 mValues = new AbstractList<Value>() { 176 @Override public int size() { 177 return insts.length; 178 } 179 180 @Override public Value get(int index) { 181 return Value.pack(insts[index]); 182 } 183 }; 184 } 185 186 @Override getExtraJavaSize()187 long getExtraJavaSize() { 188 int length = getLength(); 189 if (length == 0) { 190 return 0; 191 } 192 193 return Value.getType(mValues.get(0)).size(mRefSize) * getLength(); 194 } 195 196 /** 197 * Returns the number of elements in the array. 198 * 199 * @return number of elements in the array. 200 */ getLength()201 public int getLength() { 202 return mValues.size(); 203 } 204 205 /** 206 * Returns a list of all of the array's elements in order. 207 * The returned list does not support modification. 208 * 209 * @return list of the array's elements. 210 */ getValues()211 public List<Value> getValues() { 212 return mValues; 213 } 214 215 /** 216 * Returns the value at the given index of this array. 217 * 218 * @param index the index of the value to retrieve 219 * @return the value at the given index 220 * @throws IndexOutOfBoundsException if the index is out of range 221 */ getValue(int index)222 public Value getValue(int index) { 223 return mValues.get(index); 224 } 225 226 @Override getReferences()227 Iterable<Reference> getReferences() { 228 // The list of references will be empty if this is a primitive array. 229 List<Reference> refs = Collections.emptyList(); 230 if (!mValues.isEmpty()) { 231 Value first = mValues.get(0); 232 if (first == null || first.isAhatInstance()) { 233 refs = new AbstractList<Reference>() { 234 @Override 235 public int size() { 236 return mValues.size(); 237 } 238 239 @Override 240 public Reference get(int index) { 241 Value value = mValues.get(index); 242 if (value != null) { 243 assert value.isAhatInstance(); 244 String field = "[" + Integer.toString(index) + "]"; 245 return new Reference(AhatArrayInstance.this, 246 field, 247 value.asAhatInstance(), 248 Reachability.STRONG); 249 } 250 return null; 251 } 252 }; 253 } 254 } 255 return new SkipNullsIterator(refs); 256 } 257 isArrayInstance()258 @Override public boolean isArrayInstance() { 259 return true; 260 } 261 asArrayInstance()262 @Override public AhatArrayInstance asArrayInstance() { 263 return this; 264 } 265 asString(int maxChars)266 @Override public String asString(int maxChars) { 267 return asString(0, getLength(), maxChars); 268 } 269 270 /** 271 * Returns the String value associated with this array. 272 * Only char arrays are considered as having an associated String value. 273 */ asString(int offset, int count, int maxChars)274 String asString(int offset, int count, int maxChars) { 275 if (mCharArray == null) { 276 return null; 277 } 278 279 if (count == 0) { 280 return ""; 281 } 282 int numChars = mCharArray.length; 283 if (0 <= maxChars && maxChars < count) { 284 count = maxChars; 285 } 286 287 int end = offset + count - 1; 288 if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { 289 return new String(mCharArray, offset, count); 290 } 291 return null; 292 } 293 294 /** 295 * Returns the ascii String value associated with this array. 296 * Only byte arrays are considered as having an associated ascii String value. 297 */ asAsciiString(int offset, int count, int maxChars)298 String asAsciiString(int offset, int count, int maxChars) { 299 if (mByteArray == null) { 300 return null; 301 } 302 303 if (count == 0) { 304 return ""; 305 } 306 int numChars = mByteArray.length; 307 if (0 <= maxChars && maxChars < count) { 308 count = maxChars; 309 } 310 311 int end = offset + count - 1; 312 if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) { 313 return new String(mByteArray, offset, count, StandardCharsets.US_ASCII); 314 } 315 return null; 316 } 317 318 /** 319 * Returns the String value associated with this array. Byte arrays are 320 * considered as ascii encoded strings. 321 */ asMaybeCompressedString(int offset, int count, int maxChars)322 String asMaybeCompressedString(int offset, int count, int maxChars) { 323 String str = asString(offset, count, maxChars); 324 if (str == null) { 325 str = asAsciiString(offset, count, maxChars); 326 } 327 return str; 328 } 329 getAssociatedBitmapInstance()330 @Override public AhatInstance getAssociatedBitmapInstance() { 331 if (mByteArray != null) { 332 List<AhatInstance> refs = getReverseReferences(); 333 if (refs.size() == 1) { 334 AhatInstance ref = refs.get(0); 335 return ref.getAssociatedBitmapInstance(); 336 } 337 } 338 return null; 339 } 340 getAssociatedClassForOverhead()341 @Override public AhatClassObj getAssociatedClassForOverhead() { 342 if (mByteArray != null) { 343 List<AhatInstance> refs = getHardReverseReferences(); 344 if (refs.size() == 1) { 345 AhatClassObj ref = refs.get(0).asClassObj(); 346 if (ref != null) { 347 for (FieldValue field : ref.getStaticFieldValues()) { 348 if (field.name.equals("$classOverhead")) { 349 if (field.value.asAhatInstance() == this) { 350 return ref; 351 } 352 return null; 353 } 354 } 355 } 356 } 357 } 358 return null; 359 } 360 toString()361 @Override public String toString() { 362 String className = getClassName(); 363 if (className.endsWith("[]")) { 364 className = className.substring(0, className.length() - 2); 365 } 366 return String.format("%s[%d]@%08x", className, mValues.size(), getId()); 367 } 368 asByteArray()369 byte[] asByteArray() { 370 return mByteArray; 371 } 372 } 373