1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.beanutils2.memoryleaktests;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotNull;
21 import static org.junit.jupiter.api.Assertions.assertNotSame;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertSame;
24 import static org.junit.jupiter.api.Assumptions.assumeFalse;
25
26 import java.lang.ref.SoftReference;
27 import java.lang.ref.WeakReference;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.net.URLClassLoader;
31 import java.util.Locale;
32
33 import org.apache.commons.beanutils2.BeanUtilsBean;
34 import org.apache.commons.beanutils2.ConvertUtils;
35 import org.apache.commons.beanutils2.MappedPropertyDescriptor;
36 import org.apache.commons.beanutils2.MethodUtils;
37 import org.apache.commons.beanutils2.PropertyUtils;
38 import org.apache.commons.beanutils2.WrapDynaBean;
39 import org.apache.commons.beanutils2.WrapDynaClass;
40 import org.apache.commons.beanutils2.converters.IntegerConverter;
41 import org.apache.commons.beanutils2.locale.LocaleBeanUtilsBean;
42 import org.apache.commons.beanutils2.locale.LocaleConvertUtils;
43 import org.apache.commons.beanutils2.locale.converters.IntegerLocaleConverter;
44 import org.junit.jupiter.api.Test;
45
46
47
48
49
50
51 public class MemoryLeakTest {
52
53
54
55
56 private static URLClassLoader newClassLoader() throws MalformedURLException {
57
58 final String dataFilePath = MemoryLeakTest.class.getResource("pojotests").getFile();
59
60 final String location = "file://"
61 + dataFilePath.substring(0, dataFilePath.length() - "org.apache.commons.beanutils2.memoryleaktests.pojotests".length());
62
63
64 final StringBuilder newString = new StringBuilder();
65 for (int i = 0; i < location.length(); i++) {
66 if (location.charAt(i) == '\\') {
67 newString.append("/");
68 } else {
69 newString.append(location.charAt(i));
70 }
71 }
72 final String classLocation = newString.toString();
73
74
75 final URLClassLoader theLoader = URLClassLoader.newInstance(new URL[] { new URL(classLocation) }, null);
76 return theLoader;
77 }
78
79
80
81
82
83
84 private void clearAllBeanUtilsCaches() {
85
86
87 BeanUtilsBean.getInstance().getPropertyUtils().clearDescriptors();
88
89
90 LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getPropertyUtils().clearDescriptors();
91
92
93 MethodUtils.clearCache();
94
95
96 WrapDynaClass.clear();
97
98
99 BeanUtilsBean.setInstance(new BeanUtilsBean());
100
101
102 LocaleBeanUtilsBean.setInstance(new LocaleBeanUtilsBean());
103 }
104
105
106
107
108 private void forceGarbageCollection() throws Exception {
109
110 final SoftReference<Object> ref = new SoftReference<>(new Object());
111 int count = 0;
112 while (ref.get() != null && count++ < 5) {
113 java.util.ArrayList<String> list = new java.util.ArrayList<>();
114 try {
115 long i = 0;
116 while (ref.get() != null) {
117 list.add(
118 "A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String A Big String "
119 + i++);
120 }
121 System.out.println("Count(1) " + count + " : " + getMemoryStats());
122 } catch (final OutOfMemoryError ignored) {
123
124 }
125
126 System.gc();
127 list.clear();
128 list = null;
129 System.gc();
130 System.out.println("After GC2: " + getMemoryStats() + " Count " + count);
131 Thread.sleep(1000);
132 }
133
134 final boolean isNotNull = ref.get() != null;
135 System.out.println("Count " + count + " " + isNotNull);
136 final String message = "Your JVM is not releasing SoftReference, try running the test with less memory (-Xmx)";
137 assumeFalse(isNotNull, message);
138 }
139
140
141
142
143
144
145 private String getMemoryStats() {
146 final java.text.DecimalFormat fmt = new java.text.DecimalFormat("#,##0");
147 final Runtime runtime = Runtime.getRuntime();
148 final long free = runtime.freeMemory() / 1024;
149 final long total = runtime.totalMemory() / 1024;
150 final long used = total - free;
151 return "MEMORY - Total: " + fmt.format(total) + "k " + "Used: " + fmt.format(used) + "k " + "Free: " + fmt.format(free) + "k";
152 }
153
154
155
156
157
158
159
160
161 private void profilerLeakReport(final String test, final String className) {
162
163
164
165
166
167
168
169 }
170
171
172
173
174 @Test
175 public void testConvertUtilsBean_converters_memoryLeak() throws Exception {
176
177
178 clearAllBeanUtilsCaches();
179
180 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.CustomInteger";
181
182
183 ClassLoader loader = newClassLoader();
184 Class<?> beanClass = loader.loadClass(className);
185 Object bean = beanClass.newInstance();
186
187 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
188
189
190 assertNotNull(loader, "ClassLoader is null");
191 assertNotNull(beanClass, "BeanClass is null");
192 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
193 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
194
195
196
197 ConvertUtils.register(new IntegerConverter(), (Class<Integer>) beanClass);
198 assertEquals("12345", ConvertUtils.convert(bean, String.class));
199
200
201 loader = null;
202 beanClass = null;
203 bean = null;
204
205 forceGarbageCollection();
206
207 if (someRef.get() != null) {
208 profilerLeakReport("ConvertUtilsBean converters", className);
209 }
210
211
212 assertNull(someRef.get(), "ConvertUtilsBean is holding a reference to the classLoader");
213
214
215 clearAllBeanUtilsCaches();
216 }
217
218
219
220
221 @Test
222 public void testLocaleConvertUtilsBean_converters_memoryLeak() throws Exception {
223
224
225 clearAllBeanUtilsCaches();
226
227 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.CustomInteger";
228
229
230 ClassLoader loader = newClassLoader();
231 Class<?> beanClass = loader.loadClass(className);
232 Object bean = beanClass.newInstance();
233
234 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
235
236
237 assertNotNull(loader, "ClassLoader is null");
238 assertNotNull(beanClass, "BeanClass is null");
239 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
240 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
241
242
243
244 LocaleConvertUtils.register(IntegerLocaleConverter.builder().setLocale(Locale.US).setLocalizedPattern(false).get(), (Class<Integer>) beanClass,
245 Locale.US);
246 assertEquals(new Integer(12345), LocaleConvertUtils.convert(bean.toString(), Integer.class, Locale.US, "#,###"));
247
248
249 loader = null;
250 beanClass = null;
251 bean = null;
252
253 forceGarbageCollection();
254
255 if (someRef.get() != null) {
256 profilerLeakReport("LocaleConvertUtilsBean converters", className);
257 }
258
259
260 assertNull(someRef.get(), "LocaleConvertUtilsBean is holding a reference to the classLoader");
261
262
263 clearAllBeanUtilsCaches();
264 }
265
266
267
268
269 @Test
270 public void testMappedPropertyDescriptor_MappedMethodReference1() throws Exception {
271
272
273 clearAllBeanUtilsCaches();
274
275 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomeMappedPojo";
276 final ClassLoader loader = newClassLoader();
277 final Class<?> beanClass = loader.loadClass(className);
278 final Object bean = beanClass.newInstance();
279
280
281 assertNotNull(loader, "ClassLoader is null");
282 assertNotNull(beanClass, "BeanClass is null");
283 assertNotNull(bean, "Bean is null");
284 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
285 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
286
287 final MappedPropertyDescriptor descriptor = new MappedPropertyDescriptor("mappedProperty", beanClass);
288 assertNotNull(descriptor.getMappedReadMethod(), "1-Read Method null");
289 assertNotNull(descriptor.getMappedWriteMethod(), "1-Write Method null");
290 assertEquals("getMappedProperty", descriptor.getMappedReadMethod().getName(), "1-Read Method name");
291 assertEquals("setMappedProperty", descriptor.getMappedWriteMethod().getName(), "1-Read Write name");
292
293 forceGarbageCollection();
294
295
296
297
298
299
300
301 assertNotNull(descriptor.getMappedReadMethod(), "1-Read Method null");
302 assertNotNull(descriptor.getMappedWriteMethod(), "1-Write Method null");
303 assertEquals("getMappedProperty", descriptor.getMappedReadMethod().getName(), "1-Read Method name");
304 assertEquals("setMappedProperty", descriptor.getMappedWriteMethod().getName(), "1-Read Write name");
305
306
307 clearAllBeanUtilsCaches();
308 }
309
310
311
312
313 @Test
314 public void testMappedPropertyDescriptor_MappedMethodReference2() throws Exception {
315
316
317 clearAllBeanUtilsCaches();
318
319 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomeMappedPojo";
320 ClassLoader loader = newClassLoader();
321 Class<?> beanClass = loader.loadClass(className);
322 Object bean = beanClass.newInstance();
323
324
325 assertNotNull(loader, "ClassLoader is null");
326 assertNotNull(beanClass, "BeanClass is null");
327 assertNotNull(bean, "Bean is null");
328 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
329 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
330
331 final MappedPropertyDescriptor descriptor = new MappedPropertyDescriptor("mappedProperty", beanClass);
332 assertNotNull(descriptor.getMappedReadMethod(), "1-Read Method null");
333 assertNotNull(descriptor.getMappedWriteMethod(), "1-Write Method null");
334 assertEquals("getMappedProperty", descriptor.getMappedReadMethod().getName(), "1-Read Method name");
335 assertEquals("setMappedProperty", descriptor.getMappedWriteMethod().getName(), "1-Read Write name");
336
337
338 loader = null;
339 beanClass = null;
340 bean = null;
341
342 forceGarbageCollection();
343
344
345
346
347
348
349
350 assertNotNull(descriptor.getMappedReadMethod(), "1-Read Method null");
351 assertNotNull(descriptor.getMappedWriteMethod(), "1-Write Method null");
352 assertEquals("getMappedProperty", descriptor.getMappedReadMethod().getName(), "1-Read Method name");
353 assertEquals("setMappedProperty", descriptor.getMappedWriteMethod().getName(), "1-Read Write name");
354
355
356 clearAllBeanUtilsCaches();
357 }
358
359
360
361
362 @Test
363 public void testMethodUtils_cache_memoryLeak() throws Exception {
364
365
366 clearAllBeanUtilsCaches();
367
368 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomePojo";
369
370
371 ClassLoader loader = newClassLoader();
372 Class<?> beanClass = loader.loadClass(className);
373 Object bean = beanClass.newInstance();
374
375 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
376
377
378 assertNotNull(loader, "ClassLoader is null");
379 assertNotNull(beanClass, "BeanClass is null");
380 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
381 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
382
383
384
385 assertEquals("initialValue", MethodUtils.invokeExactMethod(bean, "getName", new Object[0]));
386
387
388 loader = null;
389 beanClass = null;
390 bean = null;
391
392 forceGarbageCollection();
393
394 if (someRef.get() != null) {
395 profilerLeakReport("MethodUtils cache", className);
396 }
397
398
399 assertNull(someRef.get(), "MethodUtils is holding a reference to the classLoader");
400
401
402 clearAllBeanUtilsCaches();
403 }
404
405
406
407
408 @Test
409 public void testPropertyUtilsBean_descriptorsCache_memoryLeak() throws Exception {
410
411
412 clearAllBeanUtilsCaches();
413
414 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomePojo";
415
416
417 ClassLoader loader = newClassLoader();
418 Class<?> beanClass = loader.loadClass(className);
419 Object bean = beanClass.newInstance();
420
421 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
422
423
424 assertNotNull(loader, "ClassLoader is null");
425 assertNotNull(beanClass, "BeanClass is null");
426 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
427 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
428
429
430
431 assertEquals("initialValue", PropertyUtils.getProperty(bean, "name"));
432
433
434 loader = null;
435 beanClass = null;
436 bean = null;
437
438 forceGarbageCollection();
439
440 if (someRef.get() != null) {
441 profilerLeakReport("PropertyUtilsBean descriptorsCache", className);
442 }
443
444
445 assertNull(someRef.get(), "PropertyUtilsBean is holding a reference to the classLoader");
446
447
448 clearAllBeanUtilsCaches();
449 }
450
451
452
453
454 @Test
455 public void testPropertyUtilsBean_mappedDescriptorsCache_memoryLeak() throws Exception {
456
457
458 clearAllBeanUtilsCaches();
459
460 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomeMappedPojo";
461
462
463 ClassLoader loader = newClassLoader();
464 Class<?> beanClass = loader.loadClass(className);
465 Object bean = beanClass.newInstance();
466
467 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
468
469
470 assertNotNull(loader, "ClassLoader is null");
471 assertNotNull(beanClass, "BeanClass is null");
472 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
473 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
474
475
476
477 assertEquals("Second Value", PropertyUtils.getProperty(bean, "mappedProperty(Second Key)"));
478 PropertyUtils.setProperty(bean, "mappedProperty(Second Key)", "New Second Value");
479 assertEquals("New Second Value", PropertyUtils.getProperty(bean, "mappedProperty(Second Key)"));
480
481
482 loader = null;
483 beanClass = null;
484 bean = null;
485
486
487
488
489
490 forceGarbageCollection();
491
492 if (someRef.get() != null) {
493 profilerLeakReport("PropertyUtilsBean mappedDescriptorsCache", className);
494 }
495
496
497 assertNull(someRef.get(), "PropertyUtilsBean is holding a reference to the classLoader");
498
499
500 clearAllBeanUtilsCaches();
501 }
502
503
504
505
506 @Test
507 public void testWrapDynaClass_dynaClasses_memoryLeak() throws Exception {
508
509
510 clearAllBeanUtilsCaches();
511
512 final String className = "org.apache.commons.beanutils2.memoryleaktests.pojotests.SomePojo";
513
514
515 ClassLoader loader = newClassLoader();
516 Class<?> beanClass = loader.loadClass(className);
517 Object bean = beanClass.newInstance();
518 WrapDynaBean wrapDynaBean = new WrapDynaBean(bean);
519
520 final WeakReference<ClassLoader> someRef = new WeakReference<>(loader);
521
522
523 assertNotNull(loader, "ClassLoader is null");
524 assertNotNull(beanClass, "BeanClass is null");
525 assertNotSame(getClass().getClassLoader(), beanClass.getClassLoader(), "ClassLoaders should be different..");
526 assertSame(beanClass.getClassLoader(), loader, "BeanClass ClassLoader incorrect");
527
528
529
530 assertEquals("initialValue", wrapDynaBean.get("name"));
531
532
533 loader = null;
534 beanClass = null;
535 bean = null;
536 wrapDynaBean = null;
537
538
539
540
541
542 forceGarbageCollection();
543
544 if (someRef.get() != null) {
545 profilerLeakReport("WrapDynaClass dynaClasses", className);
546 }
547
548
549 assertNull(someRef.get(), "WrapDynaClass is holding a reference to the classLoader");
550
551
552 clearAllBeanUtilsCaches();
553 }
554 }