Skip to content

Commit dc256fb

Browse files
committedNov 28, 2023
8320061: [nmt] Multiple issues with peak accounting
Reviewed-by: jsjolen, mbaesken
1 parent adad132 commit dc256fb

15 files changed

+551
-297
lines changed
 

‎src/hotspot/share/nmt/memReporter.cpp

+88-67
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void MemReporterBase::print_total(size_t reserved, size_t committed, size_t peak
5555
output()->print("reserved=" SIZE_FORMAT "%s, committed=" SIZE_FORMAT "%s",
5656
amount_in_current_scale(reserved), scale, amount_in_current_scale(committed), scale);
5757
if (peak != 0) {
58-
output()->print(", largest_committed=" SIZE_FORMAT "%s", amount_in_current_scale(peak), scale);
58+
output()->print(", peak=" SIZE_FORMAT "%s", amount_in_current_scale(peak), scale);
5959
}
6060
}
6161

@@ -93,9 +93,15 @@ void MemReporterBase::print_malloc(const MemoryCounter* c, MEMFLAGS flag) const
9393
}
9494

9595
void MemReporterBase::print_virtual_memory(size_t reserved, size_t committed, size_t peak) const {
96+
outputStream* out = output();
9697
const char* scale = current_scale();
97-
output()->print("(mmap: reserved=" SIZE_FORMAT "%s, committed=" SIZE_FORMAT "%s, largest_committed=" SIZE_FORMAT "%s)",
98-
amount_in_current_scale(reserved), scale, amount_in_current_scale(committed), scale, amount_in_current_scale(peak), scale);
98+
out->print("(mmap: reserved=" SIZE_FORMAT "%s, committed=" SIZE_FORMAT "%s, ",
99+
amount_in_current_scale(reserved), scale, amount_in_current_scale(committed), scale);
100+
if (peak == committed) {
101+
out->print_raw("at peak)");
102+
} else {
103+
out->print("peak=" SIZE_FORMAT "%s)", amount_in_current_scale(peak), scale);
104+
}
99105
}
100106

101107
void MemReporterBase::print_malloc_line(const MemoryCounter* c) const {
@@ -204,74 +210,79 @@ void MemSummaryReporter::report_summary_of_type(MEMFLAGS flag,
204210
committed_amount += _malloc_snapshot->malloc_overhead();
205211
}
206212

207-
if (amount_in_current_scale(reserved_amount) > 0) {
208-
outputStream* out = output();
209-
const char* scale = current_scale();
210-
out->print("-%26s (", NMTUtil::flag_to_name(flag));
211-
print_total(reserved_amount, committed_amount);
213+
// Omit printing if the current reserved value as well as all historical peaks (malloc, mmap committed, arena)
214+
// fall below scale threshold
215+
const size_t pk_vm = virtual_memory->peak_size();
216+
const size_t pk_malloc = malloc_memory->malloc_peak_size();
217+
const size_t pk_arena = malloc_memory->arena_peak_size();
218+
219+
if (amount_in_current_scale(MAX4(reserved_amount, pk_vm, pk_malloc, pk_arena)) == 0) {
220+
return;
221+
}
222+
223+
outputStream* out = output();
224+
const char* scale = current_scale();
225+
out->print("-%26s (", NMTUtil::flag_to_name(flag));
226+
print_total(reserved_amount, committed_amount);
212227
#if INCLUDE_CDS
213-
if (flag == mtClassShared) {
214-
size_t read_only_bytes = FileMapInfo::readonly_total();
215-
output()->print(", readonly=" SIZE_FORMAT "%s",
216-
amount_in_current_scale(read_only_bytes), scale);
217-
}
228+
if (flag == mtClassShared) {
229+
size_t read_only_bytes = FileMapInfo::readonly_total();
230+
output()->print(", readonly=" SIZE_FORMAT "%s",
231+
amount_in_current_scale(read_only_bytes), scale);
232+
}
218233
#endif
219-
out->print_cr(")");
234+
out->print_cr(")");
220235

221-
if (flag == mtClass) {
222-
// report class count
223-
out->print_cr("%27s (classes #" SIZE_FORMAT ")",
224-
" ", (_instance_class_count + _array_class_count));
225-
out->print_cr("%27s ( instance classes #" SIZE_FORMAT ", array classes #" SIZE_FORMAT ")",
226-
" ", _instance_class_count, _array_class_count);
227-
} else if (flag == mtThread) {
228-
if (ThreadStackTracker::track_as_vm()) {
229-
const VirtualMemory* thread_stack_usage =
230-
_vm_snapshot->by_type(mtThreadStack);
231-
// report thread count
232-
out->print_cr("%27s (threads #" SIZE_FORMAT ")", " ", ThreadStackTracker::thread_count());
233-
out->print("%27s (stack: ", " ");
234-
print_total(thread_stack_usage->reserved(), thread_stack_usage->committed(), thread_stack_usage->peak_size());
235-
} else {
236-
MallocMemory* thread_stack_memory = _malloc_snapshot->by_type(mtThreadStack);
237-
const char* scale = current_scale();
238-
// report thread count
239-
out->print_cr("%27s (threads #" SIZE_FORMAT ")", " ", thread_stack_memory->malloc_count());
240-
out->print("%27s (Stack: " SIZE_FORMAT "%s", " ",
241-
amount_in_current_scale(thread_stack_memory->malloc_size()), scale);
242-
}
243-
out->print_cr(")");
236+
if (flag == mtClass) {
237+
// report class count
238+
out->print_cr("%27s (classes #" SIZE_FORMAT ")",
239+
" ", (_instance_class_count + _array_class_count));
240+
out->print_cr("%27s ( instance classes #" SIZE_FORMAT ", array classes #" SIZE_FORMAT ")",
241+
" ", _instance_class_count, _array_class_count);
242+
} else if (flag == mtThread) {
243+
if (ThreadStackTracker::track_as_vm()) {
244+
const VirtualMemory* thread_stack_usage =
245+
_vm_snapshot->by_type(mtThreadStack);
246+
// report thread count
247+
out->print_cr("%27s (threads #" SIZE_FORMAT ")", " ", ThreadStackTracker::thread_count());
248+
out->print("%27s (stack: ", " ");
249+
print_total(thread_stack_usage->reserved(), thread_stack_usage->committed(), thread_stack_usage->peak_size());
250+
} else {
251+
MallocMemory* thread_stack_memory = _malloc_snapshot->by_type(mtThreadStack);
252+
const char* scale = current_scale();
253+
// report thread count
254+
out->print_cr("%27s (threads #" SIZE_FORMAT ")", " ", thread_stack_memory->malloc_count());
255+
out->print("%27s (Stack: " SIZE_FORMAT "%s", " ",
256+
amount_in_current_scale(thread_stack_memory->malloc_size()), scale);
244257
}
258+
out->print_cr(")");
259+
}
245260

246-
// report malloc'd memory
247-
if (amount_in_current_scale(malloc_memory->malloc_size()) > 0
248-
|| amount_in_current_scale(malloc_memory->malloc_peak_size()) > 0) {
249-
print_malloc_line(malloc_memory->malloc_counter());
250-
}
261+
// report malloc'd memory
262+
if (amount_in_current_scale(MAX2(malloc_memory->malloc_size(), pk_malloc)) > 0) {
263+
print_malloc_line(malloc_memory->malloc_counter());
264+
}
251265

252-
if (amount_in_current_scale(virtual_memory->reserved()) > 0
253-
DEBUG_ONLY(|| amount_in_current_scale(virtual_memory->peak_size()) > 0)) {
254-
print_virtual_memory_line(virtual_memory->reserved(), virtual_memory->committed(), virtual_memory->peak_size());
255-
}
266+
if (amount_in_current_scale(MAX2(virtual_memory->reserved(), pk_vm)) > 0) {
267+
print_virtual_memory_line(virtual_memory->reserved(), virtual_memory->committed(), virtual_memory->peak_size());
268+
}
256269

257-
if (amount_in_current_scale(malloc_memory->arena_size()) > 0
258-
DEBUG_ONLY(|| amount_in_current_scale(malloc_memory->arena_peak_size()) > 0)) {
259-
print_arena_line(malloc_memory->arena_counter());
260-
}
270+
if (amount_in_current_scale(MAX2(malloc_memory->arena_size(), pk_arena)) > 0) {
271+
print_arena_line(malloc_memory->arena_counter());
272+
}
261273

262-
if (flag == mtNMT &&
263-
amount_in_current_scale(_malloc_snapshot->malloc_overhead()) > 0) {
264-
out->print_cr("%27s (tracking overhead=" SIZE_FORMAT "%s)", " ",
265-
amount_in_current_scale(_malloc_snapshot->malloc_overhead()), scale);
266-
} else if (flag == mtClass) {
267-
// Metadata information
268-
report_metadata(Metaspace::NonClassType);
269-
if (Metaspace::using_class_space()) {
270-
report_metadata(Metaspace::ClassType);
271-
}
274+
if (flag == mtNMT &&
275+
amount_in_current_scale(_malloc_snapshot->malloc_overhead()) > 0) {
276+
out->print_cr("%27s (tracking overhead=" SIZE_FORMAT "%s)", " ",
277+
amount_in_current_scale(_malloc_snapshot->malloc_overhead()), scale);
278+
} else if (flag == mtClass) {
279+
// Metadata information
280+
report_metadata(Metaspace::NonClassType);
281+
if (Metaspace::using_class_space()) {
282+
report_metadata(Metaspace::ClassType);
272283
}
273-
out->print_cr(" ");
274284
}
285+
out->print_cr(" ");
275286
}
276287

277288
void MemSummaryReporter::report_metadata(Metaspace::MetadataType type) const {
@@ -321,9 +332,8 @@ int MemDetailReporter::report_malloc_sites() {
321332
const MallocSite* malloc_site;
322333
int num_omitted = 0;
323334
while ((malloc_site = malloc_itr.next()) != nullptr) {
324-
// Don't report if site has never allocated less than one unit of whatever our scale is
325-
if (scale() > 1 && amount_in_current_scale(malloc_site->size()) == 0
326-
DEBUG_ONLY(&& amount_in_current_scale(malloc_site->peak_size()) == 0)) {
335+
// Omit printing if the current value and the historic peak value both fall below the reporting scale threshold
336+
if (amount_in_current_scale(MAX2(malloc_site->size(), malloc_site->peak_size())) == 0) {
327337
num_omitted ++;
328338
continue;
329339
}
@@ -353,8 +363,10 @@ int MemDetailReporter::report_virtual_memory_allocation_sites() {
353363
if (virtual_memory_site->reserved() == 0) {
354364
continue;
355365
}
356-
// Don't report if site has reserved less than one unit of whatever our scale is
357-
if (scale() > 1 && amount_in_current_scale(virtual_memory_site->reserved()) == 0) {
366+
// Omit printing if the current value and the historic peak value both fall below the
367+
// reporting scale threshold
368+
if (amount_in_current_scale(MAX2(virtual_memory_site->reserved(),
369+
virtual_memory_site->peak_size())) == 0) {
358370
num_omitted++;
359371
continue;
360372
}
@@ -386,7 +398,16 @@ void MemDetailReporter::report_virtual_memory_map() {
386398
void MemDetailReporter::report_virtual_memory_region(const ReservedMemoryRegion* reserved_rgn) {
387399
assert(reserved_rgn != nullptr, "null pointer");
388400

389-
// Don't report if size is too small
401+
// We don't bother about reporting peaks here.
402+
// That is because peaks - in the context of virtual memory, peak of committed areas - make little sense
403+
// when we report *by region*, which are identified by their location in memory. There is a philosophical
404+
// question about identity here: e.g. a committed region that has been split into three regions by
405+
// uncommitting a middle section of it, should that still count as "having peaked" before the split? If
406+
// yes, which of the three new regions would be the spiritual successor? Rather than introducing more
407+
// complexity, we avoid printing peaks altogether. Note that peaks should still be printed when reporting
408+
// usage *by callsite*.
409+
410+
// Don't report if size is too small.
390411
if (amount_in_current_scale(reserved_rgn->size()) == 0) return;
391412

392413
outputStream* out = output();

‎src/hotspot/share/nmt/virtualMemoryTracker.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434

3535
size_t VirtualMemorySummary::_snapshot[CALC_OBJ_SIZE_IN_TYPE(VirtualMemorySnapshot, size_t)];
3636

37-
#ifdef ASSERT
3837
void VirtualMemory::update_peak(size_t size) {
3938
size_t peak_sz = peak_size();
4039
while (peak_sz < size) {
@@ -46,7 +45,6 @@ void VirtualMemory::update_peak(size_t size) {
4645
}
4746
}
4847
}
49-
#endif // ASSERT
5048

5149
void VirtualMemorySummary::initialize() {
5250
assert(sizeof(_snapshot) >= sizeof(VirtualMemorySnapshot), "Sanity Check");

‎src/hotspot/share/nmt/virtualMemoryTracker.hpp

+4-9
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,17 @@ class VirtualMemory {
4242
size_t _reserved;
4343
size_t _committed;
4444

45-
#ifdef ASSERT
4645
volatile size_t _peak_size;
4746
void update_peak(size_t size);
48-
#endif // ASSERT
4947

5048
public:
51-
VirtualMemory() : _reserved(0), _committed(0) {
52-
DEBUG_ONLY(_peak_size = 0;)
53-
}
49+
VirtualMemory() : _reserved(0), _committed(0), _peak_size(0) {}
5450

5551
inline void reserve_memory(size_t sz) { _reserved += sz; }
5652
inline void commit_memory (size_t sz) {
5753
_committed += sz;
58-
DEBUG_ONLY(update_peak(sz);)
5954
assert(_committed <= _reserved, "Sanity check");
55+
update_peak(_committed);
6056
}
6157

6258
inline void release_memory (size_t sz) {
@@ -72,7 +68,7 @@ class VirtualMemory {
7268
inline size_t reserved() const { return _reserved; }
7369
inline size_t committed() const { return _committed; }
7470
inline size_t peak_size() const {
75-
return DEBUG_ONLY(Atomic::load(&_peak_size)) NOT_DEBUG(0);
71+
return Atomic::load(&_peak_size);
7672
}
7773
};
7874

@@ -85,10 +81,9 @@ class VirtualMemoryAllocationSite : public AllocationSite {
8581

8682
inline void reserve_memory(size_t sz) { _c.reserve_memory(sz); }
8783
inline void commit_memory (size_t sz) { _c.commit_memory(sz); }
88-
inline void uncommit_memory(size_t sz) { _c.uncommit_memory(sz); }
89-
inline void release_memory(size_t sz) { _c.release_memory(sz); }
9084
inline size_t reserved() const { return _c.reserved(); }
9185
inline size_t committed() const { return _c.committed(); }
86+
inline size_t peak_size() const { return _c.peak_size(); }
9287
};
9388

9489
class VirtualMemorySummary;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2020, Red Hat, Inc. All rights reserved.
2+
* Copyright (c) 2019, 2023, Red Hat, Inc. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,59 +31,81 @@
3131
* java.management
3232
* @build jdk.test.whitebox.WhiteBox
3333
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
34-
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail HugeArenaTracking
34+
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=summary HugeArenaTracking
3535
*/
3636

3737
import java.util.Random;
3838
import jdk.test.lib.process.ProcessTools;
3939
import jdk.test.lib.process.OutputAnalyzer;
40-
import jdk.test.lib.JDKToolFinder;
4140
import jdk.test.lib.Utils;
4241
import jdk.test.whitebox.WhiteBox;
4342

4443
public class HugeArenaTracking {
45-
private static final long GB = 1024 * 1024 * 1024;
44+
private static final long MB = 1024 * 1024;
45+
private static final long GB = MB * 1024;
4646

4747
public static void main(String args[]) throws Exception {
48-
OutputAnalyzer output;
4948
final WhiteBox wb = WhiteBox.getWhiteBox();
5049

51-
// Grab my own PID
52-
String pid = Long.toString(ProcessTools.getProcessId());
53-
ProcessBuilder pb = new ProcessBuilder();
54-
5550
long arena1 = wb.NMTNewArena(1024);
5651
long arena2 = wb.NMTNewArena(1024);
5752

58-
// Run 'jcmd <pid> VM.native_memory summary'
59-
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "summary"});
60-
output = new OutputAnalyzer(pb.start());
61-
output.shouldContain("Test (reserved=2KB, committed=2KB)");
53+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
54+
new String[] { "scale=K" },
55+
new String[] { "Test (reserved=2KB, committed=2KB)",
56+
"(arena=2KB #2) (at peak)" });
6257

6358
Random rand = Utils.getRandomInstance();
6459

6560
// Allocate 2GB+ from arena
6661
long total = 0;
6762
while (total < 2 * GB) {
68-
// Cap to 10M
69-
long inc = rand.nextInt(10 * 1024 * 1024);
70-
wb.NMTArenaMalloc(arena1, inc);
71-
total += inc;
63+
wb.NMTArenaMalloc(arena1, MB);
64+
total += MB;
7265
}
7366

74-
ProcessBuilder pb2 = new ProcessBuilder();
75-
// Run 'jcmd <pid> VM.native_memory summary'
76-
pb2.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "summary", "scale=GB"});
77-
output = new OutputAnalyzer(pb2.start());
78-
output.shouldContain("Test (reserved=2GB, committed=2GB)");
67+
// run a report at GB level. We should see our allocations; since they are rounded
68+
// to GB, we expect an exact output match
69+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
70+
new String[] { "scale=G" },
71+
new String[] { "Test (reserved=2GB, committed=2GB)",
72+
"(arena=2GB #2) (at peak)" });
73+
74+
// Repeat at MB level; we expect the same behavior
75+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
76+
new String[] { "scale=M" },
77+
new String[] { "Test (reserved=2048MB, committed=2048MB)",
78+
"(arena=2048MB #2) (at peak)" });
7979

8080
wb.NMTFreeArena(arena1);
8181

82-
output = new OutputAnalyzer(pb.start());
83-
output.shouldContain("Test (reserved=1KB, committed=1KB)");
82+
// Repeat report at GB level. Reserved should be 0 now. Current usage is 1KB, since arena2 is left, but that
83+
// is below GB scale threshold, so should show up as 0.
84+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
85+
new String[] { "scale=G" },
86+
new String[] { "Test (reserved=0GB, committed=0GB)",
87+
"(arena=0GB #1) (peak=2GB #2)" });
88+
89+
// Same, for MB scale
90+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
91+
new String[] { "scale=M" },
92+
new String[] { "Test (reserved=0MB, committed=0MB)",
93+
"(arena=0MB #1) (peak=2048MB #2)" });
94+
95+
// At KB level we should see the remaining 1KB. Note that we refrain from testing peak here
96+
// since the number gets fuzzy: it depends on the size of the initially allocated chunk. At MB
97+
// and GB scale, these differences don't matter.
98+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
99+
new String[] { "scale=K" },
100+
new String[] { "Test (reserved=1KB, committed=1KB)",
101+
"(arena=1KB #1) (peak=" });
102+
84103
wb.NMTFreeArena(arena2);
85104

86-
output = new OutputAnalyzer(pb.start());
87-
output.shouldNotContain("Test (reserved");
105+
// Everything free'd, current usage 0, peak should be preserved.
106+
NMTTestUtils.runJcmdSummaryReportAndCheckOutput(
107+
new String[] { "scale=G" },
108+
new String[] { "Test (reserved=0GB, committed=0GB)",
109+
"(arena=0GB #0) (peak=2GB #2)" });
88110
}
89111
}

0 commit comments

Comments
 (0)
Please sign in to comment.