1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils.converters;
19  
20  import java.lang.ref.WeakReference;
21  
22  import org.apache.commons.beanutils.Converter;
23  import org.apache.commons.beanutils.ConvertUtils;
24  import org.apache.commons.beanutils.ConversionException;
25  
26  import junit.framework.TestCase;
27  
28  /***
29   * This class provides a number of unit tests related to classloaders and
30   * garbage collection, particularly in j2ee-like situations.
31   */
32  public class MemoryTestCase extends TestCase {
33  
34      public void testWeakReference() throws Exception {
35          ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
36          try {
37          ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
38  
39          Thread.currentThread().setContextClassLoader(componentLoader);
40          Thread.currentThread().setContextClassLoader(origContextClassLoader);
41  
42          WeakReference ref = new WeakReference(componentLoader);
43          componentLoader = null;
44  
45          forceGarbageCollection(ref);
46          assertNull(ref.get());
47          } finally {
48              // Restore context classloader that was present before this
49              // test started. It is expected to be the same as the system
50              // classloader, but we handle all cases here..
51              Thread.currentThread().setContextClassLoader(origContextClassLoader);
52  
53              // and restore all the standard converters
54              ConvertUtils.deregister();
55          }
56      }
57  
58      /***
59       * Test whether registering a standard Converter instance while
60       * a custom context classloader is set causes a memory leak.
61       *
62       * <p>This test emulates a j2ee container where BeanUtils has been
63       * loaded from a "common" lib location that is shared across all
64       * components running within the container. The "component" registers
65       * a converter object, whose class was loaded from the "common" lib
66       * location. The registered converter:
67       * <ul>
68       * <li>should not be visible to other components; and</li>
69       * <li>should not prevent the component-specific classloader from being
70       *  garbage-collected when the container sets its reference to null.
71       * </ul>
72       *
73       */
74      public void testComponentRegistersStandardConverter() throws Exception {
75  
76          ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
77          try {
78              // sanity check; who's paranoid?? :-)
79              assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
80  
81              // create a custom classloader for a "component"
82              // just like a container would.
83              ClassLoader componentLoader1 = new ClassLoader() {};
84              ClassLoader componentLoader2 = new ClassLoader() {};
85  
86              Converter origFloatConverter = ConvertUtils.lookup(Float.TYPE);
87              Converter floatConverter1 = new FloatConverter();
88  
89              // Emulate the container invoking a component #1, and the component
90              // registering a custom converter instance whose class is
91              // available via the "shared" classloader.
92              Thread.currentThread().setContextClassLoader(componentLoader1);
93              {
94                  // here we pretend we're running inside component #1
95  
96                  // When we first do a ConvertUtils operation inside a custom
97                  // classloader, we get a completely fresh copy of the
98                  // ConvertUtilsBean, with all-new Converter objects in it..
99                  assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
100 
101                 // Now we register a custom converter (but of a standard class).
102                 // This should only affect code that runs with exactly the
103                 // same context classloader set.
104                 ConvertUtils.register(floatConverter1, Float.TYPE);
105                 assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
106             }
107             Thread.currentThread().setContextClassLoader(origContextClassLoader);
108 
109             // The converter visible outside any custom component should not
110             // have been altered.
111             assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
112 
113             // Emulate the container invoking a component #2.
114             Thread.currentThread().setContextClassLoader(componentLoader2);
115             {
116                 // here we pretend we're running inside component #2
117 
118                 // we should get a completely fresh ConvertUtilsBean, with
119                 // all-new Converter objects again.
120                 assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
121                 assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
122             }
123             Thread.currentThread().setContextClassLoader(origContextClassLoader);
124 
125             // Emulate a container "undeploying" component #1. This should
126             // make component loader available for garbage collection (we hope)
127             WeakReference weakRefToComponent1 = new WeakReference(componentLoader1);
128             componentLoader1 = null;
129 
130             // force garbage collection and  verify that the componentLoader
131             // has been garbage-collected
132             forceGarbageCollection(weakRefToComponent1);
133             assertNull(
134                 "Component classloader did not release properly; memory leak present",
135                 weakRefToComponent1.get());
136         } finally {
137             // Restore context classloader that was present before this
138             // test started, so that in case of a test failure we don't stuff
139             // up later tests...
140             Thread.currentThread().setContextClassLoader(origContextClassLoader);
141 
142             // and restore all the standard converters
143             ConvertUtils.deregister();
144         }
145     }
146 
147     /***
148      * Test whether registering a custom Converter subclass while
149      * a custom context classloader is set causes a memory leak.
150      *
151      * <p>This test emulates a j2ee container where BeanUtils has been
152      * loaded from a "common" lib location that is shared across all
153      * components running within the container. The "component" registers
154      * a converter object, whose class was loaded via the component-specific
155      * classloader. The registered converter:
156      * <ul>
157      * <li>should not be visible to other components; and</li>
158      * <li>should not prevent the component-specific classloader from being
159      *  garbage-collected when the container sets its reference to null.
160      * </ul>
161      *
162      */
163     public void testComponentRegistersCustomConverter() throws Exception {
164 
165         ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
166         try {
167             // sanity check; who's paranoid?? :-)
168             assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
169 
170             // create a custom classloader for a "component"
171             // just like a container would.
172             ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
173 
174             // Load a custom Converter via component loader. This emulates what
175             // would happen if a user wrote their own FloatConverter subclass
176             // and deployed it via the component-specific classpath.
177             Thread.currentThread().setContextClassLoader(componentLoader);
178             {
179               // Here we pretend we're running inside the component, and that
180               // a class FloatConverter has been loaded from the component's
181               // private classpath.
182               Class newFloatConverterClass = componentLoader.reload(FloatConverter.class);
183               Object newFloatConverter = newFloatConverterClass.newInstance();
184               assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader);
185 
186               // verify that this new object does implement the Converter type
187               // despite being loaded via a classloader different from the one
188               // that loaded the Converter class.
189               assertTrue(
190                 "Converter loader via child does not implement parent type",
191                 Converter.class.isInstance(newFloatConverter));
192 
193               // this converter registration will only apply to the
194               // componentLoader classloader...
195               ConvertUtils.register((Converter)newFloatConverter, Float.TYPE);
196 
197               // After registering a custom converter, lookup should return
198               // it back to us. We'll try this lookup again with a different
199               // context-classloader set, and shouldn't see it
200               Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
201               assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
202 
203               newFloatConverter = null;
204             }
205             Thread.currentThread().setContextClassLoader(origContextClassLoader);
206 
207             // Because the context classloader has been reset, we shouldn't
208             // see the custom registered converter here...
209             Converter sharedConverter = ConvertUtils.lookup(Float.TYPE);
210             assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader);
211 
212             // and here we should see it again
213             Thread.currentThread().setContextClassLoader(componentLoader);
214             {
215                 Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
216                 assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
217             }
218             Thread.currentThread().setContextClassLoader(origContextClassLoader);
219             // Emulate a container "undeploying" the component. This should
220             // make component loader available for garbage collection (we hope)
221             WeakReference weakRefToComponent = new WeakReference(componentLoader);
222             componentLoader = null;
223 
224             // force garbage collection and  verify that the componentLoader
225             // has been garbage-collected
226             forceGarbageCollection(weakRefToComponent);
227             assertNull(
228                 "Component classloader did not release properly; memory leak present",
229                 weakRefToComponent.get());
230         } finally {
231             // Restore context classloader that was present before this
232             // test started. It is expected to be the same as the system
233             // classloader, but we handle all cases here..
234             Thread.currentThread().setContextClassLoader(origContextClassLoader);
235 
236             // and restore all the standard converters
237             ConvertUtils.deregister();
238         }
239     }
240 
241     /***
242      * Attempt to force garbage collection of the specified target.
243      *
244      * <p>Unfortunately there is no way to force a JVM to perform
245      * garbage collection; all we can do is <i>hint</i> to it that
246      * garbage-collection would be a good idea, and to consume
247      * memory in order to trigger it.</p>
248      *
249      * <p>On return, target.get() will return null if the target has
250      * been garbage collected.</p>
251      *
252      * <p>If target.get() still returns non-null after this method has returned,
253      * then either there is some reference still being held to the target, or
254      * else we were not able to trigger garbage collection; there is no way
255      * to tell these scenarios apart.</p>
256      */
257     private void forceGarbageCollection(WeakReference target) {
258         int bytes = 2;
259 
260         while(target.get() != null) {
261             System.gc();
262 
263             // Create increasingly-large amounts of non-referenced memory
264             // in order to persuade the JVM to collect it. We are hoping
265             // here that the JVM is dumb enough to run a full gc pass over
266             // all data (including the target) rather than simply collecting
267             // this easily-reclaimable memory!
268             try {
269                 byte[] b =  new byte[bytes];
270                 bytes = bytes * 2;
271             } catch(OutOfMemoryError e) {
272                 // well that sure should have forced a garbage collection
273                 // run to occur!
274                 break;
275             }
276         }
277 
278         // and let's do one more just to clean up any garbage we might have
279         // created on the last pass..
280         System.gc();
281     }
282 }