8280041: Retry loop issues in java.io.ClassCache

Co-authored-by: Peter Levart <plevart@openjdk.org>
Reviewed-by: rkennke, rriggs, plevart
This commit is contained in:
Aleksey Shipilev 2022-01-25 19:22:07 +00:00
parent cbe8395ace
commit cebaad1c94
5 changed files with 202 additions and 12 deletions

View file

@ -28,6 +28,7 @@ package java.io;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Objects;
// Maps Class instances to values of type T. Under memory pressure, the
// mapping is released (under soft references GC policy) and would be
@ -38,19 +39,29 @@ abstract class ClassCache<T> {
private static class CacheRef<T> extends SoftReference<T> {
private final Class<?> type;
private T strongReferent;
CacheRef(T referent, ReferenceQueue<T> queue, Class<?> type) {
super(referent, queue);
this.type = type;
this.strongReferent = referent;
}
Class<?> getType() {
return type;
}
T getStrong() {
return strongReferent;
}
void clearStrong() {
strongReferent = null;
}
}
private final ReferenceQueue<T> queue;
private final ClassValue<SoftReference<T>> map;
private final ClassValue<CacheRef<T>> map;
protected abstract T computeValue(Class<?> cl);
@ -58,23 +69,41 @@ abstract class ClassCache<T> {
queue = new ReferenceQueue<>();
map = new ClassValue<>() {
@Override
protected SoftReference<T> computeValue(Class<?> type) {
return new CacheRef<>(ClassCache.this.computeValue(type), queue, type);
protected CacheRef<T> computeValue(Class<?> type) {
T v = ClassCache.this.computeValue(type);
Objects.requireNonNull(v);
return new CacheRef<>(v, queue, type);
}
};
}
T get(Class<?> cl) {
processQueue();
T val;
do {
SoftReference<T> ref = map.get(cl);
val = ref.get();
if (val == null) {
map.remove(cl);
while (true) {
processQueue();
CacheRef<T> ref = map.get(cl);
// Case 1: A recently created CacheRef.
// We might still have strong referent, and can return it.
// This guarantees progress for at least one thread on every CacheRef.
// Clear the strong referent before returning to make the cache soft.
T strongVal = ref.getStrong();
if (strongVal != null) {
ref.clearStrong();
return strongVal;
}
} while (val == null);
return val;
// Case 2: Older or recently cleared CacheRef.
// Check if its soft referent is still available, and return it.
T val = ref.get();
if (val != null) {
return val;
}
// Case 3: The reference was cleared.
// Clear the mapping and retry.
map.remove(cl);
}
}
private void processQueue() {