Use a weak reference for the cached class loader (#71)
This replaces the strong reference to the class loader, `thisClassLoader`, with a weak one. The strong ref shows up as causing a GC root after unloading a web app in Tomcat that uses this library. With these modifications, the GC root is gone...
This commit is contained in:
committed by
GitHub
parent
9466284235
commit
e7b328d7e0
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache license, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the license for the specific language governing permissions and
|
||||
* limitations under the license.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.logging;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
// after: https://github.com/apache/logging-log4j2/blob/c47e98423b461731f7791fcb9ea1079cd451f365/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
|
||||
public final class GarbageCollectionHelper implements Closeable, Runnable {
|
||||
private static final OutputStream SINK = new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
}
|
||||
};
|
||||
private final AtomicBoolean running = new AtomicBoolean();
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private final Thread gcThread = new Thread(new GcTask());
|
||||
|
||||
class GcTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (running.get()) {
|
||||
// Allocate data to help suggest a GC
|
||||
try {
|
||||
// 1mb of heap
|
||||
byte[] buf = new byte[1024 * 1024];
|
||||
SINK.write(buf);
|
||||
} catch (final IOException ignored) {
|
||||
}
|
||||
// May no-op depending on the JVM configuration
|
||||
System.gc();
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (running.compareAndSet(false, true)) {
|
||||
gcThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
running.set(false);
|
||||
try {
|
||||
junit.framework.TestCase.assertTrue("GarbageCollectionHelper did not shut down cleanly",
|
||||
latch.await(10, TimeUnit.SECONDS));
|
||||
} catch (final InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.logging;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class LogFactoryWeakReferenceTestCase extends TestCase {
|
||||
private static final long MAX_WAIT_FOR_REF_NULLED_BY_GC = 15000;
|
||||
|
||||
public void testNotLeakingThisClassLoader() throws Exception {
|
||||
// create an isolated loader
|
||||
PathableClassLoader loader = new PathableClassLoader(null);
|
||||
loader.addLogicalLib("commons-logging");
|
||||
|
||||
// load the LogFactory class through this loader
|
||||
Class<?> logFactoryClass = loader.loadClass(LogFactory.class.getName());
|
||||
|
||||
// reflection hacks to obtain the weak reference
|
||||
Field field = logFactoryClass.getDeclaredField("thisClassLoaderRef");
|
||||
field.setAccessible(true);
|
||||
WeakReference thisClassLoaderRef = (WeakReference) field.get(null);
|
||||
|
||||
// the ref should at this point contain the loader
|
||||
assertSame(loader, thisClassLoaderRef.get());
|
||||
|
||||
// null out the hard refs
|
||||
field = null;
|
||||
logFactoryClass = null;
|
||||
loader.close();
|
||||
loader = null;
|
||||
|
||||
GarbageCollectionHelper gcHelper = new GarbageCollectionHelper();
|
||||
gcHelper.run();
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
while (thisClassLoaderRef.get() != null) {
|
||||
if (System.currentTimeMillis() - start > MAX_WAIT_FOR_REF_NULLED_BY_GC) {
|
||||
fail("After waiting " + MAX_WAIT_FOR_REF_NULLED_BY_GC + "ms, the weak ref still yields a non-null value.");
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
} finally {
|
||||
gcHelper.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user