/* * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2019 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. *
*/
// ------------------------- // | General Description | // ------------------------- // The CodeHeap state analytics are divided in two parts. // The first part examines the entire CodeHeap and aggregates all // information that is believed useful/important. // // Aggregation condenses the information of a piece of the CodeHeap // (4096 bytes by default) into an analysis granule. These granules // contain enough detail to gain initial insight while keeping the // internal structure sizes in check. // // The second part, which consists of several, independent steps, // prints the previously collected information with emphasis on // various aspects. // // The CodeHeap is a living thing. Therefore, protection against concurrent // modification (by acquiring the CodeCache_lock) is necessary. It has // to be provided by the caller of the analysis functions. // If the CodeCache_lock is not held, the analysis functions may print // less detailed information or may just do nothing. It is by intention // that an unprotected invocation is not abnormally terminated. // // Data collection and printing is done on an "on request" basis. // While no request is being processed, there is no impact on performance. // The CodeHeap state analytics do have some memory footprint. // The "aggregate" step allocates some data structures to hold the aggregated // information for later output. These data structures live until they are // explicitly discarded (function "discard") or until the VM terminates. // There is one exception: the function "all" does not leave any data // structures allocated. // // Requests for real-time, on-the-fly analysis can be issued via // jcmd <pid> Compiler.CodeHeap_Analytics [<function>] [<granularity>] // // If you are (only) interested in how the CodeHeap looks like after running // a sample workload, you can use the command line option // -XX:+PrintCodeHeapAnalytics // It will cause a full analysis to be written to tty. In addition, a full // analysis will be written the first time a "CodeCache full" condition is // detected. // // The command line option produces output identical to the jcmd function // jcmd <pid> Compiler.CodeHeap_Analytics all 4096 // ---------------------------------------------------------------------------------
// With this declaration macro, it is possible to switch between // - direct output into an argument-passed outputStream and // - buffered output into a bufferedStream with subsequent flush // of the filled buffer to the outputStream. #define USE_BUFFEREDSTREAM
// There are instances when composing an output line or a small set of // output lines out of many tty->print() calls creates significant overhead. // Writing to a bufferedStream buffer first has a significant advantage: // It uses noticeably less cpu cycles and reduces (when writing to a // network file) the required bandwidth by at least a factor of ten. Observed on MacOS. // That clearly makes up for the increased code complexity. // // Conversion of existing code is easy and straightforward, if the code already // uses a parameterized output destination, e.g. "outputStream st". // - rename the formal parameter to any other name, e.g. out_st. // - at a suitable place in your code, insert // BUFFEREDSTEAM_DECL(buf_st, out_st) // This will provide all the declarations necessary. After that, all // buf_st->print() (and the like) calls will be directed to a bufferedStream object. // Once a block of output (a line or a small set of lines) is composed, insert // BUFFEREDSTREAM_FLUSH(termstring) // to flush the bufferedStream to the final destination out_st. termstring is just // an arbitrary string (e.g. "\n") which is appended to the bufferedStream before // being written to out_st. Be aware that the last character written MUST be a '\n'. // Otherwise, buf_st->position() does not correspond to out_st->position() any longer. // BUFFEREDSTREAM_FLUSH_LOCKED(termstring) // does the same thing, protected by the ttyLocker lock. // BUFFEREDSTREAM_FLUSH_IF(termstring, remSize) // does a flush only if the remaining buffer space is less than remSize. // // To activate, #define USE_BUFFERED_STREAM before including this header. // If not activated, output will directly go to the originally used outputStream // with no additional overhead. // #ifdefined(USE_BUFFEREDSTREAM) // All necessary declarations to print via a bufferedStream // This macro must be placed before any other BUFFEREDSTREAM* // macro in the function. #define BUFFEREDSTREAM_DECL_SIZE(_anyst, _outst, _capa) \
ResourceMark _rm; \ /* _anyst name of the stream as used in the code */ \ /* _outst stream where final output will go to */ \ /* _capa allocated capacity of stream buffer */ \
size_t _nflush = 0; \
size_t _nforcedflush = 0; \
size_t _nsavedflush = 0; \
size_t _nlockedflush = 0; \
size_t _nflush_bytes = 0; \
size_t _capacity = _capa; \
bufferedStream _sstobj(_capa); \
bufferedStream* _sstbuf = &_sstobj; \
outputStream* _outbuf = _outst; \
bufferedStream* _anyst = &_sstobj; /* any stream. Use this to just print - no buffer flush. */
// Same as above, but with fixed buffer size. #define BUFFEREDSTREAM_DECL(_anyst, _outst) \
BUFFEREDSTREAM_DECL_SIZE(_anyst, _outst, 4*K);
// Flush the buffer contents unconditionally. // No action if the buffer is empty. #define BUFFEREDSTREAM_FLUSH(_termString) \ if (((_termString) != NULL) && (strlen(_termString) > 0)){\
_sstbuf->print("%s", _termString); \
} \ if (_sstbuf != _outbuf) { \ if (_sstbuf->size() != 0) { \
_nforcedflush++; _nflush_bytes += _sstbuf->size(); \
_outbuf->print("%s", _sstbuf->as_string()); \
_sstbuf->reset(); \
} \
}
// Flush the buffer contents if the remaining capacity is // less than the given threshold. #define BUFFEREDSTREAM_FLUSH_IF(_termString, _remSize) \ if (((_termString) != NULL) && (strlen(_termString) > 0)){\
_sstbuf->print("%s", _termString); \
} \ if (_sstbuf != _outbuf) { \ if ((_capacity - _sstbuf->size()) < (size_t)(_remSize)){\
_nflush++; _nforcedflush--; \
BUFFEREDSTREAM_FLUSH("") \
} else { \
_nsavedflush++; \
} \
}
// Flush the buffer contents if the remaining capacity is less // than the calculated threshold (256 bytes + capacity/16) // That should suffice for all reasonably sized output lines. #define BUFFEREDSTREAM_FLUSH_AUTO(_termString) \
BUFFEREDSTREAM_FLUSH_IF(_termString, 256+(_capacity>>4))
#define BUFFEREDSTREAM_FLUSH_LOCKED(_termString) \
{ ttyLocker ttyl;/* keep this output block together */ \
_nlockedflush++; \
BUFFEREDSTREAM_FLUSH(_termString) \
}
// Be prepared for ten different CodeHeap segments. Should be enough for a few years. constunsignedint nSizeDistElements = 31; // logarithmic range growth, max size: 2**32 constunsignedint maxTopSizeBlocks = 100; constunsignedint tsbStopper = 2 * maxTopSizeBlocks; constunsignedint maxHeaps = 10; staticunsignedint nHeaps = 0; staticstruct CodeHeapStat CodeHeapStatArray[maxHeaps];
// returns the index for the heap being processed. unsignedint CodeHeapState::findHeapIndex(outputStream* out, constchar* heapName) { if (heapName == NULL) { return maxHeaps;
} if (SegmentedCodeCache) { // Search for a pre-existing entry. If found, return that index. for (unsignedint i = 0; i < nHeaps; i++) { if (CodeHeapStatArray[i].heapName != NULL && strcmp(heapName, CodeHeapStatArray[i].heapName) == 0) { return i;
}
}
// check if there are more code heap segments than we can handle. if (nHeaps == maxHeaps) {
out->print_cr("Too many heap segments for current limit(%d).", maxHeaps); return maxHeaps;
}
// allocate new slot in StatArray.
CodeHeapStatArray[nHeaps].heapName = heapName; return nHeaps++;
} else {
nHeaps = 1;
CodeHeapStatArray[0].heapName = heapName; return 0; // This is the default index if CodeCache is not segmented.
}
}
//---< get a new statistics array >--- void CodeHeapState::prepare_StatArray(outputStream* out, size_t nElem, size_t granularity, constchar* heapName) { if (StatArray == NULL) {
StatArray = new StatElement[nElem]; //---< reset some counts >---
alloc_granules = nElem;
granule_size = granularity;
}
if (StatArray == NULL) { //---< just do nothing if allocation failed >---
out->print_cr("Statistics could not be collected for %s, probably out of memory.", heapName);
out->print_cr("Current granularity is " SIZE_FORMAT " bytes. Try a coarser granularity.", granularity);
alloc_granules = 0;
granule_size = 0;
} else { //---< initialize statistics array >---
memset((void*)StatArray, 0, nElem*sizeof(StatElement));
}
}
//---< get a new free block array >--- void CodeHeapState::prepare_FreeArray(outputStream* out, unsignedint nElem, constchar* heapName) { if (FreeArray == NULL) {
FreeArray = new FreeBlk[nElem]; //---< reset some counts >---
alloc_freeBlocks = nElem;
}
if (FreeArray == NULL) { //---< just do nothing if allocation failed >---
out->print_cr("Free space analysis cannot be done for %s, probably out of memory.", heapName);
alloc_freeBlocks = 0;
} else { //---< initialize free block array >---
memset((void*)FreeArray, 0, alloc_freeBlocks*sizeof(FreeBlk));
}
}
//---< get a new TopSizeArray >--- void CodeHeapState::prepare_TopSizeArray(outputStream* out, unsignedint nElem, constchar* heapName) { if (TopSizeArray == NULL) {
TopSizeArray = new TopSizeBlk[nElem]; //---< reset some counts >---
alloc_topSizeBlocks = nElem;
used_topSizeBlocks = 0;
}
if (TopSizeArray == NULL) { //---< just do nothing if allocation failed >---
out->print_cr("Top-%d list of largest CodeHeap blocks can not be collected for %s, probably out of memory.", nElem, heapName);
alloc_topSizeBlocks = 0;
} else { //---< initialize TopSizeArray >---
memset((void*)TopSizeArray, 0, nElem*sizeof(TopSizeBlk));
used_topSizeBlocks = 0;
}
}
//---< get a new SizeDistributionArray >--- void CodeHeapState::prepare_SizeDistArray(outputStream* out, unsignedint nElem, constchar* heapName) { if (SizeDistributionArray == NULL) {
SizeDistributionArray = new SizeDistributionElement[nElem];
}
if (SizeDistributionArray == NULL) { //---< just do nothing if allocation failed >---
out->print_cr("Size distribution can not be collected for %s, probably out of memory.", heapName);
} else { //---< initialize SizeDistArray >---
memset((void*)SizeDistributionArray, 0, nElem*sizeof(SizeDistributionElement)); // Logarithmic range growth. First range starts at _segment_size.
SizeDistributionArray[log2_seg_size-1].rangeEnd = 1U; for (unsignedint i = log2_seg_size; i < nElem; i++) {
SizeDistributionArray[i].rangeStart = 1U << (i - log2_seg_size);
SizeDistributionArray[i].rangeEnd = 1U << ((i+1) - log2_seg_size);
}
}
}
//---< get a new SizeDistributionArray >--- void CodeHeapState::update_SizeDistArray(outputStream* out, unsignedint len) { if (SizeDistributionArray != NULL) { for (unsignedint i = log2_seg_size-1; i < nSizeDistElements; i++) { if ((SizeDistributionArray[i].rangeStart <= len) && (len < SizeDistributionArray[i].rangeEnd)) {
SizeDistributionArray[i].lenSum += len;
SizeDistributionArray[i].count++; break;
}
}
}
}
// Discard all allocated internal data structures. // This should be done after an analysis session is completed. void CodeHeapState::discard(outputStream* out, CodeHeap* heap) { if (!initialization_complete) { return;
}
if (nHeaps > 0) { for (unsignedint ix = 0; ix < nHeaps; ix++) {
get_HeapStatGlobals(out, CodeHeapStatArray[ix].heapName);
discard_StatArray(out);
discard_FreeArray(out);
discard_TopSizeArray(out);
discard_SizeDistArray(out);
set_HeapStatGlobals(out, CodeHeapStatArray[ix].heapName);
CodeHeapStatArray[ix].heapName = NULL;
}
nHeaps = 0;
}
}
//---< max & min of TopSizeArray >--- // it is sufficient to have these sizes as 32bit unsigned ints. // The CodeHeap is limited in size to 4GB. Furthermore, the sizes // are stored in _segment_size units, scaling them down by a factor of 64 (at least). unsignedint currMax = 0; unsignedint currMin = 0; unsignedint currMin_ix = 0; unsignedlong total_iterations = 0;
bool done = false; constint min_granules = 256; constint max_granules = 512*K; // limits analyzable CodeHeap (with segment_granules) to 32M..128M // results in StatArray size of 24M (= max_granules * 48 Bytes per element) // For a 1GB CodeHeap, the granule size must be at least 2kB to not violate the max_granles limit. constchar* heapName = get_heapName(heap);
BUFFEREDSTREAM_DECL(ast, out)
if (!initialization_complete) {
memset(CodeHeapStatArray, 0, sizeof(CodeHeapStatArray));
initialization_complete = true;
printBox(ast, '=', "C O D E H E A P A N A L Y S I S (general remarks)", NULL);
ast->print_cr(" The code heap analysis function provides deep insights into\n" " the inner workings and the internal state of the Java VM's\n" " code cache - the place where all the JVM generated machine\n" " code is stored.\n" " \n" " This function is designed and provided for support engineers\n" " to help them understand and solve issues in customer systems.\n" " It is not intended for use and interpretation by other persons.\n" " \n");
BUFFEREDSTREAM_FLUSH("")
}
get_HeapStatGlobals(out, heapName);
// Since we are (and must be) analyzing the CodeHeap contents under the CodeCache_lock, // all heap information is "constant" and can be safely extracted/calculated before we // enter the while() loop. Actually, the loop will only be iterated once. char* low_bound = heap->low_boundary();
size_t size = heap->capacity();
size_t res_size = heap->max_capacity();
seg_size = heap->segment_size();
log2_seg_size = seg_size == 0 ? 0 : exact_log2(seg_size); // This is a global static value.
if (seg_size == 0) {
printBox(ast, '-', "Heap not fully initialized yet, segment size is zero for segment ", heapName);
BUFFEREDSTREAM_FLUSH("") return;
}
if (!holding_required_locks()) {
printBox(ast, '-', "Must be at safepoint or hold Compile_lock and CodeCache_lock when calling aggregate function for ", heapName);
BUFFEREDSTREAM_FLUSH("") return;
}
// Calculate granularity of analysis (and output). // The CodeHeap is managed (allocated) in segments (units) of CodeCacheSegmentSize. // The CodeHeap can become fairly large, in particular in productive real-life systems. // // It is often neither feasible nor desirable to aggregate the data with the highest possible // level of detail, i.e. inspecting and printing each segment on its own. // // The granularity parameter allows to specify the level of detail available in the analysis. // It must be a positive multiple of the segment size and should be selected such that enough // detail is provided while, at the same time, the printed output does not explode. // // By manipulating the granularity value, we enforce that at least min_granules units // of analysis are available. We also enforce an upper limit of max_granules units to // keep the amount of allocated storage in check. // // Finally, we adjust the granularity such that each granule covers at most 64k-1 segments. // This is necessary to prevent an unsigned short overflow while accumulating space information. //
assert(granularity > 0, "granularity should be positive.");
if (granularity > size) {
granularity = size;
} if (size/granularity < min_granules) {
granularity = size/min_granules; // at least min_granules granules
}
granularity = granularity & (~(seg_size - 1)); // must be multiple of seg_size if (granularity < seg_size) {
granularity = seg_size; // must be at least seg_size
} if (size/granularity > max_granules) {
granularity = size/max_granules; // at most max_granules granules
}
granularity = granularity & (~(seg_size - 1)); // must be multiple of seg_size if (granularity>>log2_seg_size >= (1L<<sizeof(unsignedshort)*8)) {
granularity = ((1L<<(sizeof(unsignedshort)*8))-1)<<log2_seg_size; // Limit: (64k-1) * seg_size
}
segment_granules = granularity == seg_size;
size_t granules = (size + (granularity-1))/granularity;
printBox(ast, '=', "C O D E H E A P A N A L Y S I S (used blocks) for segment ", heapName);
ast->print_cr(" The aggregate step takes an aggregated snapshot of the CodeHeap.\n" " Subsequent print functions create their output based on this snapshot.\n" " The CodeHeap is a living thing, and every effort has been made for the\n" " collected data to be consistent. Only the method names and signatures\n" " are retrieved at print time. That may lead to rare cases where the\n" " name of a method is no longer available, e.g. because it was unloaded.\n");
ast->print_cr(" CodeHeap committed size " SIZE_FORMAT "K (" SIZE_FORMAT "M), reserved size " SIZE_FORMAT "K (" SIZE_FORMAT "M), %d%% occupied.",
size/(size_t)K, size/(size_t)M, res_size/(size_t)K, res_size/(size_t)M, (unsignedint)(100.0*size/res_size));
ast->print_cr(" CodeHeap allocation segment size is " SIZE_FORMAT " bytes. This is the smallest possible granularity.", seg_size);
ast->print_cr(" CodeHeap (committed part) is mapped to " SIZE_FORMAT " granules of size " SIZE_FORMAT " bytes.", granules, granularity);
ast->print_cr(" Each granule takes " SIZE_FORMAT " bytes of C heap, that is " SIZE_FORMAT "K in total for statistics data.", sizeof(StatElement), (sizeof(StatElement)*granules)/(size_t)K);
ast->print_cr(" The number of granules is limited to %dk, requiring a granules size of at least %d bytes for a 1GB heap.", (unsignedint)(max_granules/K), (unsignedint)(G/max_granules));
BUFFEREDSTREAM_FLUSH("\n")
while (!done) { //---< reset counters with every aggregation >---
nBlocks_t1 = 0;
nBlocks_t2 = 0;
nBlocks_alive = 0;
nBlocks_stub = 0;
for (HeapBlock *h = heap->first_block(); h != NULL && !insane; h = heap->next_block(h)) { unsignedint hb_len = (unsignedint)h->length(); // despite being size_t, length can never overflow an unsigned int.
size_t hb_bytelen = ((size_t)hb_len)<<log2_seg_size; unsignedint ix_beg = (unsignedint)(((char*)h-low_bound)/granule_size); unsignedint ix_end = (unsignedint)(((char*)h-low_bound+(hb_bytelen-1))/granule_size); unsignedint compile_id = 0;
CompLevel comp_lvl = CompLevel_none;
compType cType = noComp;
blobType cbType = noType;
//---< some sanity checks >--- // Do not assert here, just check, print error message and return. // This is a diagnostic function. It is not supposed to tear down the VM. if ((char*)h < low_bound) {
insane = true; ast->print_cr("Sanity check: HeapBlock @%p below low bound (%p)", (char*)h, low_bound);
} if ((char*)h > (low_bound + res_size)) {
insane = true; ast->print_cr("Sanity check: HeapBlock @%p outside reserved range (%p)", (char*)h, low_bound + res_size);
} if ((char*)h > (low_bound + size)) {
insane = true; ast->print_cr("Sanity check: HeapBlock @%p outside used range (%p)", (char*)h, low_bound + size);
} if (ix_end >= granules) {
insane = true; ast->print_cr("Sanity check: end index (%d) out of bounds (" SIZE_FORMAT ")", ix_end, granules);
} if (size != heap->capacity()) {
insane = true; ast->print_cr("Sanity check: code heap capacity has changed (" SIZE_FORMAT "K to " SIZE_FORMAT "K)", size/(size_t)K, heap->capacity()/(size_t)K);
} if (ix_beg > ix_end) {
insane = true; ast->print_cr("Sanity check: end index (%d) lower than begin index (%d)", ix_end, ix_beg);
} if (insane) {
BUFFEREDSTREAM_FLUSH("") continue;
}
if (h->free()) {
nBlocks_free++;
freeSpace += hb_bytelen; if (hb_bytelen > maxFreeSize) {
maxFreeSize = hb_bytelen;
maxFreeBlock = h;
}
} else {
update_SizeDistArray(out, hb_len);
nBlocks_used++;
usedSpace += hb_bytelen;
CodeBlob* cb = (CodeBlob*)heap->find_start(h);
cbType = get_cbType(cb); // Will check for cb == NULL and other safety things. if (cbType != noType) { constchar* blob_name = nullptr; unsignedint nm_size = 0;
nmethod* nm = cb->as_nmethod_or_null(); if (nm != NULL) { // no is_readable check required, nm = (nmethod*)cb.
ResourceMark rm;
Method* method = nm->method(); if (nm->is_in_use() || nm->is_not_entrant()) {
blob_name = os::strdup(method->name_and_sig_as_C_string());
} else {
blob_name = os::strdup(cb->name());
}
nm_size = nm->total_size();
compile_id = nm->compile_id();
comp_lvl = (CompLevel)(nm->comp_level()); if (nm->is_compiled_by_c1()) {
cType = c1;
} if (nm->is_compiled_by_c2()) {
cType = c2;
} if (nm->is_compiled_by_jvmci()) {
cType = jvmci;
} switch (cbType) { case nMethod_inuse: { // only for executable methods!!! // space for these cbs is accounted for later.
n_methods++; break;
} case nMethod_notused:
nBlocks_alive++;
nBlocks_disconn++;
aliveSpace += hb_bytelen;
disconnSpace += hb_bytelen; break; case nMethod_notentrant: // equivalent to nMethod_alive
nBlocks_alive++;
nBlocks_notentr++;
aliveSpace += hb_bytelen;
notentrSpace += hb_bytelen; break; default: break;
}
} else {
blob_name = os::strdup(cb->name());
}
//------------------------------------------ //---< register block in TopSizeArray >--- //------------------------------------------ if (alloc_topSizeBlocks > 0) { if (used_topSizeBlocks == 0) {
TopSizeArray[0].start = h;
TopSizeArray[0].blob_name = blob_name;
TopSizeArray[0].len = hb_len;
TopSizeArray[0].index = tsbStopper;
TopSizeArray[0].nm_size = nm_size;
TopSizeArray[0].compiler = cType;
TopSizeArray[0].level = comp_lvl;
TopSizeArray[0].type = cbType;
currMax = hb_len;
currMin = hb_len;
currMin_ix = 0;
used_topSizeBlocks++;
blob_name = NULL; // indicate blob_name was consumed // This check roughly cuts 5000 iterations (JVM98, mixed, dbg, termination stats):
} elseif ((used_topSizeBlocks < alloc_topSizeBlocks) && (hb_len < currMin)) { //---< all blocks in list are larger, but there is room left in array >---
TopSizeArray[currMin_ix].index = used_topSizeBlocks;
TopSizeArray[used_topSizeBlocks].start = h;
TopSizeArray[used_topSizeBlocks].blob_name = blob_name;
TopSizeArray[used_topSizeBlocks].len = hb_len;
TopSizeArray[used_topSizeBlocks].index = tsbStopper;
TopSizeArray[used_topSizeBlocks].nm_size = nm_size;
TopSizeArray[used_topSizeBlocks].compiler = cType;
TopSizeArray[used_topSizeBlocks].level = comp_lvl;
TopSizeArray[used_topSizeBlocks].type = cbType;
currMin = hb_len;
currMin_ix = used_topSizeBlocks;
used_topSizeBlocks++;
blob_name = NULL; // indicate blob_name was consumed
} else { // This check cuts total_iterations by a factor of 6 (JVM98, mixed, dbg, termination stats): // We don't need to search the list if we know beforehand that the current block size is // smaller than the currently recorded minimum and there is no free entry left in the list. if (!((used_topSizeBlocks == alloc_topSizeBlocks) && (hb_len <= currMin))) { if (currMax < hb_len) {
currMax = hb_len;
} unsignedint i; unsignedint prev_i = tsbStopper; unsignedint limit_i = 0; for (i = 0; i != tsbStopper; i = TopSizeArray[i].index) { if (limit_i++ >= alloc_topSizeBlocks) {
insane = true; break; // emergency exit
} if (i >= used_topSizeBlocks) {
insane = true; break; // emergency exit
}
total_iterations++; if (TopSizeArray[i].len < hb_len) { //---< We want to insert here, element <i> is smaller than the current one >--- if (used_topSizeBlocks < alloc_topSizeBlocks) { // still room for a new entry to insert // old entry gets moved to the next free element of the array. // That's necessary to keep the entry for the largest block at index 0. // This move might cause the current minimum to be moved to another place if (i == currMin_ix) {
assert(TopSizeArray[i].len == currMin, "sort error");
currMin_ix = used_topSizeBlocks;
}
memcpy((void*)&TopSizeArray[used_topSizeBlocks], (void*)&TopSizeArray[i], sizeof(TopSizeBlk));
TopSizeArray[i].start = h;
TopSizeArray[i].blob_name = blob_name;
TopSizeArray[i].len = hb_len;
TopSizeArray[i].index = used_topSizeBlocks;
TopSizeArray[i].nm_size = nm_size;
TopSizeArray[i].compiler = cType;
TopSizeArray[i].level = comp_lvl;
TopSizeArray[i].type = cbType;
used_topSizeBlocks++;
blob_name = NULL; // indicate blob_name was consumed
} else { // no room for new entries, current block replaces entry for smallest block //---< Find last entry (entry for smallest remembered block) >--- // We either want to insert right before the smallest entry, which is when <i> // indexes the smallest entry. We then just overwrite the smallest entry. // What's more likely: // We want to insert somewhere in the list. The smallest entry (@<j>) then falls off the cliff. // The element at the insert point <i> takes it's slot. The second-smallest entry now becomes smallest. // Data of the current block is filled in at index <i>. unsignedint j = i; unsignedint prev_j = tsbStopper; unsignedint limit_j = 0; while (TopSizeArray[j].index != tsbStopper) { if (limit_j++ >= alloc_topSizeBlocks) {
insane = true; break; // emergency exit
} if (j >= used_topSizeBlocks) {
insane = true; break; // emergency exit
}
total_iterations++;
prev_j = j;
j = TopSizeArray[j].index;
} if (!insane) { if (TopSizeArray[j].blob_name != NULL) {
os::free((void*)TopSizeArray[j].blob_name);
} if (prev_j == tsbStopper) { //---< Above while loop did not iterate, we already are the min entry >--- //---< We have to just replace the smallest entry >---
currMin = hb_len;
currMin_ix = j;
TopSizeArray[j].start = h;
TopSizeArray[j].blob_name = blob_name;
TopSizeArray[j].len = hb_len;
TopSizeArray[j].index = tsbStopper; // already set!!
TopSizeArray[i].nm_size = nm_size;
TopSizeArray[j].compiler = cType;
TopSizeArray[j].level = comp_lvl;
TopSizeArray[j].type = cbType;
} else { //---< second-smallest entry is now smallest >---
TopSizeArray[prev_j].index = tsbStopper;
currMin = TopSizeArray[prev_j].len;
currMin_ix = prev_j; //---< previously smallest entry gets overwritten >---
memcpy((void*)&TopSizeArray[j], (void*)&TopSizeArray[i], sizeof(TopSizeBlk));
TopSizeArray[i].start = h;
TopSizeArray[i].blob_name = blob_name;
TopSizeArray[i].len = hb_len;
TopSizeArray[i].index = j;
TopSizeArray[i].nm_size = nm_size;
TopSizeArray[i].compiler = cType;
TopSizeArray[i].level = comp_lvl;
TopSizeArray[i].type = cbType;
}
blob_name = NULL; // indicate blob_name was consumed
} // insane
} break;
}
prev_i = i;
} if (insane) { // Note: regular analysis could probably continue by resetting "insane" flag.
out->print_cr("Possible loop in TopSizeBlocks list detected. Analysis aborted.");
discard_TopSizeArray(out);
}
}
}
} if (blob_name != NULL) {
os::free((void*)blob_name);
blob_name = NULL;
} //---------------------------------------------- //---< END register block in TopSizeArray >--- //----------------------------------------------
} else {
nBlocks_zomb++;
}
if (!insane) { // There is a risk for this block (because it contains many print statements) to get // interspersed with print data from other threads. We take this risk intentionally. // Getting stalled waiting for tty_lock while holding the CodeCache_lock is not desirable.
printBox(ast, '-', "Global CodeHeap statistics for segment ", heapName);
ast->print_cr("freeSpace = " SIZE_FORMAT_W(8) "k, nBlocks_free = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", freeSpace/(size_t)K, nBlocks_free, (100.0*freeSpace)/size, (100.0*freeSpace)/res_size);
ast->print_cr("usedSpace = " SIZE_FORMAT_W(8) "k, nBlocks_used = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", usedSpace/(size_t)K, nBlocks_used, (100.0*usedSpace)/size, (100.0*usedSpace)/res_size);
ast->print_cr(" Tier1 Space = " SIZE_FORMAT_W(8) "k, nBlocks_t1 = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", t1Space/(size_t)K, nBlocks_t1, (100.0*t1Space)/size, (100.0*t1Space)/res_size);
ast->print_cr(" Tier2 Space = " SIZE_FORMAT_W(8) "k, nBlocks_t2 = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", t2Space/(size_t)K, nBlocks_t2, (100.0*t2Space)/size, (100.0*t2Space)/res_size);
ast->print_cr(" Alive Space = " SIZE_FORMAT_W(8) "k, nBlocks_alive = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", aliveSpace/(size_t)K, nBlocks_alive, (100.0*aliveSpace)/size, (100.0*aliveSpace)/res_size);
ast->print_cr(" disconnected = " SIZE_FORMAT_W(8) "k, nBlocks_disconn = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", disconnSpace/(size_t)K, nBlocks_disconn, (100.0*disconnSpace)/size, (100.0*disconnSpace)/res_size);
ast->print_cr(" not entrant = " SIZE_FORMAT_W(8) "k, nBlocks_notentr = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", notentrSpace/(size_t)K, nBlocks_notentr, (100.0*notentrSpace)/size, (100.0*notentrSpace)/res_size);
ast->print_cr(" stubSpace = " SIZE_FORMAT_W(8) "k, nBlocks_stub = %6d, %10.3f%% of capacity, %10.3f%% of max_capacity", stubSpace/(size_t)K, nBlocks_stub, (100.0*stubSpace)/size, (100.0*stubSpace)/res_size);
ast->print_cr("ZombieBlocks = %8d. These are HeapBlocks which could not be identified as CodeBlobs.", nBlocks_zomb);
ast->cr();
ast->print_cr("Segment start = " INTPTR_FORMAT ", used space = " SIZE_FORMAT_W(8)"k", p2i(low_bound), size/K);
ast->print_cr("Segment end (used) = " INTPTR_FORMAT ", remaining space = " SIZE_FORMAT_W(8)"k", p2i(low_bound) + size, (res_size - size)/K);
ast->print_cr("Segment end (reserved) = " INTPTR_FORMAT ", reserved space = " SIZE_FORMAT_W(8)"k", p2i(low_bound) + res_size, res_size/K);
ast->cr();
ast->print_cr("latest allocated compilation id = %d", latest_compilation_id);
ast->print_cr("highest observed compilation id = %d", highest_compilation_id);
ast->print_cr("Building TopSizeList iterations = %ld", total_iterations);
BUFFEREDSTREAM_FLUSH("\n")
// This loop is intentionally printing directly to "out". // It should not print anything, anyway.
out->print("Verifying collected data...");
size_t granule_segs = granule_size>>log2_seg_size; for (unsignedint ix = 0; ix < granules; ix++) { if (StatArray[ix].t1_count > granule_segs) {
out->print_cr("t1_count[%d] = %d", ix, StatArray[ix].t1_count);
} if (StatArray[ix].t2_count > granule_segs) {
out->print_cr("t2_count[%d] = %d", ix, StatArray[ix].t2_count);
} if (StatArray[ix].tx_count > granule_segs) {
out->print_cr("tx_count[%d] = %d", ix, StatArray[ix].tx_count);
} if (StatArray[ix].stub_count > granule_segs) {
out->print_cr("stub_count[%d] = %d", ix, StatArray[ix].stub_count);
} if (StatArray[ix].t1_space > granule_segs) {
out->print_cr("t1_space[%d] = %d", ix, StatArray[ix].t1_space);
} if (StatArray[ix].t2_space > granule_segs) {
out->print_cr("t2_space[%d] = %d", ix, StatArray[ix].t2_space);
} if (StatArray[ix].tx_space > granule_segs) {
out->print_cr("tx_space[%d] = %d", ix, StatArray[ix].tx_space);
} if (StatArray[ix].stub_space > granule_segs) {
out->print_cr("stub_space[%d] = %d", ix, StatArray[ix].stub_space);
} // this cast is awful! I need it because NT/Intel reports a signed/unsigned mismatch. if ((size_t)(StatArray[ix].t1_count+StatArray[ix].t2_count+StatArray[ix].tx_count+StatArray[ix].stub_count) > granule_segs) {
out->print_cr("t1_count[%d] = %d, t2_count[%d] = %d, tx_count[%d] = %d, stub_count[%d] = %d", ix, StatArray[ix].t1_count, ix, StatArray[ix].t2_count, ix, StatArray[ix].tx_count, ix, StatArray[ix].stub_count);
} if ((size_t)(StatArray[ix].t1_space+StatArray[ix].t2_space+StatArray[ix].tx_space+StatArray[ix].stub_space) > granule_segs) {
out->print_cr("t1_space[%d] = %d, t2_space[%d] = %d, tx_space[%d] = %d, stub_space[%d] = %d", ix, StatArray[ix].t1_space, ix, StatArray[ix].t2_space, ix, StatArray[ix].tx_space, ix, StatArray[ix].stub_space);
}
}
// This loop is intentionally printing directly to "out". // It should not print anything, anyway. if (used_topSizeBlocks > 0) { unsignedint j = 0; if (TopSizeArray[0].len != currMax) {
out->print_cr("currMax(%d) differs from TopSizeArray[0].len(%d)", currMax, TopSizeArray[0].len);
} for (unsignedint i = 0; (TopSizeArray[i].index != tsbStopper) && (j++ < alloc_topSizeBlocks); i = TopSizeArray[i].index) { if (TopSizeArray[i].len < TopSizeArray[TopSizeArray[i].index].len) {
out->print_cr("sort error at index %d: %d !>= %d", i, TopSizeArray[i].len, TopSizeArray[TopSizeArray[i].index].len);
}
} if (j >= alloc_topSizeBlocks) {
out->print_cr("Possible loop in TopSizeArray chaining!\n allocBlocks = %d, usedBlocks = %d", alloc_topSizeBlocks, used_topSizeBlocks); for (unsignedint i = 0; i < alloc_topSizeBlocks; i++) {
out->print_cr(" TopSizeArray[%d].index = %d, len = %d", i, TopSizeArray[i].index, TopSizeArray[i].len);
}
}
}
out->print_cr("...done\n\n");
} else { // insane heap state detected. Analysis data incomplete. Just throw it away.
discard_StatArray(out);
discard_TopSizeArray(out);
}
}
done = false; while (!done && (nBlocks_free > 0)) {
printBox(ast, '=', "C O D E H E A P A N A L Y S I S (free blocks) for segment ", heapName);
ast->print_cr(" The aggregate step collects information about all free blocks in CodeHeap.\n" " Subsequent print functions create their output based on this snapshot.\n");
ast->print_cr(" Free space in %s is distributed over %d free blocks.", heapName, nBlocks_free);
ast->print_cr(" Each free block takes " SIZE_FORMAT " bytes of C heap for statistics data, that is " SIZE_FORMAT "K in total.", sizeof(FreeBlk), (sizeof(FreeBlk)*nBlocks_free)/K);
BUFFEREDSTREAM_FLUSH("\n")
//---------------------------------------- //-- Prepare the FreeArray of FreeBlks -- //----------------------------------------
//---< discard old array if size does not match >--- if (nBlocks_free != alloc_freeBlocks) {
discard_FreeArray(out);
}
//---------------------------------------- //-- Collect all FreeBlks in FreeArray -- //----------------------------------------
unsignedint ix = 0;
FreeBlock* cur = heap->freelist();
while (cur != NULL) { if (ix < alloc_freeBlocks) { // don't index out of bounds if _freelist has more blocks than anticipated
FreeArray[ix].start = cur;
FreeArray[ix].len = (unsignedint)(cur->length()<<log2_seg_size);
FreeArray[ix].index = ix;
}
cur = cur->link();
ix++;
} if (ix != alloc_freeBlocks) {
ast->print_cr("Free block count mismatch. Expected %d free blocks, but found %d.", alloc_freeBlocks, ix);
ast->print_cr("I will update the counter and retry data collection");
BUFFEREDSTREAM_FLUSH("\n")
nBlocks_free = ix; continue;
}
done = true;
}
if (!done || (nBlocks_free == 0)) { if (nBlocks_free == 0) {
printBox(ast, '-', "no free blocks found in ", heapName);
} elseif (!done) {
ast->print_cr("Free block count mismatch could not be resolved.");
ast->print_cr("Try to run \"aggregate\" function to update counters");
}
BUFFEREDSTREAM_FLUSH("")
//---< discard old array and update global values >---
discard_FreeArray(out);
set_HeapStatGlobals(out, heapName); return;
}
//---< calculate and fill remaining fields >--- if (FreeArray != NULL) { // This loop is intentionally printing directly to "out". // It should not print anything, anyway. for (unsignedint ix = 0; ix < alloc_freeBlocks-1; ix++) {
size_t lenSum = 0;
FreeArray[ix].gap = (unsignedint)((address)FreeArray[ix+1].start - ((address)FreeArray[ix].start + FreeArray[ix].len)); for (HeapBlock *h = heap->next_block(FreeArray[ix].start); (h != NULL) && (h != FreeArray[ix+1].start); h = heap->next_block(h)) {
CodeBlob *cb = (CodeBlob*)(heap->find_start(h)); if ((cb != NULL) && !cb->is_nmethod()) { // checks equivalent to those in get_cbType()
FreeArray[ix].stubs_in_gap = true;
}
FreeArray[ix].n_gapBlocks++;
lenSum += h->length()<<log2_seg_size; if (((address)h < ((address)FreeArray[ix].start+FreeArray[ix].len)) || (h >= FreeArray[ix+1].start)) {
out->print_cr("unsorted occupied CodeHeap block found @ %p, gap interval [%p, %p)", h, (address)FreeArray[ix].start+FreeArray[ix].len, FreeArray[ix+1].start);
}
} if (lenSum != FreeArray[ix].gap) {
out->print_cr("Length mismatch for gap between FreeBlk[%d] and FreeBlk[%d]. Calculated: %d, accumulated: %d.", ix, ix+1, FreeArray[ix].gap, (unsignedint)lenSum);
}
}
}
set_HeapStatGlobals(out, heapName);
printBox(ast, '=', "C O D E H E A P A N A L Y S I S C O M P L E T E for segment ", heapName);
BUFFEREDSTREAM_FLUSH("\n")
}
{
printBox(ast, '=', "U S E D S P A C E S T A T I S T I C S for ", heapName);
ast->print_cr("Note: The Top%d list of the largest used blocks associates method names\n" " and other identifying information with the block size data.\n" "\n" " Method names are dynamically retrieved from the code cache at print time.\n" " Due to the living nature of the code cache and because the CodeCache_lock\n" " is not continuously held, the displayed name might be wrong or no name\n" " might be found at all. The likelihood for that to happen increases\n" " over time passed between analysis and print step.\n", used_topSizeBlocks);
BUFFEREDSTREAM_FLUSH_LOCKED("\n")
}
//---------------------------- //-- Print Top Used Blocks -- //----------------------------
{ char* low_bound = heap->low_boundary();
printBox(ast, '-', "Largest Used Blocks in ", heapName);
print_blobType_legend(ast);
//---< print Top Ten Used Blocks >--- if (used_topSizeBlocks > 0) { unsignedint printed_topSizeBlocks = 0; for (unsignedint i = 0; i != tsbStopper; i = TopSizeArray[i].index) {
printed_topSizeBlocks++; if (TopSizeArray[i].blob_name == NULL) {
TopSizeArray[i].blob_name = os::strdup("unnamed blob or blob name unavailable");
} // heap->find_start() is safe. Only works on _segmap. // Returns NULL or void*. Returned CodeBlob may be uninitialized.
HeapBlock* heapBlock = TopSizeArray[i].start;
CodeBlob* this_blob = (CodeBlob*)(heap->find_start(heapBlock)); if (this_blob != NULL) { //---< access these fields only if we own the CodeCache_lock >--- //---< blob address >---
ast->print(INTPTR_FORMAT, p2i(this_blob));
ast->fill_to(19); //---< blob offset from CodeHeap begin >---
ast->print("(+" UINT32_FORMAT_X_0 ")", (unsignedint)((char*)this_blob-low_bound));
ast->fill_to(33);
} else { //---< block address >---
ast->print(INTPTR_FORMAT, p2i(TopSizeArray[i].start));
ast->fill_to(19); //---< block offset from CodeHeap begin >---
ast->print("(+" UINT32_FORMAT_X_0 ")", (unsignedint)((char*)TopSizeArray[i].start-low_bound));
ast->fill_to(33);
}
//---< print size, name, and signature (for nMethods) >--- bool is_nmethod = TopSizeArray[i].nm_size > 0; if (is_nmethod) { //---< nMethod size in hex >---
ast->print(UINT32_FORMAT_X_0, TopSizeArray[i].nm_size);
ast->print("(" SIZE_FORMAT_W(4) "K)", TopSizeArray[i].nm_size/K);
ast->fill_to(51);
ast->print(" %c", blobTypeChar[TopSizeArray[i].type]); //---< compiler information >---
ast->fill_to(56);
ast->print("%5s %3d", compTypeName[TopSizeArray[i].compiler], TopSizeArray[i].level); //---< name and signature >---
ast->fill_to(67+6);
ast->print("%s", TopSizeArray[i].blob_name);
} else { //---< block size in hex >---
ast->print(UINT32_FORMAT_X_0, (unsignedint)(TopSizeArray[i].len<<log2_seg_size));
ast->print("(" SIZE_FORMAT_W(4) "K)", (TopSizeArray[i].len<<log2_seg_size)/K); //---< no compiler information >---
ast->fill_to(56); //---< name and signature >---
ast->fill_to(67+6);
ast->print("%s", TopSizeArray[i].blob_name);
}
ast->cr();
BUFFEREDSTREAM_FLUSH_AUTO("")
} if (used_topSizeBlocks != printed_topSizeBlocks) {
ast->print_cr("used blocks: %d, printed blocks: %d", used_topSizeBlocks, printed_topSizeBlocks); for (unsignedint i = 0; i < alloc_topSizeBlocks; i++) {
ast->print_cr(" TopSizeArray[%d].index = %d, len = %d", i, TopSizeArray[i].index, TopSizeArray[i].len);
BUFFEREDSTREAM_FLUSH_AUTO("")
}
}
BUFFEREDSTREAM_FLUSH("\n\n")
}
}
for (unsignedint i = 0; i < nSizeDistElements; i++) {
total_count += SizeDistributionArray[i].count;
total_size += SizeDistributionArray[i].lenSum;
}
if ((total_count > 0) && (total_size > 0)) {
printBox(ast, '-', "Block count histogram for ", heapName);
ast->print_cr("Note: The histogram indicates how many blocks (as a percentage\n" " of all blocks) have a size in the given range.\n" " %ld characters are printed per percentage point.\n", pctFactor/100);
ast->print_cr("total size of all blocks: %7ldM", (total_size<<log2_seg_size)/M);
ast->print_cr("total number of all blocks: %7ld\n", total_count);
BUFFEREDSTREAM_FLUSH_LOCKED("")
printBox(ast, '-', "Contribution per size range to total size for ", heapName);
ast->print_cr("Note: The histogram indicates how much space (as a percentage of all\n" " occupied space) is used by the blocks in the given size range.\n" " %ld characters are printed per percentage point.\n", pctFactor/100);
ast->print_cr("total size of all blocks: %7ldM", (total_size<<log2_seg_size)/M);
ast->print_cr("total number of all blocks: %7ld\n", total_count);
BUFFEREDSTREAM_FLUSH_LOCKED("")
{
printBox(ast, '=', "F R E E S P A C E S T A T I S T I C S for ", heapName);
ast->print_cr("Note: in this context, a gap is the occupied space between two free blocks.\n" " Those gaps are of interest if there is a chance that they become\n" " unoccupied, e.g. by class unloading. Then, the two adjacent free\n" " blocks, together with the now unoccupied space, form a new, large\n" " free block.");
BUFFEREDSTREAM_FLUSH_LOCKED("\n")
}
{
printBox(ast, '-', "List of all Free Blocks in ", heapName);
for (unsignedint ix = 0; ix < alloc_freeBlocks; ix++) { if (FreeArray[ix].len > currMax10) { // larger than the ten largest found so far unsignedint currSize = FreeArray[ix].len;
unsignedint iy; for (iy = 0; iy < nTop && FreeTopTen[iy] != NULL; iy++) { if (FreeTopTen[iy]->len < currSize) { for (unsignedint iz = nTop-1; iz > iy; iz--) { // make room to insert new free block
FreeTopTen[iz] = FreeTopTen[iz-1];
}
FreeTopTen[iy] = &FreeArray[ix]; // insert new free block if (FreeTopTen[nTop-1] != NULL) {
currMax10 = FreeTopTen[nTop-1]->len;
} break; // done with this, check next free block
}
} if (iy >= nTop) {
ast->print_cr("Internal logic error. New Max10 = %d detected, but could not be merged. Old Max10 = %d",
currSize, currMax10); continue;
} if (FreeTopTen[iy] == NULL) {
FreeTopTen[iy] = &FreeArray[ix]; if (iy == (nTop-1)) {
currMax10 = currSize;
}
}
}
}
BUFFEREDSTREAM_FLUSH_AUTO("")
{
printBox(ast, '-', "Top Ten Free Blocks in ", heapName);
//-------------------------------------------------------- //-- Find and Print Top Ten Free-Occupied-Free Triples -- //--------------------------------------------------------
//---< find and print Top Ten Triples (Free-Occupied-Free) >---
currMax10 = 0; struct FreeBlk *FreeTopTenTriple[nTop];
memset(FreeTopTenTriple, 0, sizeof(FreeTopTenTriple));
for (unsignedint ix = 0; ix < alloc_freeBlocks-1; ix++) { // If there are stubs in the gap, this gap will never become completely free. // The triple will thus never merge to one free block. unsignedint lenTriple = FreeArray[ix].len + (FreeArray[ix].stubs_in_gap ? 0 : FreeArray[ix].gap + FreeArray[ix+1].len);
FreeArray[ix].len = lenTriple; if (lenTriple > currMax10) { // larger than the ten largest found so far
unsignedint iy; for (iy = 0; (iy < nTop) && (FreeTopTenTriple[iy] != NULL); iy++) { if (FreeTopTenTriple[iy]->len < lenTriple) { for (unsignedint iz = nTop-1; iz > iy; iz--) {
FreeTopTenTriple[iz] = FreeTopTenTriple[iz-1];
}
FreeTopTenTriple[iy] = &FreeArray[ix]; if (FreeTopTenTriple[nTop-1] != NULL) {
currMax10 = FreeTopTenTriple[nTop-1]->len;
} break;
}
} if (iy == nTop) {
ast->print_cr("Internal logic error. New Max10 = %d detected, but could not be merged. Old Max10 = %d",
lenTriple, currMax10); continue;
} if (FreeTopTenTriple[iy] == NULL) {
FreeTopTenTriple[iy] = &FreeArray[ix]; if (iy == (nTop-1)) {
currMax10 = lenTriple;
}
}
}
}
BUFFEREDSTREAM_FLUSH_AUTO("")
{
printBox(ast, '-', "Top Ten Free-Occupied-Free Triples in ", heapName);
ast->print_cr(" Use this information to judge how likely it is that a large(r) free block\n" " might get created by code cache sweeping.\n" " If all the occupied blocks can be swept, the three free blocks will be\n" " merged into one (much larger) free block. That would reduce free space\n" " fragmentation.\n");
{
printBox(ast, '=', "B L O C K C O U N T S for ", heapName);
ast->print_cr(" Each granule contains an individual number of heap blocks. Large blocks\n" " may span multiple granules and are counted for each granule they touch.\n"); if (segment_granules) {
ast->print_cr(" You have selected granule size to be as small as segment size.\n" " As a result, each granule contains exactly one block (or a part of one block)\n" " or is displayed as empty (' ') if it's BlobType does not match the selection.\n" " Occupied granules show their BlobType character, see legend.\n");
print_blobType_legend(ast);
}
BUFFEREDSTREAM_FLUSH_LOCKED("")
}
{ if (segment_granules) {
printBox(ast, '-', "Total (all types) count for granule size == segment size", NULL);
{
printBox(ast, '=', "S P A C E U S A G E & F R A G M E N T A T I O N for ", heapName);
ast->print_cr(" The heap space covered by one granule is occupied to a various extend.\n" " The granule occupancy is displayed by one decimal digit per granule.\n"); if (segment_granules) {
ast->print_cr(" You have selected granule size to be as small as segment size.\n" " As a result, each granule contains exactly one block (or a part of one block)\n" " or is displayed as empty (' ') if it's BlobType does not match the selection.\n" " Occupied granules show their BlobType character, see legend.\n");
print_blobType_legend(ast);
} else {
ast->print_cr(" These digits represent a fill percentage range (see legend).\n");
print_space_legend(ast);
}
BUFFEREDSTREAM_FLUSH_LOCKED("")
}
{ if (segment_granules) {
printBox(ast, '-', "Total (all types) space consumption for granule size == segment size", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line);
print_blobType_single(ast, StatArray[ix].type);
}
} else {
printBox(ast, '-', "Total (all types) space consumption. ' ' indicates empty, '*' indicates full.", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); unsignedint space = StatArray[ix].t1_space + StatArray[ix].t2_space + StatArray[ix].tx_space
+ StatArray[ix].stub_space;
print_space_single(ast, space);
}
}
BUFFEREDSTREAM_FLUSH_LOCKED("|\n\n\n")
}
{ if (nBlocks_t1 > 0) {
printBox(ast, '-', "Tier1 space consumption. ' ' indicates empty, '*' indicates full", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); if (segment_granules && StatArray[ix].t1_space > 0) {
print_blobType_single(ast, StatArray[ix].type);
} else {
print_space_single(ast, StatArray[ix].t1_space);
}
}
ast->print("|");
} else {
ast->print("No Tier1 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_t2 > 0) {
printBox(ast, '-', "Tier2 space consumption. ' ' indicates empty, '*' indicates full", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); if (segment_granules && StatArray[ix].t2_space > 0) {
print_blobType_single(ast, StatArray[ix].type);
} else {
print_space_single(ast, StatArray[ix].t2_space);
}
}
ast->print("|");
} else {
ast->print("No Tier2 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_alive > 0) {
printBox(ast, '-', "not_used/not_entrant/not_installed space consumption. ' ' indicates empty, '*' indicates full", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); if (segment_granules && StatArray[ix].tx_space > 0) {
print_blobType_single(ast, StatArray[ix].type);
} else {
print_space_single(ast, StatArray[ix].tx_space);
}
}
ast->print("|");
} else {
ast->print("No Tier2 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_stub > 0) {
printBox(ast, '-', "Stub and Blob space consumption. ' ' indicates empty, '*' indicates full", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); if (segment_granules && StatArray[ix].stub_space > 0) {
print_blobType_single(ast, StatArray[ix].type);
} else {
print_space_single(ast, StatArray[ix].stub_space);
}
}
ast->print("|");
} else {
ast->print("No Stubs and Blobs found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{
printBox(ast, '=', "M E T H O D A G E by CompileID for ", heapName);
ast->print_cr(" The age of a compiled method in the CodeHeap is not available as a\n" " time stamp. Instead, a relative age is deducted from the method's compilation ID.\n" " Age information is available for tier1 and tier2 methods only. There is no\n" " age information for stubs and blobs, because they have no compilation ID assigned.\n" " Information for the youngest method (highest ID) in the granule is printed.\n" " Refer to the legend to learn how method age is mapped to the displayed digit.");
print_age_legend(ast);
BUFFEREDSTREAM_FLUSH_LOCKED("")
}
{
printBox(ast, '-', "Age distribution. '0' indicates youngest 1/256, '8': oldest half, ' ': no age information", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line); unsignedint age1 = StatArray[ix].t1_age; unsignedint age2 = StatArray[ix].t2_age; unsignedint agex = StatArray[ix].tx_age; unsignedint age = age1 > age2 ? age1 : age2;
age = age > agex ? age : agex;
print_age_single(ast, age);
}
ast->print("|");
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_t1 > 0) {
printBox(ast, '-', "Tier1 age distribution. '0' indicates youngest 1/256, '8': oldest half, ' ': no age information", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line);
print_age_single(ast, StatArray[ix].t1_age);
}
ast->print("|");
} else {
ast->print("No Tier1 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_t2 > 0) {
printBox(ast, '-', "Tier2 age distribution. '0' indicates youngest 1/256, '8': oldest half, ' ': no age information", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line);
print_age_single(ast, StatArray[ix].t2_age);
}
ast->print("|");
} else {
ast->print("No Tier2 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (nBlocks_alive > 0) {
printBox(ast, '-', "not_used/not_entrant/not_installed age distribution. '0' indicates youngest 1/256, '8': oldest half, ' ': no age information", NULL);
granules_per_line = 128; for (unsignedint ix = 0; ix < alloc_granules; ix++) {
print_line_delim(out, ast, low_bound, ix, granules_per_line);
print_age_single(ast, StatArray[ix].tx_age);
}
ast->print("|");
} else {
ast->print("No Tier2 nMethods found in CodeHeap.");
}
BUFFEREDSTREAM_FLUSH_LOCKED("\n\n\n")
}
{ if (!segment_granules) { // Prevent totally redundant printouts
printBox(ast, '-', "age distribution by tier <a1>:<a2>. '0' indicates youngest 1/256, '8': oldest half, ' ': no age information", NULL);
//---< print at least 128K per block (i.e. between headers) >--- if (granules_per_line*granule_size < 128*K) {
granules_per_line = (unsignedint)((128*K)/granule_size);
}
printBox(ast, '=', "M E T H O D N A M E S for ", heapName);
ast->print_cr(" Method names are dynamically retrieved from the code cache at print time.\n" " Due to the living nature of the code heap and because the CodeCache_lock\n" " is not continuously held, the displayed name might be wrong or no name\n" " might be found at all. The likelihood for that to happen increases\n" " over time passed between aggregation and print steps.\n");
BUFFEREDSTREAM_FLUSH_LOCKED("")
for (unsignedint ix = 0; ix < alloc_granules; ix++) { //---< print a new blob on a new line >--- if (ix%granules_per_line == 0) { if (!name_in_addr_range) {
ast->print_cr("No methods, blobs, or stubs found in this address range");
}
name_in_addr_range = false;
size_t end_ix = (ix+granules_per_line <= alloc_granules) ? ix+granules_per_line : alloc_granules;
ast->cr();
ast->print_cr("--------------------------------------------------------------------");
ast->print_cr("Address range [" INTPTR_FORMAT "," INTPTR_FORMAT "), " SIZE_FORMAT "k", p2i(low_bound+ix*granule_size), p2i(low_bound + end_ix*granule_size), (end_ix - ix)*granule_size/(size_t)K);
ast->print_cr("--------------------------------------------------------------------");
BUFFEREDSTREAM_FLUSH_AUTO("")
} // Only check granule if it contains at least one blob. unsignedint nBlobs = StatArray[ix].t1_count + StatArray[ix].t2_count + StatArray[ix].tx_count +
StatArray[ix].stub_count; if (nBlobs > 0 ) { for (unsignedint is = 0; is < granule_size; is+=(unsignedint)seg_size) { // heap->find_start() is safe. Only works on _segmap. // Returns NULL or void*. Returned CodeBlob may be uninitialized. char* this_seg = low_bound + ix*granule_size + is;
CodeBlob* this_blob = (CodeBlob*)(heap->find_start(this_seg)); bool blob_is_safe = blob_access_is_safe(this_blob); // blob could have been flushed, freed, and merged. // this_blob < last_blob is an indicator for that. if (blob_is_safe && (this_blob > last_blob)) {
last_blob = this_blob;
//---< get type and name >---
blobType cbType = noType; if (segment_granules) {
cbType = (blobType)StatArray[ix].type;
} else { //---< access these fields only if we own the CodeCache_lock >--- if (have_locks) {
cbType = get_cbType(this_blob);
}
}
//---< access these fields only if we own the CodeCache_lock >--- constchar* blob_name = "<unavailable>";
nmethod* nm = NULL; if (have_locks) {
blob_name = this_blob->name();
nm = this_blob->as_nmethod_or_null(); // this_blob->name() could return NULL if no name was given to CTOR. Inlined, maybe invisible on stack if (blob_name == NULL) {
blob_name = "<unavailable>";
}
}
//---< print table header for new print range >--- if (!name_in_addr_range) {
name_in_addr_range = true;
ast->fill_to(51);
ast->print("%9s", "compiler");
ast->fill_to(61);
ast->print_cr("%6s", "method");
ast->print_cr("%18s %13s %17s %9s %18s %s", "Addr(module) ", "offset", "size", " type lvl", "blobType ", "Name");
BUFFEREDSTREAM_FLUSH_AUTO("")
}
//---< print line prefix (address and offset from CodeHeap start) >---
ast->print(INTPTR_FORMAT, p2i(this_blob));
ast->fill_to(19);
ast->print("(+" UINT32_FORMAT_X_0 ")", (unsignedint)((char*)this_blob-low_bound));
ast->fill_to(33);
// access nmethod and Method fields only if we own the CodeCache_lock. // This fact is implicitly transported via nm != NULL. if (nmethod_access_is_safe(nm)) {
Method* method = nm->method();
ResourceMark rm; //---< collect all data to locals as quickly as possible >--- unsignedint total_size = nm->total_size(); bool get_name = (cbType == nMethod_inuse) || (cbType == nMethod_notused); //---< nMethod size in hex >---
ast->print(UINT32_FORMAT_X_0, total_size);
ast->print("(" SIZE_FORMAT_W(4) "K)", total_size/K); //---< compiler information >---
ast->fill_to(51);
ast->print("%5s %3d", compTypeName[StatArray[ix].compiler], StatArray[ix].level); //---< name and signature >---
ast->fill_to(62);
ast->print("%s", blobTypeName[cbType]);
ast->fill_to(82);
ast->print("%c", edge); for (unsignedint i = 0; i < lineLen-2; i++) {
ast->print("%c", border);
}
ast->print_cr("%c", edge);
}
void CodeHeapState::print_blobType_legend(outputStream* out) {
out->cr();
printBox(out, '-', "Block types used in the following CodeHeap dump", NULL); for (int type = noType; type < lastType; type += 1) {
out->print_cr(" %c - %s", blobTypeChar[type], blobTypeName[type]);
}
out->print_cr(" -----------------------------------------------------");
out->cr();
}
void CodeHeapState::print_line_delim(outputStream* out, bufferedStream* ast, char* low_bound, unsignedint ix, unsignedint gpl) {
assert(out != ast, "must not use the same stream!"); if (ix % gpl == 0) { if (ix > 0) {
ast->print("|");
}
ast->cr();
// can't use BUFFEREDSTREAM_FLUSH_IF("", 512) here. // can't use this expression. bufferedStream::capacity() does not exist. // if ((ast->capacity() - ast->size()) < 512) { // Assume instead that default bufferedStream capacity (4K) was used. if (ast->size() > 3*K) {
ttyLocker ttyl;
out->print("%s", ast->as_string());
ast->reset();
}
// Find out which blob type we have at hand. // Return "noType" if anything abnormal is detected.
CodeHeapState::blobType CodeHeapState::get_cbType(CodeBlob* cb) { if (cb != NULL) { if (cb->is_runtime_stub()) return runtimeStub; if (cb->is_deoptimization_stub()) return deoptimizationStub; if (cb->is_uncommon_trap_stub()) return uncommonTrapStub; if (cb->is_exception_stub()) return exceptionStub; if (cb->is_safepoint_stub()) return safepointStub; if (cb->is_adapter_blob()) return adapterBlob; if (cb->is_method_handles_adapter_blob()) return mh_adapterBlob; if (cb->is_buffer_blob()) return bufferBlob;
//---< access these fields only if we own CodeCache_lock and Compile_lock >--- // Should be ensured by caller. aggregate() and print_names() do that. if (holding_required_locks()) {
nmethod* nm = cb->as_nmethod_or_null(); if (nm != NULL) { // no is_readable check required, nm = (nmethod*)cb. if (nm->is_in_use()) return nMethod_inuse; if (!nm->is_not_entrant()) return nMethod_notused; return nMethod_notentrant;
}
}
} return noType;
}
// make sure the blob at hand is not garbage. bool CodeHeapState::blob_access_is_safe(CodeBlob* this_blob) { return (this_blob != NULL) && // a blob must have been found, obviously
(this_blob->header_size() >= 0) &&
(this_blob->relocation_size() >= 0) &&
((address)this_blob + this_blob->header_size() == (address)(this_blob->relocation_begin())) &&
((address)this_blob + CodeBlob::align_code_offset(this_blob->header_size() + this_blob->relocation_size()) == (address)(this_blob->content_begin()));
}
// make sure the nmethod at hand (and the linked method) is not garbage. bool CodeHeapState::nmethod_access_is_safe(nmethod* nm) {
Method* method = (nm == NULL) ? NULL : nm->method(); // nm->method() was found to be uninitialized, i.e. != NULL, but invalid. return (nm != NULL) && (method != NULL) && (method->signature() != NULL);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.