Home | History | Annotate | Download | only in vm
      1 /*
      2  * Copyright (C) 2008 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 /*
     18  * Allocation tracking and reporting.  We maintain a circular buffer with
     19  * the most recent allocations.  The data can be viewed through DDMS.
     20  *
     21  * There are two basic approaches: manage the buffer with atomic updates
     22  * and do a system-wide suspend when DDMS requests it, or protect all
     23  * accesses with a mutex.  The former is potentially more efficient, but
     24  * the latter is much simpler and more reliable.
     25  *
     26  * Ideally we'd just use the object heap allocation mutex to guard this
     27  * structure, but at the point we grab that (under dvmMalloc()) we're just
     28  * allocating a collection of bytes and no longer have the class reference.
     29  * Because this is an optional feature it's best to leave the existing
     30  * code undisturbed and just use an additional lock.
     31  *
     32  * We don't currently track allocations of class objects.  We could, but
     33  * with the possible exception of Proxy objects they're not that interesting.
     34  *
     35  * TODO: if we add support for class unloading, we need to add the class
     36  * references here to the root set (or just disable class unloading while
     37  * this is active).
     38  *
     39  * TODO: consider making the parameters configurable, so DDMS can decide
     40  * how many allocations it wants to see and what the stack depth should be.
     41  * Changing the window size is easy, changing the max stack depth is harder
     42  * because we go from an array of fixed-size structs to variable-sized data.
     43  */
     44 #include "Dalvik.h"
     45 
     46 #ifdef HAVE_ANDROID_OS
     47 #include "cutils/properties.h"
     48 static bool isPowerOfTwo(int x) { return (x & (x - 1)) == 0; }
     49 #endif
     50 
     51 #define kMaxAllocRecordStackDepth   16      /* max 255 */
     52 
     53 #define kDefaultNumAllocRecords 64*1024 /* MUST be power of 2 */
     54 
     55 /*
     56  * Record the details of an allocation.
     57  */
     58 struct AllocRecord {
     59     ClassObject*    clazz;      /* class allocated in this block */
     60     u4              size;       /* total size requested */
     61     u2              threadId;   /* simple thread ID; could be recycled */
     62 
     63     /* stack trace elements; unused entries have method==NULL */
     64     struct {
     65         const Method* method;   /* which method we're executing in */
     66         int         pc;         /* current execution offset, in 16-bit units */
     67     } stackElem[kMaxAllocRecordStackDepth];
     68 };
     69 
     70 /*
     71  * Initialize a few things.  This gets called early, so keep activity to
     72  * a minimum.
     73  */
     74 bool dvmAllocTrackerStartup()
     75 {
     76     /* prep locks */
     77     dvmInitMutex(&gDvm.allocTrackerLock);
     78 
     79     /* initialized when enabled by DDMS */
     80     assert(gDvm.allocRecords == NULL);
     81 
     82     return true;
     83 }
     84 
     85 /*
     86  * Release anything we're holding on to.
     87  */
     88 void dvmAllocTrackerShutdown()
     89 {
     90     free(gDvm.allocRecords);
     91     dvmDestroyMutex(&gDvm.allocTrackerLock);
     92 }
     93 
     94 
     95 /*
     96  * ===========================================================================
     97  *      Collection
     98  * ===========================================================================
     99  */
    100 
    101 static int getAllocRecordMax() {
    102 #ifdef HAVE_ANDROID_OS
    103     // Check whether there's a system property overriding the number of records.
    104     const char* propertyName = "dalvik.vm.allocTrackerMax";
    105     char allocRecordMaxString[PROPERTY_VALUE_MAX];
    106     if (property_get(propertyName, allocRecordMaxString, "") > 0) {
    107         char* end;
    108         size_t value = strtoul(allocRecordMaxString, &end, 10);
    109         if (*end != '\0') {
    110             ALOGE("Ignoring %s '%s' --- invalid", propertyName, allocRecordMaxString);
    111             return kDefaultNumAllocRecords;
    112         }
    113         if (!isPowerOfTwo(value)) {
    114             ALOGE("Ignoring %s '%s' --- not power of two", propertyName, allocRecordMaxString);
    115             return kDefaultNumAllocRecords;
    116         }
    117         return value;
    118     }
    119 #endif
    120     return kDefaultNumAllocRecords;
    121 }
    122 
    123 /*
    124  * Enable allocation tracking.  Does nothing if tracking is already enabled.
    125  *
    126  * Returns "true" on success.
    127  */
    128 bool dvmEnableAllocTracker()
    129 {
    130     bool result = true;
    131     dvmLockMutex(&gDvm.allocTrackerLock);
    132 
    133     if (gDvm.allocRecords == NULL) {
    134         gDvm.allocRecordMax = getAllocRecordMax();
    135 
    136         ALOGI("Enabling alloc tracker (%d entries, %d frames --> %d bytes)",
    137               gDvm.allocRecordMax, kMaxAllocRecordStackDepth,
    138               sizeof(AllocRecord) * gDvm.allocRecordMax);
    139         gDvm.allocRecordHead = gDvm.allocRecordCount = 0;
    140         gDvm.allocRecords = (AllocRecord*) malloc(sizeof(AllocRecord) * gDvm.allocRecordMax);
    141 
    142         if (gDvm.allocRecords == NULL)
    143             result = false;
    144     }
    145 
    146     dvmUnlockMutex(&gDvm.allocTrackerLock);
    147     return result;
    148 }
    149 
    150 /*
    151  * Disable allocation tracking.  Does nothing if tracking is not enabled.
    152  */
    153 void dvmDisableAllocTracker()
    154 {
    155     dvmLockMutex(&gDvm.allocTrackerLock);
    156 
    157     if (gDvm.allocRecords != NULL) {
    158         free(gDvm.allocRecords);
    159         gDvm.allocRecords = NULL;
    160     }
    161 
    162     dvmUnlockMutex(&gDvm.allocTrackerLock);
    163 }
    164 
    165 /*
    166  * Get the last few stack frames.
    167  */
    168 static void getStackFrames(Thread* self, AllocRecord* pRec)
    169 {
    170     int stackDepth = 0;
    171     void* fp;
    172 
    173     fp = self->interpSave.curFrame;
    174 
    175     while ((fp != NULL) && (stackDepth < kMaxAllocRecordStackDepth)) {
    176         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
    177         const Method* method = saveArea->method;
    178 
    179         if (!dvmIsBreakFrame((u4*) fp)) {
    180             pRec->stackElem[stackDepth].method = method;
    181             if (dvmIsNativeMethod(method)) {
    182                 pRec->stackElem[stackDepth].pc = 0;
    183             } else {
    184                 assert(saveArea->xtra.currentPc >= method->insns &&
    185                         saveArea->xtra.currentPc <
    186                         method->insns + dvmGetMethodInsnsSize(method));
    187                 pRec->stackElem[stackDepth].pc =
    188                     (int) (saveArea->xtra.currentPc - method->insns);
    189             }
    190             stackDepth++;
    191         }
    192 
    193         assert(fp != saveArea->prevFrame);
    194         fp = saveArea->prevFrame;
    195     }
    196 
    197     /* clear out the rest (normally there won't be any) */
    198     while (stackDepth < kMaxAllocRecordStackDepth) {
    199         pRec->stackElem[stackDepth].method = NULL;
    200         pRec->stackElem[stackDepth].pc = 0;
    201         stackDepth++;
    202     }
    203 }
    204 
    205 /*
    206  * Add a new allocation to the set.
    207  */
    208 void dvmDoTrackAllocation(ClassObject* clazz, size_t size)
    209 {
    210     Thread* self = dvmThreadSelf();
    211     if (self == NULL) {
    212         ALOGW("alloc tracker: no thread");
    213         return;
    214     }
    215 
    216     dvmLockMutex(&gDvm.allocTrackerLock);
    217     if (gDvm.allocRecords == NULL) {
    218         dvmUnlockMutex(&gDvm.allocTrackerLock);
    219         return;
    220     }
    221 
    222     /* advance and clip */
    223     if (++gDvm.allocRecordHead == gDvm.allocRecordMax)
    224         gDvm.allocRecordHead = 0;
    225 
    226     AllocRecord* pRec = &gDvm.allocRecords[gDvm.allocRecordHead];
    227 
    228     pRec->clazz = clazz;
    229     pRec->size = size;
    230     pRec->threadId = self->threadId;
    231     getStackFrames(self, pRec);
    232 
    233     if (gDvm.allocRecordCount < gDvm.allocRecordMax)
    234         gDvm.allocRecordCount++;
    235 
    236     dvmUnlockMutex(&gDvm.allocTrackerLock);
    237 }
    238 
    239 
    240 /*
    241  * ===========================================================================
    242  *      Reporting
    243  * ===========================================================================
    244  */
    245 
    246 /*
    247 The data we send to DDMS contains everything we have recorded.
    248 
    249 Message header (all values big-endian):
    250   (1b) message header len (to allow future expansion); includes itself
    251   (1b) entry header len
    252   (1b) stack frame len
    253   (2b) number of entries
    254   (4b) offset to string table from start of message
    255   (2b) number of class name strings
    256   (2b) number of method name strings
    257   (2b) number of source file name strings
    258   For each entry:
    259     (4b) total allocation size
    260     (2b) threadId
    261     (2b) allocated object's class name index
    262     (1b) stack depth
    263     For each stack frame:
    264       (2b) method's class name
    265       (2b) method name
    266       (2b) method source file
    267       (2b) line number, clipped to 32767; -2 if native; -1 if no source
    268   (xb) class name strings
    269   (xb) method name strings
    270   (xb) source file strings
    271 
    272   As with other DDM traffic, strings are sent as a 4-byte length
    273   followed by UTF-16 data.
    274 
    275 We send up 16-bit unsigned indexes into string tables.  In theory there
    276 can be (kMaxAllocRecordStackDepth * gDvm.allocRecordMax) unique strings in
    277 each table, but in practice there should be far fewer.
    278 
    279 The chief reason for using a string table here is to keep the size of
    280 the DDMS message to a minimum.  This is partly to make the protocol
    281 efficient, but also because we have to form the whole thing up all at
    282 once in a memory buffer.
    283 
    284 We use separate string tables for class names, method names, and source
    285 files to keep the indexes small.  There will generally be no overlap
    286 between the contents of these tables.
    287 */
    288 const int kMessageHeaderLen = 15;
    289 const int kEntryHeaderLen = 9;
    290 const int kStackFrameLen = 8;
    291 
    292 /*
    293  * Return the index of the head element.
    294  *
    295  * We point at the most-recently-written record, so if allocRecordCount is 1
    296  * we want to use the current element.  Take "head+1" and subtract count
    297  * from it.
    298  *
    299  * We need to handle underflow in our circular buffer, so we add
    300  * gDvm.allocRecordMax and then mask it back down.
    301  */
    302 inline static int headIndex()
    303 {
    304     return (gDvm.allocRecordHead+1 + gDvm.allocRecordMax - gDvm.allocRecordCount)
    305             & (gDvm.allocRecordMax-1);
    306 }
    307 
    308 /*
    309  * Dump the contents of a PointerSet full of character pointers.
    310  */
    311 static void dumpStringTable(PointerSet* strings)
    312 {
    313     int count = dvmPointerSetGetCount(strings);
    314     int i;
    315 
    316     for (i = 0; i < count; i++)
    317         printf("  %s\n", (const char*) dvmPointerSetGetEntry(strings, i));
    318 }
    319 
    320 /*
    321  * Get the method's source file.  If we don't know it, return "" instead
    322  * of a NULL pointer.
    323  */
    324 static const char* getMethodSourceFile(const Method* method)
    325 {
    326     const char* fileName = dvmGetMethodSourceFile(method);
    327     if (fileName == NULL)
    328         fileName = "";
    329     return fileName;
    330 }
    331 
    332 /*
    333  * Generate string tables.
    334  *
    335  * Our source material is UTF-8 string constants from DEX files.  If we
    336  * want to be thorough we can generate a hash value for each string and
    337  * use the VM hash table implementation, or we can do a quick & dirty job
    338  * by just maintaining a list of unique pointers.  If the same string
    339  * constant appears in multiple DEX files we'll end up with duplicates,
    340  * but in practice this shouldn't matter (and if it does, we can uniq-sort
    341  * the result in a second pass).
    342  */
    343 static bool populateStringTables(PointerSet* classNames,
    344     PointerSet* methodNames, PointerSet* fileNames)
    345 {
    346     int count = gDvm.allocRecordCount;
    347     int idx = headIndex();
    348     int classCount, methodCount, fileCount;         /* debug stats */
    349 
    350     classCount = methodCount = fileCount = 0;
    351 
    352     while (count--) {
    353         AllocRecord* pRec = &gDvm.allocRecords[idx];
    354 
    355         dvmPointerSetAddEntry(classNames, pRec->clazz->descriptor);
    356         classCount++;
    357 
    358         int i;
    359         for (i = 0; i < kMaxAllocRecordStackDepth; i++) {
    360             if (pRec->stackElem[i].method == NULL)
    361                 break;
    362 
    363             const Method* method = pRec->stackElem[i].method;
    364             dvmPointerSetAddEntry(classNames, method->clazz->descriptor);
    365             classCount++;
    366             dvmPointerSetAddEntry(methodNames, method->name);
    367             methodCount++;
    368             dvmPointerSetAddEntry(fileNames, getMethodSourceFile(method));
    369             fileCount++;
    370         }
    371 
    372         idx = (idx + 1) & (gDvm.allocRecordMax-1);
    373     }
    374 
    375     ALOGI("class %d/%d, method %d/%d, file %d/%d",
    376         dvmPointerSetGetCount(classNames), classCount,
    377         dvmPointerSetGetCount(methodNames), methodCount,
    378         dvmPointerSetGetCount(fileNames), fileCount);
    379 
    380     return true;
    381 }
    382 
    383 /*
    384  * Generate the base info (i.e. everything but the string tables).
    385  *
    386  * This should be called twice.  On the first call, "ptr" is NULL and
    387  * "baseLen" is zero.  The return value is used to allocate a buffer.
    388  * On the second call, "ptr" points to a data buffer, and "baseLen"
    389  * holds the value from the result of the first call.
    390  *
    391  * The size of the output data is returned.
    392  */
    393 static size_t generateBaseOutput(u1* ptr, size_t baseLen,
    394     const PointerSet* classNames, const PointerSet* methodNames,
    395     const PointerSet* fileNames)
    396 {
    397     u1* origPtr = ptr;
    398     int count = gDvm.allocRecordCount;
    399     int idx = headIndex();
    400 
    401     if (origPtr != NULL) {
    402         set1(&ptr[0], kMessageHeaderLen);
    403         set1(&ptr[1], kEntryHeaderLen);
    404         set1(&ptr[2], kStackFrameLen);
    405         set2BE(&ptr[3], count);
    406         set4BE(&ptr[5], baseLen);
    407         set2BE(&ptr[9], dvmPointerSetGetCount(classNames));
    408         set2BE(&ptr[11], dvmPointerSetGetCount(methodNames));
    409         set2BE(&ptr[13], dvmPointerSetGetCount(fileNames));
    410     }
    411     ptr += kMessageHeaderLen;
    412 
    413     while (count--) {
    414         AllocRecord* pRec = &gDvm.allocRecords[idx];
    415 
    416         /* compute depth */
    417         int  depth;
    418         for (depth = 0; depth < kMaxAllocRecordStackDepth; depth++) {
    419             if (pRec->stackElem[depth].method == NULL)
    420                 break;
    421         }
    422 
    423         /* output header */
    424         if (origPtr != NULL) {
    425             set4BE(&ptr[0], pRec->size);
    426             set2BE(&ptr[4], pRec->threadId);
    427             set2BE(&ptr[6],
    428                 dvmPointerSetFind(classNames, pRec->clazz->descriptor));
    429             set1(&ptr[8], depth);
    430         }
    431         ptr += kEntryHeaderLen;
    432 
    433         /* convert stack frames */
    434         int i;
    435         for (i = 0; i < depth; i++) {
    436             if (origPtr != NULL) {
    437                 const Method* method = pRec->stackElem[i].method;
    438                 int lineNum;
    439 
    440                 lineNum = dvmLineNumFromPC(method, pRec->stackElem[i].pc);
    441                 if (lineNum > 32767)
    442                     lineNum = 32767;
    443 
    444                 set2BE(&ptr[0], dvmPointerSetFind(classNames,
    445                         method->clazz->descriptor));
    446                 set2BE(&ptr[2], dvmPointerSetFind(methodNames,
    447                         method->name));
    448                 set2BE(&ptr[4], dvmPointerSetFind(fileNames,
    449                         getMethodSourceFile(method)));
    450                 set2BE(&ptr[6], (u2)lineNum);
    451             }
    452             ptr += kStackFrameLen;
    453         }
    454 
    455         idx = (idx + 1) & (gDvm.allocRecordMax-1);
    456     }
    457 
    458     return ptr - origPtr;
    459 }
    460 
    461 /*
    462  * Compute the size required to store a string table.  Includes the length
    463  * word and conversion to UTF-16.
    464  */
    465 static size_t computeStringTableSize(PointerSet* strings)
    466 {
    467     int count = dvmPointerSetGetCount(strings);
    468     size_t size = 0;
    469     int i;
    470 
    471     for (i = 0; i < count; i++) {
    472         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
    473 
    474         size += 4 + dvmUtf8Len(str) * 2;
    475     }
    476 
    477     return size;
    478 }
    479 
    480 /*
    481  * Convert a UTF-8 string to UTF-16.  We also need to byte-swap the values
    482  * to big-endian, and we can't assume even alignment on the target.
    483  *
    484  * Returns the string's length, in characters.
    485  */
    486 static int convertUtf8ToUtf16BEUA(u1* utf16Str, const char* utf8Str)
    487 {
    488     u1* origUtf16Str = utf16Str;
    489 
    490     while (*utf8Str != '\0') {
    491         u2 utf16 = dexGetUtf16FromUtf8(&utf8Str);       /* advances utf8Str */
    492         set2BE(utf16Str, utf16);
    493         utf16Str += 2;
    494     }
    495 
    496     return (utf16Str - origUtf16Str) / 2;
    497 }
    498 
    499 /*
    500  * Output a string table serially.
    501  */
    502 static size_t outputStringTable(PointerSet* strings, u1* ptr)
    503 {
    504     int count = dvmPointerSetGetCount(strings);
    505     u1* origPtr = ptr;
    506     int i;
    507 
    508     for (i = 0; i < count; i++) {
    509         const char* str = (const char*) dvmPointerSetGetEntry(strings, i);
    510         int charLen;
    511 
    512         /* copy UTF-8 string to big-endian unaligned UTF-16 */
    513         charLen = convertUtf8ToUtf16BEUA(&ptr[4], str);
    514         set4BE(&ptr[0], charLen);
    515 
    516         ptr += 4 + charLen * 2;
    517     }
    518 
    519     return ptr - origPtr;
    520 }
    521 
    522 /*
    523  * Generate a DDM packet with all of the tracked allocation data.
    524  *
    525  * On success, returns "true" with "*pData" and "*pDataLen" set.
    526  */
    527 bool dvmGenerateTrackedAllocationReport(u1** pData, size_t* pDataLen)
    528 {
    529     bool result = false;
    530     u1* buffer = NULL;
    531 
    532     dvmLockMutex(&gDvm.allocTrackerLock);
    533 
    534     /*
    535      * Part 1: generate string tables.
    536      */
    537     PointerSet* classNames = NULL;
    538     PointerSet* methodNames = NULL;
    539     PointerSet* fileNames = NULL;
    540 
    541     /*
    542      * Allocate storage.  Usually there's 60-120 of each thing (sampled
    543      * when max=512), but it varies widely and isn't closely bound to
    544      * the number of allocations we've captured.  The sets expand quickly
    545      * if needed.
    546      */
    547     classNames = dvmPointerSetAlloc(128);
    548     methodNames = dvmPointerSetAlloc(128);
    549     fileNames = dvmPointerSetAlloc(128);
    550     if (classNames == NULL || methodNames == NULL || fileNames == NULL) {
    551         ALOGE("Failed allocating pointer sets");
    552         goto bail;
    553     }
    554 
    555     if (!populateStringTables(classNames, methodNames, fileNames))
    556         goto bail;
    557 
    558     if (false) {
    559         printf("Classes:\n");
    560         dumpStringTable(classNames);
    561         printf("Methods:\n");
    562         dumpStringTable(methodNames);
    563         printf("Files:\n");
    564         dumpStringTable(fileNames);
    565     }
    566 
    567     /*
    568      * Part 2: compute the size of the output.
    569      *
    570      * (Could also just write to an expanding buffer.)
    571      */
    572     size_t baseSize, totalSize;
    573     baseSize = generateBaseOutput(NULL, 0, classNames, methodNames, fileNames);
    574     assert(baseSize > 0);
    575     totalSize = baseSize;
    576     totalSize += computeStringTableSize(classNames);
    577     totalSize += computeStringTableSize(methodNames);
    578     totalSize += computeStringTableSize(fileNames);
    579     ALOGI("Generated AT, size is %zd/%zd", baseSize, totalSize);
    580 
    581     /*
    582      * Part 3: allocate a buffer and generate the output.
    583      */
    584     u1* strPtr;
    585 
    586     buffer = (u1*) malloc(totalSize);
    587     strPtr = buffer + baseSize;
    588     generateBaseOutput(buffer, baseSize, classNames, methodNames, fileNames);
    589     strPtr += outputStringTable(classNames, strPtr);
    590     strPtr += outputStringTable(methodNames, strPtr);
    591     strPtr += outputStringTable(fileNames, strPtr);
    592     if (strPtr - buffer != (int)totalSize) {
    593         ALOGE("size mismatch (%d vs %zd)", strPtr - buffer, totalSize);
    594         dvmAbort();
    595     }
    596     //dvmPrintHexDump(buffer, totalSize);
    597 
    598     *pData = buffer;
    599     *pDataLen = totalSize;
    600     buffer = NULL;          // don't free -- caller will own
    601     result = true;
    602 
    603 bail:
    604     dvmPointerSetFree(classNames);
    605     dvmPointerSetFree(methodNames);
    606     dvmPointerSetFree(fileNames);
    607     free(buffer);
    608     dvmUnlockMutex(&gDvm.allocTrackerLock);
    609     //dvmDumpTrackedAllocations(false);
    610     return result;
    611 }
    612 
    613 /*
    614  * Dump the tracked allocations to the log file.
    615  *
    616  * If "enable" is set, we try to enable the feature if it's not already
    617  * active.
    618  */
    619 void dvmDumpTrackedAllocations(bool enable)
    620 {
    621     if (enable)
    622         dvmEnableAllocTracker();
    623 
    624     dvmLockMutex(&gDvm.allocTrackerLock);
    625     if (gDvm.allocRecords == NULL) {
    626         dvmUnlockMutex(&gDvm.allocTrackerLock);
    627         return;
    628     }
    629 
    630     /*
    631      * "idx" is the head of the list.  We want to start at the end of the
    632      * list and move forward to the tail.
    633      */
    634     int idx = headIndex();
    635     int count = gDvm.allocRecordCount;
    636 
    637     ALOGI("Tracked allocations, (head=%d count=%d)",
    638         gDvm.allocRecordHead, count);
    639     while (count--) {
    640         AllocRecord* pRec = &gDvm.allocRecords[idx];
    641         ALOGI(" T=%-2d %6d %s",
    642             pRec->threadId, pRec->size, pRec->clazz->descriptor);
    643 
    644         if (true) {
    645             for (int i = 0; i < kMaxAllocRecordStackDepth; i++) {
    646                 if (pRec->stackElem[i].method == NULL)
    647                     break;
    648 
    649                 const Method* method = pRec->stackElem[i].method;
    650                 if (dvmIsNativeMethod(method)) {
    651                     ALOGI("    %s.%s (Native)",
    652                         method->clazz->descriptor, method->name);
    653                 } else {
    654                     ALOGI("    %s.%s +%d",
    655                         method->clazz->descriptor, method->name,
    656                         pRec->stackElem[i].pc);
    657                 }
    658             }
    659         }
    660 
    661         /* pause periodically to help logcat catch up */
    662         if ((count % 5) == 0)
    663             usleep(40000);
    664 
    665         idx = (idx + 1) & (gDvm.allocRecordMax-1);
    666     }
    667 
    668     dvmUnlockMutex(&gDvm.allocTrackerLock);
    669     if (false) {
    670         u1* data;
    671         size_t dataLen;
    672         if (dvmGenerateTrackedAllocationReport(&data, &dataLen))
    673             free(data);
    674     }
    675 }
    676