/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.serial.memory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import junit.framework.TestCase;
import org.jboss.profiler.jvmti.JVMTIInterface;
import org.jboss.serial.classmetamodel.ClassMetaData;
import org.jboss.serial.classmetamodel.ClassMetamodelFactory;
import org.jboss.serial.io.JBossObjectInputStream;
import org.jboss.serial.io.JBossObjectOutputStream;
import org.jboss.serial.util.TestUtil;
/**
* This class requires -agentlib:jbossAgent on JVM arguments on the JVM for working properly
*
* It's also needed to set classes.location property as we need the classLoader finding this.
* @author csuconic
*
*/
public class MemoryLeakTestCase extends TestCase {
private static final String CLASS_NAME = "org.jboss.serial.memory.test.SomePojo";
private static JVMTIInterface jvmtiInterface = null;
static
{
JVMTIInterface jvmti = new JVMTIInterface();
if (jvmti.isActive())
{
jvmtiInterface = new JVMTIInterface();
}
}
public void testValidate() throws Exception
{
if (jvmtiInterface==null)
{
System.out.println("testValidate is only valid when using profiler");
return;
}
try
{
URLClassLoader theLoader = newClassLoader();
Class testClass = theLoader.loadClass(CLASS_NAME);
//testClass=null;
jvmtiInterface.forceGC();
assertNotNull("Class should still have a reference",jvmtiInterface.getClassByName(CLASS_NAME));
Object someInstance = testClass.newInstance();
WeakReference weakReference = new WeakReference(someInstance);
someInstance=null;
// 2 - because there is a static reference on the POJO itself
assertEquals(2,jvmtiInterface.getAllObjects(testClass).length);
jvmtiInterface.forceGC();
assertTrue(weakReference.get()==null);
// there is a static reference on the POJO itself
assertEquals(1,jvmtiInterface.getAllObjects(testClass).length);
testClass=null;
theLoader=null;
jvmtiInterface.forceGC();
Class clazz = jvmtiInterface.getClassByName(CLASS_NAME);
assertNull("Class should been unloaded by definition",clazz);
theLoader = newClassLoader();
WeakReference softReference = new WeakReference (theLoader.loadClass(CLASS_NAME));
Class strongReference = (Class)softReference.get();
theLoader=null;
jvmtiInterface.forceGC();
assertNotNull(jvmtiInterface.getClassByName(CLASS_NAME));
assertNotNull(softReference.get());
jvmtiInterface.forceGC();
assertNotNull(softReference.get());
jvmtiInterface.forceGC();
assertNotNull(softReference.get());
jvmtiInterface.forceGC();
assertNotNull(softReference.get());
jvmtiInterface.forceGC();
assertNotNull(softReference.get());
strongReference=null;
jvmtiInterface.forceGC();
assertNull(jvmtiInterface.getClassByName(CLASS_NAME));
}
catch (Exception e)
{
e.printStackTrace();
throw e;
}
}
private static URLClassLoader newClassLoader() throws MalformedURLException {
String dataFilePath = MemoryLeakTestCase.class.getResource("test").getFile();
String location = "file://" + dataFilePath.substring(0,dataFilePath.length()-"org.jboss.serial.memory.test".length());
StringBuffer newString = new StringBuffer();
for (int i=0;i<location.length();i++)
{
if (location.charAt(i)=='\\')
{
newString.append("/");
}
else
{
newString.append(location.charAt(i));
}
}
String classLocation = newString.toString();
URLClassLoader theLoader = URLClassLoader.newInstance(new URL[]{new URL(classLocation)},null);
return theLoader;
}
static class HierarchicalClassLoader extends ClassLoader
{
public HierarchicalClassLoader(ClassLoader parent)
{
super(parent);
}
}
private static ClassLoader newHierarchicalClassLoader(ClassLoader parent) throws MalformedURLException {
return new HierarchicalClassLoader(parent);
}
public void testMetaDataOnly() throws Exception
{
ClassMetamodelFactory.clear();
int count=0;
ArrayList classes = new ArrayList();
try
{
for (count=0;count<10;count++)
{
URLClassLoader loader = newClassLoader();
Class testClass = loader.loadClass(CLASS_NAME);
testClass.newInstance();
ClassMetamodelFactory.getClassMetaData(testClass,true);
classes.add(testClass);
//testClass=null;
//loader=null;
}
}
catch (OutOfMemoryError e)
{
e.printStackTrace();
}
System.out.println("Loaded " + count + " total");
classes.clear();
System.out.println("cache size = " + ClassMetamodelFactory.getCache().size());
forceOutOfMemoryError();
forceGC();
System.out.println("cache size (after GC)= " + ClassMetamodelFactory.getCache().size());
if (jvmtiInterface!=null)
{
Class referenceClass = jvmtiInterface.getClassByName(CLASS_NAME);
if (referenceClass!=null)
{
printReferences(jvmtiInterface,referenceClass);
jvmtiInterface.heapSnapshot("classFactoryModel","mem");
fail("Class " + CLASS_NAME + " should be unloaded");
}
}
else
{
assertEquals(0,ClassMetamodelFactory.getCache().size());
}
}
private void forceGC() throws InterruptedException {
if (jvmtiInterface!=null)
{
jvmtiInterface.forceGC();
}
else
{
System.gc();
Thread.sleep(1000);
}
}
public void testSerialization() throws Exception
{
ClassMetamodelFactory.clear();
URLClassLoader loader = newClassLoader();
Class testClass = loader.loadClass(CLASS_NAME);
Object someInstance = testClass.newInstance();
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
JBossObjectOutputStream objOut = new JBossObjectOutputStream(byteOut);
objOut.writeObject(someInstance);
objOut.flush();
JBossObjectInputStream objInput = new JBossObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()),loader);
Object newObject = objInput.readObject();
forceGC();
System.out.println("Size = " + ClassMetamodelFactory.getCache().size());
if (jvmtiInterface!=null)
{
// there is a static reference on the POJO itself
assertEquals(3,jvmtiInterface.getAllObjects(testClass).length);
}
loader=null;
testClass=null;
someInstance=null;
newObject=null;
objOut=null;
objInput=null;
forceOutOfMemoryError();
forceGC();
System.out.println("Size = " + ClassMetamodelFactory.getCache().size());
if (jvmtiInterface!=null)
{
Class clazz = jvmtiInterface.getClassByName(CLASS_NAME);
if (clazz!=null)
{
Object obj = clazz;
Object[] referenceHolders = printReferences(jvmtiInterface, obj);
// You can use JBossProfiler to analyze this snapshot
jvmtiInterface.heapSnapshot("./memory-tst","mem");
}
assertNull("Class org.jboss.serial.memory.SomePojo should been unloaded already",clazz);
}
else
{
assertEquals(0,ClassMetamodelFactory.getCache().size());
}
}
public void testSerializationWithCrossedClassLoaders() throws Exception
{
ClassMetamodelFactory.clear();
Class testClass = null;
URLClassLoader loader1 = null;
for (int i=0;i<4;i++)
{
loader1 = newClassLoader();
System.out.println("Operation " + i);
ClassLoader loader2 = newHierarchicalClassLoader(loader1);
testClass = loader1.loadClass(CLASS_NAME);
Class testClass2 = loader2.loadClass("org.jboss.serial.memory.test.SomePojoCross");
Object childObject = testClass.newInstance();
Object rootObject = testClass2.newInstance();
Method method = testClass2.getMethod("setOtherPojo",new Class[]{Object.class});
method.invoke(rootObject,new Object[]{childObject});
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
JBossObjectOutputStream objOut = new JBossObjectOutputStream(byteOut);
objOut.writeObject(rootObject);
objOut.flush();
loader1 = null;
testClass=null;
childObject = null;
forceOutOfMemoryError();
forceGC();
JBossObjectInputStream objInput = new JBossObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()),loader2);
Object newObject = objInput.readObject();
}
testClass=null;
loader1=null;
forceOutOfMemoryError();
forceGC();
assertEquals(0,ClassMetamodelFactory.getCache().size());
}
// simulates cross ClassLoader conflicts on the cache
public void testSerializationCrossLoaderClearingCache() throws Exception
{
ClassMetamodelFactory.clear();
Class testClass = null;
URLClassLoader loader = newClassLoader();
for (int i=0;i<10;i++)
{
forceCrossedClassLoaderRelease();
System.out.println("Operation " + i);
ClassLoader loader2 = newHierarchicalClassLoader(loader);
testClass = loader.loadClass(CLASS_NAME);
Class testClass2 = loader2.loadClass("org.jboss.serial.memory.test.SomePojoCross");
Object childObject = testClass.newInstance();
Object rootObject = testClass2.newInstance();
Method method = testClass2.getMethod("setOtherPojo",new Class[]{Object.class});
method.invoke(rootObject,new Object[]{childObject});
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
JBossObjectOutputStream objOut = new JBossObjectOutputStream(byteOut);
objOut.writeObject(rootObject);
objOut.flush();
forceCrossedClassLoaderRelease();
JBossObjectInputStream objInput = new JBossObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()),loader);
Object newObject = objInput.readObject();
}
testClass=null;
loader=null;
forceOutOfMemoryError();
forceGC();
assertEquals(0,ClassMetamodelFactory.getCache().size());
}
public static void forceCrossedClassLoaderRelease() {
Iterator valueIterator = ClassMetamodelFactory.getCache().values().iterator();
while (valueIterator.hasNext())
{
Map map = (Map)valueIterator.next();
Iterator classesIterator = map.values().iterator();
while (classesIterator.hasNext())
{
ClassMetaData metaData = (ClassMetaData)classesIterator.next();
metaData.setClazz(null);
}
}
}
// Just combining possibilities
public final void testSerializationWithCrossedClassLoadersInverted() throws Exception
{
ClassMetamodelFactory.clear();
Class testClass = null;
URLClassLoader loader = newClassLoader();
for (int i=0;i<10;i++)
{
System.out.println("Operation " + i);
ClassLoader loader2 = newHierarchicalClassLoader(loader);
testClass = loader2.loadClass(CLASS_NAME);
Class testClass2 = loader.loadClass("org.jboss.serial.memory.test.SomePojoCross");
Object childObject = testClass.newInstance();
Object rootObject = testClass2.newInstance();
Method method = testClass2.getMethod("setOtherPojo",new Class[]{Object.class});
method.invoke(rootObject,new Object[]{childObject});
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
JBossObjectOutputStream objOut = new JBossObjectOutputStream(byteOut);
objOut.writeObject(rootObject);
objOut.flush();
JBossObjectInputStream objInput = new JBossObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()),loader);
Object newObject = objInput.readObject();
}
testClass=null;
loader=null;
forceOutOfMemoryError();
forceGC();
assertEquals(0,ClassMetamodelFactory.getCache().size());
}
/* This is just an example on how we would validate JavaSerialization. You can uncoment this to perform some tests.
public void testJavaSerialization() throws Exception
{
if (newjvmtiInterface!=null)
{
System.out.println("testValidate is only valid when using profiler");
return;
}
try
{
serializeJava(jvmtiInterface,CLASS_NAME);
forceOutOfMemoryError();
serializeJava(jvmtiInterface,CLASS_NAME + "2");
ClassMetamodelFactory.clear();
forceOutOfMemoryError();
Class clazz = jvmtiInterface.getClassByName(CLASS_NAME);
if (clazz!=null)
{
Object obj = clazz;
Object[] referenceHolders = printReferences(jvmtiInterface, obj);
// You can use JBossProfiler to analyze this snapshot
jvmtiInterface.heapSnapshot("./memory-tst","mem");
}
assertNull("Class org.jboss.serial.memory.SomePojo should been unloaded already",clazz);
}
catch (Exception e)
{
e.printStackTrace();
Class clazz = jvmtiInterface.getClassByName(CLASS_NAME);
if (clazz!=null)
{
Object obj = clazz;
Object[] referenceHolders = printReferences(jvmtiInterface, obj);
// You can use JBossProfiler to analyze this snapshot
jvmtiInterface.heapSnapshot("./memory-ex2","mem");
}
System.out.println("Class org.jboss.serial.memory.SomePojo should been unloaded already" + clazz);
throw e;
}
} */
private static void forceOutOfMemoryError() {
TestUtil.forceSoftReferences();
}
private Object[] printReferences(JVMTIInterface jvmtiInterface, Object obj) {
Object referenceHolders[] = jvmtiInterface.getReferenceHolders(new Object[]{obj});
System.out.println("References for " + obj);
for (int i=0;i<referenceHolders.length;i++)
{
System.out.println("Reference[" + i + "]=" + referenceHolders[i].getClass().getName());
}
return referenceHolders;
}
}