1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
49
50
51 Thread.currentThread().setContextClassLoader(origContextClassLoader);
52
53
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
79 assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
80
81
82
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
90
91
92 Thread.currentThread().setContextClassLoader(componentLoader1);
93 {
94
95
96
97
98
99 assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
100
101
102
103
104 ConvertUtils.register(floatConverter1, Float.TYPE);
105 assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
106 }
107 Thread.currentThread().setContextClassLoader(origContextClassLoader);
108
109
110
111 assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
112
113
114 Thread.currentThread().setContextClassLoader(componentLoader2);
115 {
116
117
118
119
120 assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
121 assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
122 }
123 Thread.currentThread().setContextClassLoader(origContextClassLoader);
124
125
126
127 WeakReference weakRefToComponent1 = new WeakReference(componentLoader1);
128 componentLoader1 = null;
129
130
131
132 forceGarbageCollection(weakRefToComponent1);
133 assertNull(
134 "Component classloader did not release properly; memory leak present",
135 weakRefToComponent1.get());
136 } finally {
137
138
139
140 Thread.currentThread().setContextClassLoader(origContextClassLoader);
141
142
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
168 assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
169
170
171
172 ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
173
174
175
176
177 Thread.currentThread().setContextClassLoader(componentLoader);
178 {
179
180
181
182 Class newFloatConverterClass = componentLoader.reload(FloatConverter.class);
183 Object newFloatConverter = newFloatConverterClass.newInstance();
184 assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader);
185
186
187
188
189 assertTrue(
190 "Converter loader via child does not implement parent type",
191 Converter.class.isInstance(newFloatConverter));
192
193
194
195 ConvertUtils.register((Converter)newFloatConverter, Float.TYPE);
196
197
198
199
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
208
209 Converter sharedConverter = ConvertUtils.lookup(Float.TYPE);
210 assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader);
211
212
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
220
221 WeakReference weakRefToComponent = new WeakReference(componentLoader);
222 componentLoader = null;
223
224
225
226 forceGarbageCollection(weakRefToComponent);
227 assertNull(
228 "Component classloader did not release properly; memory leak present",
229 weakRefToComponent.get());
230 } finally {
231
232
233
234 Thread.currentThread().setContextClassLoader(origContextClassLoader);
235
236
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
264
265
266
267
268 try {
269 byte[] b = new byte[bytes];
270 bytes = bytes * 2;
271 } catch(OutOfMemoryError e) {
272
273
274 break;
275 }
276 }
277
278
279
280 System.gc();
281 }
282 }