Skip to content

Commit 4229baf

Browse files
committed
8310015: ZGC: Unbounded asynchronous unmapping can lead to running out of address space
Reviewed-by: stefank, aboldtch
1 parent 266f983 commit 4229baf

File tree

6 files changed

+99
-12
lines changed

6 files changed

+99
-12
lines changed

src/hotspot/share/gc/x/xUnmapper.cpp

+42-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "precompiled.hpp"
2525
#include "gc/shared/gc_globals.hpp"
26+
#include "gc/shared/gcLogPrecious.hpp"
2627
#include "gc/x/xList.inline.hpp"
2728
#include "gc/x/xLock.inline.hpp"
2829
#include "gc/x/xPage.inline.hpp"
@@ -35,6 +36,8 @@ XUnmapper::XUnmapper(XPageAllocator* page_allocator) :
3536
_page_allocator(page_allocator),
3637
_lock(),
3738
_queue(),
39+
_enqueued_bytes(0),
40+
_warned_sync_unmapping(false),
3841
_stop(false) {
3942
set_name("XUnmapper");
4043
create_and_start();
@@ -50,13 +53,50 @@ XPage* XUnmapper::dequeue() {
5053

5154
XPage* const page = _queue.remove_first();
5255
if (page != nullptr) {
56+
_enqueued_bytes -= page->size();
5357
return page;
5458
}
5559

5660
_lock.wait();
5761
}
5862
}
5963

64+
bool XUnmapper::try_enqueue(XPage* page) {
65+
if (ZVerifyViews) {
66+
// Asynchronous unmap and destroy is not supported with ZVerifyViews
67+
return false;
68+
}
69+
70+
// Enqueue for asynchronous unmap and destroy
71+
XLocker<XConditionLock> locker(&_lock);
72+
if (is_saturated()) {
73+
// The unmapper thread is lagging behind and is unable to unmap memory fast enough
74+
if (!_warned_sync_unmapping) {
75+
_warned_sync_unmapping = true;
76+
log_warning_p(gc)("WARNING: Encountered synchronous unmapping because asynchronous unmapping could not keep up");
77+
}
78+
log_debug(gc, unmap)("Synchronous unmapping " SIZE_FORMAT "M page", page->size() / M);
79+
return false;
80+
}
81+
82+
log_trace(gc, unmap)("Asynchronous unmapping " SIZE_FORMAT "M page (" SIZE_FORMAT "M / " SIZE_FORMAT "M enqueued)",
83+
page->size() / M, _enqueued_bytes / M, queue_capacity() / M);
84+
85+
_queue.insert_last(page);
86+
_enqueued_bytes += page->size();
87+
_lock.notify_all();
88+
89+
return true;
90+
}
91+
92+
size_t XUnmapper::queue_capacity() const {
93+
return align_up<size_t>(_page_allocator->max_capacity() * ZAsyncUnmappingLimit / 100.0, XGranuleSize);
94+
}
95+
96+
bool XUnmapper::is_saturated() const {
97+
return _enqueued_bytes >= queue_capacity();
98+
}
99+
60100
void XUnmapper::do_unmap_and_destroy_page(XPage* page) const {
61101
EventZUnmap event;
62102
const size_t unmapped = page->size();
@@ -70,15 +110,9 @@ void XUnmapper::do_unmap_and_destroy_page(XPage* page) const {
70110
}
71111

72112
void XUnmapper::unmap_and_destroy_page(XPage* page) {
73-
// Asynchronous unmap and destroy is not supported with ZVerifyViews
74-
if (ZVerifyViews) {
75-
// Immediately unmap and destroy
113+
if (!try_enqueue(page)) {
114+
// Synchronously unmap and destroy
76115
do_unmap_and_destroy_page(page);
77-
} else {
78-
// Enqueue for asynchronous unmap and destroy
79-
XLocker<XConditionLock> locker(&_lock);
80-
_queue.insert_last(page);
81-
_lock.notify_all();
82116
}
83117
}
84118

src/hotspot/share/gc/x/xUnmapper.hpp

+5
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ class XUnmapper : public ConcurrentGCThread {
3636
XPageAllocator* const _page_allocator;
3737
XConditionLock _lock;
3838
XList<XPage> _queue;
39+
size_t _enqueued_bytes;
40+
bool _warned_sync_unmapping;
3941
bool _stop;
4042

4143
XPage* dequeue();
44+
bool try_enqueue(XPage* page);
45+
size_t queue_capacity() const;
46+
bool is_saturated() const;
4247
void do_unmap_and_destroy_page(XPage* page) const;
4348

4449
protected:

src/hotspot/share/gc/z/shared/z_shared_globals.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@
6161
"Uncommit memory if it has been unused for the specified " \
6262
"amount of time (in seconds)") \
6363
\
64+
product(double, ZAsyncUnmappingLimit, 100.0, DIAGNOSTIC, \
65+
"Specify the max amount (percentage of max heap size) of async " \
66+
"unmapping that can be in-flight before unmapping requests are " \
67+
"temporarily forced to be synchronous instead. " \
68+
"The default means after an amount of pages proportional to the " \
69+
"max capacity is enqueued, we resort to synchronous unmapping.") \
70+
\
6471
product(uint, ZStatisticsInterval, 10, DIAGNOSTIC, \
6572
"Time between statistics print outs (in seconds)") \
6673
range(1, (uint)-1) \

src/hotspot/share/gc/z/zUnmapper.cpp

+39-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "precompiled.hpp"
2525
#include "gc/shared/gc_globals.hpp"
26+
#include "gc/shared/gcLogPrecious.hpp"
2627
#include "gc/z/zList.inline.hpp"
2728
#include "gc/z/zLock.inline.hpp"
2829
#include "gc/z/zPage.inline.hpp"
@@ -35,6 +36,8 @@ ZUnmapper::ZUnmapper(ZPageAllocator* page_allocator)
3536
: _page_allocator(page_allocator),
3637
_lock(),
3738
_queue(),
39+
_enqueued_bytes(0),
40+
_warned_sync_unmapping(false),
3841
_stop(false) {
3942
set_name("ZUnmapper");
4043
create_and_start();
@@ -50,13 +53,45 @@ ZPage* ZUnmapper::dequeue() {
5053

5154
ZPage* const page = _queue.remove_first();
5255
if (page != nullptr) {
56+
_enqueued_bytes -= page->size();
5357
return page;
5458
}
5559

5660
_lock.wait();
5761
}
5862
}
5963

64+
bool ZUnmapper::try_enqueue(ZPage* page) {
65+
// Enqueue for asynchronous unmap and destroy
66+
ZLocker<ZConditionLock> locker(&_lock);
67+
if (is_saturated()) {
68+
// The unmapper thread is lagging behind and is unable to unmap memory fast enough
69+
if (!_warned_sync_unmapping) {
70+
_warned_sync_unmapping = true;
71+
log_warning_p(gc)("WARNING: Encountered synchronous unmapping because asynchronous unmapping could not keep up");
72+
}
73+
log_debug(gc, unmap)("Synchronous unmapping " SIZE_FORMAT "M page", page->size() / M);
74+
return false;
75+
}
76+
77+
log_trace(gc, unmap)("Asynchronous unmapping " SIZE_FORMAT "M page (" SIZE_FORMAT "M / " SIZE_FORMAT "M enqueued)",
78+
page->size() / M, _enqueued_bytes / M, queue_capacity() / M);
79+
80+
_queue.insert_last(page);
81+
_enqueued_bytes += page->size();
82+
_lock.notify_all();
83+
84+
return true;
85+
}
86+
87+
size_t ZUnmapper::queue_capacity() const {
88+
return align_up<size_t>(_page_allocator->max_capacity() * ZAsyncUnmappingLimit / 100.0, ZGranuleSize);
89+
}
90+
91+
bool ZUnmapper::is_saturated() const {
92+
return _enqueued_bytes >= queue_capacity();
93+
}
94+
6095
void ZUnmapper::do_unmap_and_destroy_page(ZPage* page) const {
6196
EventZUnmap event;
6297
const size_t unmapped = page->size();
@@ -70,10 +105,10 @@ void ZUnmapper::do_unmap_and_destroy_page(ZPage* page) const {
70105
}
71106

72107
void ZUnmapper::unmap_and_destroy_page(ZPage* page) {
73-
// Enqueue for asynchronous unmap and destroy
74-
ZLocker<ZConditionLock> locker(&_lock);
75-
_queue.insert_last(page);
76-
_lock.notify_all();
108+
if (!try_enqueue(page)) {
109+
// Synchronously unmap and destroy
110+
do_unmap_and_destroy_page(page);
111+
}
77112
}
78113

79114
void ZUnmapper::run_thread() {

src/hotspot/share/gc/z/zUnmapper.hpp

+5
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,14 @@ class ZUnmapper : public ZThread {
3636
ZPageAllocator* const _page_allocator;
3737
ZConditionLock _lock;
3838
ZList<ZPage> _queue;
39+
size_t _enqueued_bytes;
40+
bool _warned_sync_unmapping;
3941
bool _stop;
4042

4143
ZPage* dequeue();
44+
bool try_enqueue(ZPage* page);
45+
size_t queue_capacity() const;
46+
bool is_saturated() const;
4247
void do_unmap_and_destroy_page(ZPage* page) const;
4348

4449
protected:

src/hotspot/share/logging/logTag.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ class outputStream;
197197
LOG_TAG(tlab) \
198198
LOG_TAG(tracking) \
199199
LOG_TAG(unload) /* Trace unloading of classes */ \
200+
LOG_TAG(unmap) \
200201
LOG_TAG(unshareable) \
201202
NOT_PRODUCT(LOG_TAG(upcall)) \
202203
LOG_TAG(update) \

0 commit comments

Comments
 (0)