/* * 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("")
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 ist noch experimentell.