Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
8277072: ObjectStreamClass caches keep ClassLoaders alive
Backport-of: 8eb453baebe377697286f7eb32280ca9f1fd7775
  • Loading branch information
rkennke committed Mar 25, 2022
1 parent ba6f733 commit 7d132f5
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 213 deletions.
87 changes: 87 additions & 0 deletions src/java.base/share/classes/java/io/ClassCache.java
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/

package java.io;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;

// Maps Class instances to values of type T. Under memory pressure, the
// mapping is released (under soft references GC policy) and would be
// recomputed the next time it is queried. The mapping is bound to the
// lifetime of the class: when the class is unloaded, the mapping is
// removed too.
abstract class ClassCache<T> {

private static class CacheRef<T> extends SoftReference<T> {
private final Class<?> type;

CacheRef(T referent, ReferenceQueue<T> queue, Class<?> type) {
super(referent, queue);
this.type = type;
}

Class<?> getType() {
return type;
}
}

private final ReferenceQueue<T> queue;
private final ClassValue<SoftReference<T>> map;

protected abstract T computeValue(Class<?> cl);

protected ClassCache() {
queue = new ReferenceQueue<>();
map = new ClassValue<>() {
@Override
protected SoftReference<T> computeValue(Class<?> type) {
return new CacheRef<>(ClassCache.this.computeValue(type), 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 (val == null);
return val;
}

private void processQueue() {
Reference<? extends T> ref;
while((ref = queue.poll()) != null) {
CacheRef<? extends T> cacheRef = (CacheRef<? extends T>)ref;
map.remove(cacheRef.getType());
}
}
}
246 changes: 33 additions & 213 deletions src/java.base/share/classes/java/io/ObjectStreamClass.java
Expand Up @@ -30,7 +30,6 @@
import java.lang.invoke.MethodType;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
Expand Down Expand Up @@ -108,19 +107,22 @@ public class ObjectStreamClass implements Serializable {

private static class Caches {
/** cache mapping local classes -> descriptors */
static final ConcurrentMap<WeakClassKey,Reference<?>> localDescs =
new ConcurrentHashMap<>();
static final ClassCache<ObjectStreamClass> localDescs =
new ClassCache<>() {
@Override
protected ObjectStreamClass computeValue(Class<?> type) {
return new ObjectStreamClass(type);
}
};

/** cache mapping field group/local desc pairs -> field reflectors */
static final ConcurrentMap<FieldReflectorKey,Reference<?>> reflectors =
new ConcurrentHashMap<>();

/** queue for WeakReferences to local classes */
private static final ReferenceQueue<Class<?>> localDescsQueue =
new ReferenceQueue<>();
/** queue for WeakReferences to field reflectors keys */
private static final ReferenceQueue<Class<?>> reflectorsQueue =
new ReferenceQueue<>();
static final ClassCache<Map<FieldReflectorKey, FieldReflector>> reflectors =
new ClassCache<>() {
@Override
protected Map<FieldReflectorKey, FieldReflector> computeValue(Class<?> type) {
return new ConcurrentHashMap<>();
}
};
}

/** class associated with this descriptor (if any) */
Expand Down Expand Up @@ -362,136 +364,7 @@ static ObjectStreamClass lookup(Class<?> cl, boolean all) {
if (!(all || Serializable.class.isAssignableFrom(cl))) {
return null;
}
processQueue(Caches.localDescsQueue, Caches.localDescs);
WeakClassKey key = new WeakClassKey(cl, Caches.localDescsQueue);
Reference<?> ref = Caches.localDescs.get(key);
Object entry = null;
if (ref != null) {
entry = ref.get();
}
EntryFuture future = null;
if (entry == null) {
EntryFuture newEntry = new EntryFuture();
Reference<?> newRef = new SoftReference<>(newEntry);
do {
if (ref != null) {
Caches.localDescs.remove(key, ref);
}
ref = Caches.localDescs.putIfAbsent(key, newRef);
if (ref != null) {
entry = ref.get();
}
} while (ref != null && entry == null);
if (entry == null) {
future = newEntry;
}
}

if (entry instanceof ObjectStreamClass) { // check common case first
return (ObjectStreamClass) entry;
}
if (entry instanceof EntryFuture) {
future = (EntryFuture) entry;
if (future.getOwner() == Thread.currentThread()) {
/*
* Handle nested call situation described by 4803747: waiting
* for future value to be set by a lookup() call further up the
* stack will result in deadlock, so calculate and set the
* future value here instead.
*/
entry = null;
} else {
entry = future.get();
}
}
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
if (future.set(entry)) {
Caches.localDescs.put(key, new SoftReference<>(entry));
} else {
// nested lookup call already set future
entry = future.get();
}
}

if (entry instanceof ObjectStreamClass) {
return (ObjectStreamClass) entry;
} else if (entry instanceof RuntimeException) {
throw (RuntimeException) entry;
} else if (entry instanceof Error) {
throw (Error) entry;
} else {
throw new InternalError("unexpected entry: " + entry);
}
}

/**
* Placeholder used in class descriptor and field reflector lookup tables
* for an entry in the process of being initialized. (Internal) callers
* which receive an EntryFuture belonging to another thread as the result
* of a lookup should call the get() method of the EntryFuture; this will
* return the actual entry once it is ready for use and has been set(). To
* conserve objects, EntryFutures synchronize on themselves.
*/
private static class EntryFuture {

private static final Object unset = new Object();
private final Thread owner = Thread.currentThread();
private Object entry = unset;

/**
* Attempts to set the value contained by this EntryFuture. If the
* EntryFuture's value has not been set already, then the value is
* saved, any callers blocked in the get() method are notified, and
* true is returned. If the value has already been set, then no saving
* or notification occurs, and false is returned.
*/
synchronized boolean set(Object entry) {
if (this.entry != unset) {
return false;
}
this.entry = entry;
notifyAll();
return true;
}

/**
* Returns the value contained by this EntryFuture, blocking if
* necessary until a value is set.
*/
@SuppressWarnings("removal")
synchronized Object get() {
boolean interrupted = false;
while (entry == unset) {
try {
wait();
} catch (InterruptedException ex) {
interrupted = true;
}
}
if (interrupted) {
AccessController.doPrivileged(
new PrivilegedAction<>() {
public Void run() {
Thread.currentThread().interrupt();
return null;
}
}
);
}
return entry;
}

/**
* Returns the thread that created this EntryFuture.
*/
Thread getOwner() {
return owner;
}
return Caches.localDescs.get(cl);
}

/**
Expand Down Expand Up @@ -2250,102 +2123,49 @@ private static FieldReflector getReflector(ObjectStreamField[] fields,
{
// class irrelevant if no fields
Class<?> cl = (localDesc != null && fields.length > 0) ?
localDesc.cl : null;
processQueue(Caches.reflectorsQueue, Caches.reflectors);
FieldReflectorKey key = new FieldReflectorKey(cl, fields,
Caches.reflectorsQueue);
Reference<?> ref = Caches.reflectors.get(key);
Object entry = null;
if (ref != null) {
entry = ref.get();
}
EntryFuture future = null;
if (entry == null) {
EntryFuture newEntry = new EntryFuture();
Reference<?> newRef = new SoftReference<>(newEntry);
do {
if (ref != null) {
Caches.reflectors.remove(key, ref);
}
ref = Caches.reflectors.putIfAbsent(key, newRef);
if (ref != null) {
entry = ref.get();
}
} while (ref != null && entry == null);
if (entry == null) {
future = newEntry;
}
}
localDesc.cl : Void.class;

if (entry instanceof FieldReflector) { // check common case first
return (FieldReflector) entry;
} else if (entry instanceof EntryFuture) {
entry = ((EntryFuture) entry).get();
} else if (entry == null) {
try {
entry = new FieldReflector(matchFields(fields, localDesc));
} catch (Throwable th) {
entry = th;
}
future.set(entry);
Caches.reflectors.put(key, new SoftReference<>(entry));
}

if (entry instanceof FieldReflector) {
return (FieldReflector) entry;
} else if (entry instanceof InvalidClassException) {
throw (InvalidClassException) entry;
} else if (entry instanceof RuntimeException) {
throw (RuntimeException) entry;
} else if (entry instanceof Error) {
throw (Error) entry;
} else {
throw new InternalError("unexpected entry: " + entry);
var clReflectors = Caches.reflectors.get(cl);
var key = new FieldReflectorKey(fields);
var reflector = clReflectors.get(key);
if (reflector == null) {
reflector = new FieldReflector(matchFields(fields, localDesc));
var oldReflector = clReflectors.putIfAbsent(key, reflector);
if (oldReflector != null) {
reflector = oldReflector;
}
}
return reflector;
}

/**
* FieldReflector cache lookup key. Keys are considered equal if they
* refer to the same class and equivalent field formats.
* refer to equivalent field formats.
*/
private static class FieldReflectorKey extends WeakReference<Class<?>> {
private static class FieldReflectorKey {

private final String[] sigs;
private final int hash;
private final boolean nullClass;

FieldReflectorKey(Class<?> cl, ObjectStreamField[] fields,
ReferenceQueue<Class<?>> queue)
FieldReflectorKey(ObjectStreamField[] fields)
{
super(cl, queue);
nullClass = (cl == null);
sigs = new String[2 * fields.length];
for (int i = 0, j = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
sigs[j++] = f.getName();
sigs[j++] = f.getSignature();
}
hash = System.identityHashCode(cl) + Arrays.hashCode(sigs);
hash = Arrays.hashCode(sigs);
}

public int hashCode() {
return hash;
}

public boolean equals(Object obj) {
if (obj == this) {
return true;
}

if (obj instanceof FieldReflectorKey other) {
Class<?> referent;
return (nullClass ? other.nullClass
: ((referent = get()) != null) &&
(other.refersTo(referent))) &&
Arrays.equals(sigs, other.sigs);
} else {
return false;
}
return obj == this ||
obj instanceof FieldReflectorKey other &&
Arrays.equals(sigs, other.sigs);
}
}

Expand Down

1 comment on commit 7d132f5

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.