@@ -101,8 +101,15 @@ static boolean unaligned() {
101
101
// increasing delay before throwing OutOfMemoryError:
102
102
// 1, 2, 4, 8, 16, 32, 64, 128, 256 (total 511 ms ~ 0.5 s)
103
103
// which means that OOME will be thrown after 0.5 s of trying
104
+ private static final long INITIAL_SLEEP = 1 ;
104
105
private static final int MAX_SLEEPS = 9 ;
105
106
107
+ private static final Object RESERVE_SLOWPATH_LOCK = new Object ();
108
+
109
+ // Token for detecting whether some other thread has done a GC since the
110
+ // last time the checking thread went around the retry-with-GC loop.
111
+ private static int RESERVE_GC_EPOCH = 0 ; // Never negative.
112
+
106
113
// These methods should be called whenever direct memory is allocated or
107
114
// freed. They allow the user to control the amount of direct memory
108
115
// which a process may access. All sizes are specified in bytes.
@@ -118,29 +125,45 @@ static void reserveMemory(long size, long cap) {
118
125
return ;
119
126
}
120
127
121
- final JavaLangRefAccess jlra = SharedSecrets .getJavaLangRefAccess ();
128
+ // Don't completely discard interruptions. Instead, record them and
129
+ // reapply when we're done here (whether successfully or OOME).
122
130
boolean interrupted = false ;
123
131
try {
124
-
125
- // Retry allocation until success or there are no more
126
- // references (including Cleaners that might free direct
127
- // buffer memory) to process and allocation still fails.
128
- boolean refprocActive ;
129
- do {
132
+ // Keep trying to reserve until either succeed or there is no
133
+ // further cleaning available from prior GCs. If the latter then
134
+ // GC to hopefully find more cleaning to do. Once a thread GCs it
135
+ // drops to the later retry with backoff loop.
136
+ for (int cleanedEpoch = -1 ; true ; ) {
137
+ synchronized (RESERVE_SLOWPATH_LOCK ) {
138
+ // Test if cleaning for prior GCs (from here) is complete.
139
+ // If so, GC to produce more cleaning work, and change
140
+ // the token to inform other threads that there may be
141
+ // more cleaning work to do. This is done under the lock
142
+ // to close a race. We could have multiple threads pass
143
+ // the test "simultaneously", resulting in back-to-back
144
+ // GCs. For a STW GC the window is small, but for a
145
+ // concurrent GC it's quite large. If a thread were to
146
+ // somehow be stuck trying to take the lock while enough
147
+ // other threads succeeded for the epoch to wrap, it just
148
+ // does an excess GC.
149
+ if (RESERVE_GC_EPOCH == cleanedEpoch ) {
150
+ // Increment with overflow to 0, so the value can
151
+ // never equal the initial/reset cleanedEpoch value.
152
+ RESERVE_GC_EPOCH = Integer .max (0 , RESERVE_GC_EPOCH + 1 );
153
+ System .gc ();
154
+ break ;
155
+ }
156
+ cleanedEpoch = RESERVE_GC_EPOCH ;
157
+ }
130
158
try {
131
- refprocActive = jlra .waitForReferenceProcessing ();
159
+ if (tryReserveOrClean (size , cap )) {
160
+ return ;
161
+ }
132
162
} catch (InterruptedException e ) {
133
- // Defer interrupts and keep trying.
134
163
interrupted = true ;
135
- refprocActive = true ;
136
- }
137
- if (tryReserveMemory (size , cap )) {
138
- return ;
164
+ cleanedEpoch = -1 ; // Reset when incomplete.
139
165
}
140
- } while (refprocActive );
141
-
142
- // trigger VM's Reference processing
143
- System .gc ();
166
+ }
144
167
145
168
// A retry loop with exponential back-off delays.
146
169
// Sometimes it would suffice to give up once reference
@@ -151,40 +174,53 @@ static void reserveMemory(long size, long cap) {
151
174
// DirectBufferAllocTest to (usually) succeed, while
152
175
// without it that test likely fails. Since failure here
153
176
// ends in OOME, there's no need to hurry.
154
- long sleepTime = 1 ;
155
- int sleeps = 0 ;
156
- while (true ) {
157
- if (tryReserveMemory (size , cap )) {
158
- return ;
159
- }
160
- if (sleeps >= MAX_SLEEPS ) {
161
- break ;
162
- }
177
+ for (int sleeps = 0 ; true ; ) {
163
178
try {
164
- if (!jlra .waitForReferenceProcessing ()) {
165
- Thread .sleep (sleepTime );
166
- sleepTime <<= 1 ;
167
- sleeps ++;
179
+ if (tryReserveOrClean (size , cap )) {
180
+ return ;
181
+ } else if (sleeps < MAX_SLEEPS ) {
182
+ Thread .sleep (INITIAL_SLEEP << sleeps );
183
+ ++sleeps ; // Only increment if sleep completed.
184
+ } else {
185
+ throw new OutOfMemoryError
186
+ ("Cannot reserve "
187
+ + size + " bytes of direct buffer memory (allocated: "
188
+ + RESERVED_MEMORY .get () + ", limit: " + MAX_MEMORY +")" );
168
189
}
169
190
} catch (InterruptedException e ) {
170
191
interrupted = true ;
171
192
}
172
193
}
173
194
174
- // no luck
175
- throw new OutOfMemoryError
176
- ("Cannot reserve "
177
- + size + " bytes of direct buffer memory (allocated: "
178
- + RESERVED_MEMORY .get () + ", limit: " + MAX_MEMORY +")" );
179
-
180
195
} finally {
196
+ // Reapply any deferred interruption.
181
197
if (interrupted ) {
182
- // don't swallow interrupts
183
198
Thread .currentThread ().interrupt ();
184
199
}
185
200
}
186
201
}
187
202
203
+ // Try to reserve memory, or failing that, try to make progress on
204
+ // cleaning. Returns true if successfully reserved memory, false if
205
+ // failed and ran out of cleaning work.
206
+ private static boolean tryReserveOrClean (long size , long cap )
207
+ throws InterruptedException
208
+ {
209
+ JavaLangRefAccess jlra = SharedSecrets .getJavaLangRefAccess ();
210
+ boolean progressing = true ;
211
+ while (true ) {
212
+ if (tryReserveMemory (size , cap )) {
213
+ return true ;
214
+ } else if (BufferCleaner .tryCleaning ()) {
215
+ progressing = true ;
216
+ } else if (!progressing ) {
217
+ return false ;
218
+ } else {
219
+ progressing = jlra .waitForReferenceProcessing ();
220
+ }
221
+ }
222
+ }
223
+
188
224
private static boolean tryReserveMemory (long size , long cap ) {
189
225
190
226
// -XX:MaxDirectMemorySize limits the total capacity rather than the
0 commit comments