| /* |
| * 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.beanutils2.converters; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.lang.ref.WeakReference; |
| |
| import org.apache.commons.beanutils2.ConvertUtils; |
| import org.apache.commons.beanutils2.Converter; |
| import org.junit.Test; |
| |
| /** |
| * This class provides a number of unit tests related to classloaders and |
| * garbage collection, particularly in j2ee-like situations. |
| * |
| * @version $Id$ |
| */ |
| public class MemoryTestCase { |
| |
| @Test |
| public void testWeakReference() throws Exception { |
| final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); |
| try { |
| ClassReloader componentLoader = new ClassReloader(origContextClassLoader); |
| |
| Thread.currentThread().setContextClassLoader(componentLoader); |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| final WeakReference<ClassLoader> ref = new WeakReference<ClassLoader>(componentLoader); |
| componentLoader = null; |
| |
| forceGarbageCollection(ref); |
| assertNull(ref.get()); |
| } finally { |
| // Restore context classloader that was present before this |
| // test started. It is expected to be the same as the system |
| // classloader, but we handle all cases here.. |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // and restore all the standard converters |
| ConvertUtils.deregister(); |
| } |
| } |
| |
| /** |
| * Test whether registering a standard Converter instance while |
| * a custom context classloader is set causes a memory leak. |
| * |
| * <p>This test emulates a j2ee container where BeanUtils has been |
| * loaded from a "common" lib location that is shared across all |
| * components running within the container. The "component" registers |
| * a converter object, whose class was loaded from the "common" lib |
| * location. The registered converter: |
| * <ul> |
| * <li>should not be visible to other components; and</li> |
| * <li>should not prevent the component-specific classloader from being |
| * garbage-collected when the container sets its reference to null. |
| * </ul> |
| * |
| */ |
| @Test |
| public void testComponentRegistersStandardConverter() throws Exception { |
| |
| final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); |
| try { |
| // sanity check; who's paranoid?? :-) |
| assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader()); |
| |
| // create a custom classloader for a "component" |
| // just like a container would. |
| ClassLoader componentLoader1 = new ClassLoader() {}; |
| final ClassLoader componentLoader2 = new ClassLoader() {}; |
| |
| final Converter origFloatConverter = ConvertUtils.lookup(Float.TYPE); |
| final Converter floatConverter1 = new FloatConverter(); |
| |
| // Emulate the container invoking a component #1, and the component |
| // registering a custom converter instance whose class is |
| // available via the "shared" classloader. |
| Thread.currentThread().setContextClassLoader(componentLoader1); |
| { |
| // here we pretend we're running inside component #1 |
| |
| // When we first do a ConvertUtils operation inside a custom |
| // classloader, we get a completely fresh copy of the |
| // ConvertUtilsBean, with all-new Converter objects in it.. |
| assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); |
| |
| // Now we register a custom converter (but of a standard class). |
| // This should only affect code that runs with exactly the |
| // same context classloader set. |
| ConvertUtils.register(floatConverter1, Float.TYPE); |
| assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1); |
| } |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // The converter visible outside any custom component should not |
| // have been altered. |
| assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); |
| |
| // Emulate the container invoking a component #2. |
| Thread.currentThread().setContextClassLoader(componentLoader2); |
| { |
| // here we pretend we're running inside component #2 |
| |
| // we should get a completely fresh ConvertUtilsBean, with |
| // all-new Converter objects again. |
| assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter); |
| assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1); |
| } |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // Emulate a container "undeploying" component #1. This should |
| // make component loader available for garbage collection (we hope) |
| final WeakReference<ClassLoader> weakRefToComponent1 = new WeakReference<>(componentLoader1); |
| componentLoader1 = null; |
| |
| // force garbage collection and verify that the componentLoader |
| // has been garbage-collected |
| forceGarbageCollection(weakRefToComponent1); |
| assertNull( |
| "Component classloader did not release properly; memory leak present", |
| weakRefToComponent1.get()); |
| } finally { |
| // Restore context classloader that was present before this |
| // test started, so that in case of a test failure we don't stuff |
| // up later tests... |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // and restore all the standard converters |
| ConvertUtils.deregister(); |
| } |
| } |
| |
| /** |
| * Test whether registering a custom Converter subclass while |
| * a custom context classloader is set causes a memory leak. |
| * |
| * <p>This test emulates a j2ee container where BeanUtils has been |
| * loaded from a "common" lib location that is shared across all |
| * components running within the container. The "component" registers |
| * a converter object, whose class was loaded via the component-specific |
| * classloader. The registered converter: |
| * <ul> |
| * <li>should not be visible to other components; and</li> |
| * <li>should not prevent the component-specific classloader from being |
| * garbage-collected when the container sets its reference to null. |
| * </ul> |
| * |
| */ |
| @Test |
| public void testComponentRegistersCustomConverter() throws Exception { |
| |
| final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader(); |
| try { |
| // sanity check; who's paranoid?? :-) |
| assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader()); |
| |
| // create a custom classloader for a "component" |
| // just like a container would. |
| ClassReloader componentLoader = new ClassReloader(origContextClassLoader); |
| |
| // Load a custom Converter via component loader. This emulates what |
| // would happen if a user wrote their own FloatConverter subclass |
| // and deployed it via the component-specific classpath. |
| Thread.currentThread().setContextClassLoader(componentLoader); |
| { |
| // Here we pretend we're running inside the component, and that |
| // a class FloatConverter has been loaded from the component's |
| // private classpath. |
| final Class<?> newFloatConverterClass = componentLoader.reload(FloatConverter.class); |
| Object newFloatConverter = newFloatConverterClass.newInstance(); |
| assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader); |
| |
| // verify that this new object does implement the Converter type |
| // despite being loaded via a classloader different from the one |
| // that loaded the Converter class. |
| assertTrue( |
| "Converter loader via child does not implement parent type", |
| Converter.class.isInstance(newFloatConverter)); |
| |
| // this converter registration will only apply to the |
| // componentLoader classloader... |
| ConvertUtils.register((Converter)newFloatConverter, Float.TYPE); |
| |
| // After registering a custom converter, lookup should return |
| // it back to us. We'll try this lookup again with a different |
| // context-classloader set, and shouldn't see it |
| final Converter componentConverter = ConvertUtils.lookup(Float.TYPE); |
| assertTrue(componentConverter.getClass().getClassLoader() == componentLoader); |
| |
| newFloatConverter = null; |
| } |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // Because the context classloader has been reset, we shouldn't |
| // see the custom registered converter here... |
| final Converter sharedConverter = ConvertUtils.lookup(Float.TYPE); |
| assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader); |
| |
| // and here we should see it again |
| Thread.currentThread().setContextClassLoader(componentLoader); |
| { |
| final Converter componentConverter = ConvertUtils.lookup(Float.TYPE); |
| assertTrue(componentConverter.getClass().getClassLoader() == componentLoader); |
| } |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| // Emulate a container "undeploying" the component. This should |
| // make component loader available for garbage collection (we hope) |
| final WeakReference<ClassLoader> weakRefToComponent = new WeakReference<ClassLoader>(componentLoader); |
| componentLoader = null; |
| |
| // force garbage collection and verify that the componentLoader |
| // has been garbage-collected |
| forceGarbageCollection(weakRefToComponent); |
| assertNull( |
| "Component classloader did not release properly; memory leak present", |
| weakRefToComponent.get()); |
| } finally { |
| // Restore context classloader that was present before this |
| // test started. It is expected to be the same as the system |
| // classloader, but we handle all cases here.. |
| Thread.currentThread().setContextClassLoader(origContextClassLoader); |
| |
| // and restore all the standard converters |
| ConvertUtils.deregister(); |
| } |
| } |
| |
| /** |
| * Attempt to force garbage collection of the specified target. |
| * |
| * <p>Unfortunately there is no way to force a JVM to perform |
| * garbage collection; all we can do is <i>hint</i> to it that |
| * garbage-collection would be a good idea, and to consume |
| * memory in order to trigger it.</p> |
| * |
| * <p>On return, target.get() will return null if the target has |
| * been garbage collected.</p> |
| * |
| * <p>If target.get() still returns non-null after this method has returned, |
| * then either there is some reference still being held to the target, or |
| * else we were not able to trigger garbage collection; there is no way |
| * to tell these scenarios apart.</p> |
| */ |
| private void forceGarbageCollection(final WeakReference<?> target) { |
| int bytes = 2; |
| |
| while(target.get() != null) { |
| System.gc(); |
| |
| // Create increasingly-large amounts of non-referenced memory |
| // in order to persuade the JVM to collect it. We are hoping |
| // here that the JVM is dumb enough to run a full gc pass over |
| // all data (including the target) rather than simply collecting |
| // this easily-reclaimable memory! |
| try { |
| @SuppressWarnings("unused") |
| final |
| byte[] b = new byte[bytes]; |
| bytes = bytes * 2; |
| } catch(final OutOfMemoryError e) { |
| // well that sure should have forced a garbage collection |
| // run to occur! |
| break; |
| } |
| } |
| |
| // and let's do one more just to clean up any garbage we might have |
| // created on the last pass.. |
| System.gc(); |
| } |
| } |