8277072: ObjectStreamClass caches keep ClassLoaders alive
Reviewed-by: rriggs, plevart
This commit is contained in:
parent
3e0b083f20
commit
8eb453baeb
87
src/java.base/share/classes/java/io/ClassCache.java
Normal file
87
src/java.base/share/classes/java/io/ClassCache.java
Normal file
@ -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.MethodHandles;
|
||||
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 @@ public class ObjectStreamClass implements Serializable {
|
||||
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,82 +2121,39 @@ public class ObjectStreamClass implements Serializable {
|
||||
{
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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() {
|
||||
@ -2331,19 +2161,9 @@ public class ObjectStreamClass implements Serializable {
|
||||
}
|
||||
|
||||
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))) &&
|
||||
return obj == this ||
|
||||
obj instanceof FieldReflectorKey other &&
|
||||
Arrays.equals(sigs, other.sigs);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
/* @test
|
||||
* @bug 8277072
|
||||
* @library /test/lib/
|
||||
* @summary ObjectStreamClass caches keep ClassLoaders alive
|
||||
* @run testng/othervm -Xmx10m -XX:SoftRefLRUPolicyMSPerMB=1 ObjectStreamClassCaching
|
||||
*/
|
||||
public class ObjectStreamClassCaching {
|
||||
|
||||
@Test
|
||||
public void testCachingEffectiveness() throws Exception {
|
||||
var ref = lookupObjectStreamClass(TestClass.class);
|
||||
System.gc();
|
||||
Thread.sleep(100L);
|
||||
// to trigger any ReferenceQueue processing...
|
||||
lookupObjectStreamClass(AnotherTestClass.class);
|
||||
assertFalse(ref.refersTo(null),
|
||||
"Cache lost entry although memory was not under pressure");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheReleaseUnderMemoryPressure() throws Exception {
|
||||
var ref = lookupObjectStreamClass(TestClass.class);
|
||||
pressMemoryHard(ref);
|
||||
System.gc();
|
||||
Thread.sleep(100L);
|
||||
assertTrue(ref.refersTo(null),
|
||||
"Cache still has entry although memory was pressed hard");
|
||||
}
|
||||
|
||||
// separate method so that the looked-up ObjectStreamClass is not kept on stack
|
||||
private static WeakReference<?> lookupObjectStreamClass(Class<?> cl) {
|
||||
return new WeakReference<>(ObjectStreamClass.lookup(cl));
|
||||
}
|
||||
|
||||
private static void pressMemoryHard(Reference<?> ref) {
|
||||
try {
|
||||
var list = new ArrayList<>();
|
||||
while (!ref.refersTo(null)) {
|
||||
list.add(new byte[1024 * 1024 * 64]); // 64 MiB chunks
|
||||
}
|
||||
} catch (OutOfMemoryError e) {
|
||||
// release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestClass implements Serializable {
|
||||
}
|
||||
|
||||
class AnotherTestClass implements Serializable {
|
||||
}
|
108
test/jdk/java/io/ObjectStreamClass/TestOSCClassLoaderLeak.java
Normal file
108
test/jdk/java/io/ObjectStreamClass/TestOSCClassLoaderLeak.java
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.ObjectStreamField;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import org.testng.annotations.Test;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import jdk.test.lib.util.ForceGC;
|
||||
|
||||
/* @test
|
||||
* @bug 8277072
|
||||
* @library /test/lib/
|
||||
* @build jdk.test.lib.util.ForceGC
|
||||
* @summary ObjectStreamClass caches keep ClassLoaders alive
|
||||
* @run testng TestOSCClassLoaderLeak
|
||||
*/
|
||||
public class TestOSCClassLoaderLeak {
|
||||
|
||||
@Test
|
||||
public void testClassLoaderLeak() throws Exception {
|
||||
TestClassLoader myOwnClassLoader = new TestClassLoader();
|
||||
Class<?> loadClass = myOwnClassLoader.loadClass("ObjectStreamClass_MemoryLeakExample");
|
||||
Constructor con = loadClass.getConstructor();
|
||||
con.setAccessible(true);
|
||||
Object objectStreamClass_MemoryLeakExample = con.newInstance();
|
||||
objectStreamClass_MemoryLeakExample.toString();
|
||||
|
||||
WeakReference<Object> myOwnClassLoaderWeakReference = new WeakReference<>(myOwnClassLoader);
|
||||
assertNotNull(myOwnClassLoaderWeakReference.get());
|
||||
objectStreamClass_MemoryLeakExample = null;
|
||||
myOwnClassLoader = null;
|
||||
loadClass = null;
|
||||
con = null;
|
||||
assertNotNull(myOwnClassLoaderWeakReference.get());
|
||||
|
||||
ForceGC gc = new ForceGC();
|
||||
assertTrue(gc.await(() -> myOwnClassLoaderWeakReference.get() == null));
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectStreamClass_MemoryLeakExample {
|
||||
private static final ObjectStreamField[] fields = ObjectStreamClass.lookup(TestClass.class).getFields();
|
||||
public ObjectStreamClass_MemoryLeakExample() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(fields);
|
||||
}
|
||||
}
|
||||
|
||||
class TestClassLoader extends ClassLoader {
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
if (name.equals("TestClass") || name.equals("ObjectStreamClass_MemoryLeakExample")) {
|
||||
byte[] bt = loadClassData(name);
|
||||
return defineClass(name, bt, 0, bt.length);
|
||||
} else {
|
||||
return super.loadClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] loadClassData(String className) {
|
||||
ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
|
||||
try (InputStream is = TestClassLoader.class.getClassLoader().getResourceAsStream(className.replace(".", "/") + ".class")) {
|
||||
int len = 0;
|
||||
while ((len = is.read()) != -1) {
|
||||
byteSt.write(len);
|
||||
}
|
||||
} catch (java.io.IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return byteSt.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
class TestClass implements Serializable {
|
||||
public String x;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user