001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.beanutils.converters;
019:
020: import java.lang.ref.WeakReference;
021:
022: import org.apache.commons.beanutils.Converter;
023: import org.apache.commons.beanutils.ConvertUtils;
024: import org.apache.commons.beanutils.ConversionException;
025:
026: import junit.framework.TestCase;
027:
028: /**
029: * This class provides a number of unit tests related to classloaders and
030: * garbage collection, particularly in j2ee-like situations.
031: */
032: public class MemoryTestCase extends TestCase {
033:
034: public void testWeakReference() throws Exception {
035: ClassLoader origContextClassLoader = Thread.currentThread()
036: .getContextClassLoader();
037: try {
038: ClassReloader componentLoader = new ClassReloader(
039: origContextClassLoader);
040:
041: Thread.currentThread().setContextClassLoader(
042: componentLoader);
043: Thread.currentThread().setContextClassLoader(
044: origContextClassLoader);
045:
046: WeakReference ref = new WeakReference(componentLoader);
047: componentLoader = null;
048:
049: forceGarbageCollection(ref);
050: assertNull(ref.get());
051: } finally {
052: // Restore context classloader that was present before this
053: // test started. It is expected to be the same as the system
054: // classloader, but we handle all cases here..
055: Thread.currentThread().setContextClassLoader(
056: origContextClassLoader);
057:
058: // and restore all the standard converters
059: ConvertUtils.deregister();
060: }
061: }
062:
063: /**
064: * Test whether registering a standard Converter instance while
065: * a custom context classloader is set causes a memory leak.
066: *
067: * <p>This test emulates a j2ee container where BeanUtils has been
068: * loaded from a "common" lib location that is shared across all
069: * components running within the container. The "component" registers
070: * a converter object, whose class was loaded from the "common" lib
071: * location. The registered converter:
072: * <ul>
073: * <li>should not be visible to other components; and</li>
074: * <li>should not prevent the component-specific classloader from being
075: * garbage-collected when the container sets its reference to null.
076: * </ul>
077: *
078: */
079: public void testComponentRegistersStandardConverter()
080: throws Exception {
081:
082: ClassLoader origContextClassLoader = Thread.currentThread()
083: .getContextClassLoader();
084: try {
085: // sanity check; who's paranoid?? :-)
086: assertEquals(origContextClassLoader, ConvertUtils.class
087: .getClassLoader());
088:
089: // create a custom classloader for a "component"
090: // just like a container would.
091: ClassLoader componentLoader1 = new ClassLoader() {
092: };
093: ClassLoader componentLoader2 = new ClassLoader() {
094: };
095:
096: Converter origFloatConverter = ConvertUtils
097: .lookup(Float.TYPE);
098: Converter floatConverter1 = new FloatConverter();
099:
100: // Emulate the container invoking a component #1, and the component
101: // registering a custom converter instance whose class is
102: // available via the "shared" classloader.
103: Thread.currentThread().setContextClassLoader(
104: componentLoader1);
105: {
106: // here we pretend we're running inside component #1
107:
108: // When we first do a ConvertUtils operation inside a custom
109: // classloader, we get a completely fresh copy of the
110: // ConvertUtilsBean, with all-new Converter objects in it..
111: assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
112:
113: // Now we register a custom converter (but of a standard class).
114: // This should only affect code that runs with exactly the
115: // same context classloader set.
116: ConvertUtils.register(floatConverter1, Float.TYPE);
117: assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
118: }
119: Thread.currentThread().setContextClassLoader(
120: origContextClassLoader);
121:
122: // The converter visible outside any custom component should not
123: // have been altered.
124: assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
125:
126: // Emulate the container invoking a component #2.
127: Thread.currentThread().setContextClassLoader(
128: componentLoader2);
129: {
130: // here we pretend we're running inside component #2
131:
132: // we should get a completely fresh ConvertUtilsBean, with
133: // all-new Converter objects again.
134: assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
135: assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
136: }
137: Thread.currentThread().setContextClassLoader(
138: origContextClassLoader);
139:
140: // Emulate a container "undeploying" component #1. This should
141: // make component loader available for garbage collection (we hope)
142: WeakReference weakRefToComponent1 = new WeakReference(
143: componentLoader1);
144: componentLoader1 = null;
145:
146: // force garbage collection and verify that the componentLoader
147: // has been garbage-collected
148: forceGarbageCollection(weakRefToComponent1);
149: assertNull(
150: "Component classloader did not release properly; memory leak present",
151: weakRefToComponent1.get());
152: } finally {
153: // Restore context classloader that was present before this
154: // test started, so that in case of a test failure we don't stuff
155: // up later tests...
156: Thread.currentThread().setContextClassLoader(
157: origContextClassLoader);
158:
159: // and restore all the standard converters
160: ConvertUtils.deregister();
161: }
162: }
163:
164: /**
165: * Test whether registering a custom Converter subclass while
166: * a custom context classloader is set causes a memory leak.
167: *
168: * <p>This test emulates a j2ee container where BeanUtils has been
169: * loaded from a "common" lib location that is shared across all
170: * components running within the container. The "component" registers
171: * a converter object, whose class was loaded via the component-specific
172: * classloader. The registered converter:
173: * <ul>
174: * <li>should not be visible to other components; and</li>
175: * <li>should not prevent the component-specific classloader from being
176: * garbage-collected when the container sets its reference to null.
177: * </ul>
178: *
179: */
180: public void testComponentRegistersCustomConverter()
181: throws Exception {
182:
183: ClassLoader origContextClassLoader = Thread.currentThread()
184: .getContextClassLoader();
185: try {
186: // sanity check; who's paranoid?? :-)
187: assertEquals(origContextClassLoader, ConvertUtils.class
188: .getClassLoader());
189:
190: // create a custom classloader for a "component"
191: // just like a container would.
192: ClassReloader componentLoader = new ClassReloader(
193: origContextClassLoader);
194:
195: // Load a custom Converter via component loader. This emulates what
196: // would happen if a user wrote their own FloatConverter subclass
197: // and deployed it via the component-specific classpath.
198: Thread.currentThread().setContextClassLoader(
199: componentLoader);
200: {
201: // Here we pretend we're running inside the component, and that
202: // a class FloatConverter has been loaded from the component's
203: // private classpath.
204: Class newFloatConverterClass = componentLoader
205: .reload(FloatConverter.class);
206: Object newFloatConverter = newFloatConverterClass
207: .newInstance();
208: assertTrue(newFloatConverter.getClass()
209: .getClassLoader() == componentLoader);
210:
211: // verify that this new object does implement the Converter type
212: // despite being loaded via a classloader different from the one
213: // that loaded the Converter class.
214: assertTrue(
215: "Converter loader via child does not implement parent type",
216: Converter.class.isInstance(newFloatConverter));
217:
218: // this converter registration will only apply to the
219: // componentLoader classloader...
220: ConvertUtils.register((Converter) newFloatConverter,
221: Float.TYPE);
222:
223: // After registering a custom converter, lookup should return
224: // it back to us. We'll try this lookup again with a different
225: // context-classloader set, and shouldn't see it
226: Converter componentConverter = ConvertUtils
227: .lookup(Float.TYPE);
228: assertTrue(componentConverter.getClass()
229: .getClassLoader() == componentLoader);
230:
231: newFloatConverter = null;
232: }
233: Thread.currentThread().setContextClassLoader(
234: origContextClassLoader);
235:
236: // Because the context classloader has been reset, we shouldn't
237: // see the custom registered converter here...
238: Converter sharedConverter = ConvertUtils.lookup(Float.TYPE);
239: assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader);
240:
241: // and here we should see it again
242: Thread.currentThread().setContextClassLoader(
243: componentLoader);
244: {
245: Converter componentConverter = ConvertUtils
246: .lookup(Float.TYPE);
247: assertTrue(componentConverter.getClass()
248: .getClassLoader() == componentLoader);
249: }
250: Thread.currentThread().setContextClassLoader(
251: origContextClassLoader);
252: // Emulate a container "undeploying" the component. This should
253: // make component loader available for garbage collection (we hope)
254: WeakReference weakRefToComponent = new WeakReference(
255: componentLoader);
256: componentLoader = null;
257:
258: // force garbage collection and verify that the componentLoader
259: // has been garbage-collected
260: forceGarbageCollection(weakRefToComponent);
261: assertNull(
262: "Component classloader did not release properly; memory leak present",
263: weakRefToComponent.get());
264: } finally {
265: // Restore context classloader that was present before this
266: // test started. It is expected to be the same as the system
267: // classloader, but we handle all cases here..
268: Thread.currentThread().setContextClassLoader(
269: origContextClassLoader);
270:
271: // and restore all the standard converters
272: ConvertUtils.deregister();
273: }
274: }
275:
276: /**
277: * Attempt to force garbage collection of the specified target.
278: *
279: * <p>Unfortunately there is no way to force a JVM to perform
280: * garbage collection; all we can do is <i>hint</i> to it that
281: * garbage-collection would be a good idea, and to consume
282: * memory in order to trigger it.</p>
283: *
284: * <p>On return, target.get() will return null if the target has
285: * been garbage collected.</p>
286: *
287: * <p>If target.get() still returns non-null after this method has returned,
288: * then either there is some reference still being held to the target, or
289: * else we were not able to trigger garbage collection; there is no way
290: * to tell these scenarios apart.</p>
291: */
292: private void forceGarbageCollection(WeakReference target) {
293: int bytes = 2;
294:
295: while (target.get() != null) {
296: System.gc();
297:
298: // Create increasingly-large amounts of non-referenced memory
299: // in order to persuade the JVM to collect it. We are hoping
300: // here that the JVM is dumb enough to run a full gc pass over
301: // all data (including the target) rather than simply collecting
302: // this easily-reclaimable memory!
303: try {
304: byte[] b = new byte[bytes];
305: bytes = bytes * 2;
306: } catch (OutOfMemoryError e) {
307: // well that sure should have forced a garbage collection
308: // run to occur!
309: break;
310: }
311: }
312:
313: // and let's do one more just to clean up any garbage we might have
314: // created on the last pass..
315: System.gc();
316: }
317: }
|