Skip to content
Permalink
Browse files
8277072: ObjectStreamClass caches keep ClassLoaders alive
Reviewed-by: rriggs, plevart
  • Loading branch information
rkennke committed Dec 10, 2021
1 parent 3e0b083 commit 8eb453baebe377697286f7eb32280ca9f1fd7775
Showing 4 changed files with 311 additions and 213 deletions.
@@ -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());
}
}
}
@@ -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;
@@ -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) */
@@ -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);
}

/**
@@ -2248,102 +2121,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);
}
}

9 comments on commit 8eb453b

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 8eb453b Dec 10, 2021

Choose a reason for hiding this comment

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

@rkennke
Copy link
Contributor Author

@rkennke rkennke commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

/backport jdk18

@openjdk
Copy link

@openjdk openjdk bot commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

@rkennke the backport was successfully created on the branch rkennke-backport-8eb453ba in my personal fork of openjdk/jdk18. To create a pull request with this backport targeting openjdk/jdk18:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 8eb453ba from the openjdk/jdk repository.

The commit being backported was authored by Roman Kennke on 10 Dec 2021 and was reviewed by Roger Riggs and Peter Levart.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk18:

$ git fetch https://github.com/openjdk-bots/jdk18 rkennke-backport-8eb453ba:rkennke-backport-8eb453ba
$ git checkout rkennke-backport-8eb453ba
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk18 rkennke-backport-8eb453ba

@rkennke
Copy link
Contributor Author

@rkennke rkennke commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

/backport jdk17u-dev

@openjdk
Copy link

@openjdk openjdk bot commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

@rkennke the backport was successfully created on the branch rkennke-backport-8eb453ba in my personal fork of openjdk/jdk17u-dev. To create a pull request with this backport targeting openjdk/jdk17u-dev:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 8eb453ba from the openjdk/jdk repository.

The commit being backported was authored by Roman Kennke on 10 Dec 2021 and was reviewed by Roger Riggs and Peter Levart.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk17u-dev:

$ git fetch https://github.com/openjdk-bots/jdk17u-dev rkennke-backport-8eb453ba:rkennke-backport-8eb453ba
$ git checkout rkennke-backport-8eb453ba
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk17u-dev rkennke-backport-8eb453ba

@rkennke
Copy link
Contributor Author

@rkennke rkennke commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

/backport jdk11u-dev

@openjdk
Copy link

@openjdk openjdk bot commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

@rkennke Could not automatically backport 8eb453ba to openjdk/jdk11u-dev due to conflicts in the following files:

  • src/java.base/share/classes/java/io/ObjectStreamClass.java

To manually resolve these conflicts run the following commands in your personal fork of openjdk/jdk11u-dev:

$ git checkout -b rkennke-backport-8eb453ba
$ git fetch --no-tags https://git.openjdk.java.net/jdk 8eb453baebe377697286f7eb32280ca9f1fd7775
$ git cherry-pick --no-commit 8eb453baebe377697286f7eb32280ca9f1fd7775
$ # Resolve conflicts
$ git add files/with/resolved/conflicts
$ git commit -m 'Backport 8eb453baebe377697286f7eb32280ca9f1fd7775'

Once you have resolved the conflicts as explained above continue with creating a pull request towards the openjdk/jdk11u-dev with the title Backport 8eb453baebe377697286f7eb32280ca9f1fd7775.

@rkennke
Copy link
Contributor Author

@rkennke rkennke commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

/backport jdk18u

@openjdk
Copy link

@openjdk openjdk bot commented on 8eb453b Mar 9, 2022

Choose a reason for hiding this comment

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

@rkennke the backport was successfully created on the branch rkennke-backport-8eb453ba in my personal fork of openjdk/jdk18u. To create a pull request with this backport targeting openjdk/jdk18u:master, just click the following link:

➡️ Create pull request

The title of the pull request is automatically filled in correctly and below you find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit 8eb453ba from the openjdk/jdk repository.

The commit being backported was authored by Roman Kennke on 10 Dec 2021 and was reviewed by Roger Riggs and Peter Levart.

Thanks!

If you need to update the source branch of the pull then run the following commands in a local clone of your personal fork of openjdk/jdk18u:

$ git fetch https://github.com/openjdk-bots/jdk18u rkennke-backport-8eb453ba:rkennke-backport-8eb453ba
$ git checkout rkennke-backport-8eb453ba
# make changes
$ git add paths/to/changed/files
$ git commit --message 'Describe additional changes made'
$ git push https://github.com/openjdk-bots/jdk18u rkennke-backport-8eb453ba

Please sign in to comment.