// Copyright (c) Keith D Gregory, all rights reserved
package com.kdgregory.example.memory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;



/**
 *  This program demonstrates what happens when you load the same class via
 *  different classloaders ... and keep doing so. If you comment-out the
 *  line that adds the new classloader to the list, then you can run this
 *  program "forever".
 */
public class PermgenExhaustion
{
    // this class has to be public and static to be loaded outside of the
    // defining class ... it's small, so we'll need to load a lot of them
    public static class MyClass
    {
        public MyClass() { /* don't do anything */ }
    }
    

    // a classloader that delegates everything except a specific class
    private static class MyClassLoader
    extends URLClassLoader
    {
        String _className;
        byte[] _classData;
        
        public MyClassLoader(Class<?> klass, byte[] classData)
        {
            super(new URL[0]);
            _className = klass.getName();
            _classData = classData;
        }

        @Override
        public Class<?> loadClass(String name) 
        throws ClassNotFoundException
        {
            if (!name.equals(_className))
                return super.loadClass(name);
            return
                defineClass(null, _classData, 0, _classData.length);
        }
    }
    
    
    // load the class bytes once, they can be reused
    private static byte[] loadClassFile(Class<?> klass)
    throws IOException
    {
        String className = klass.getName();
        if (className.indexOf('.') >= 0)
        {
            // strip package name, since we're loading this relative to itself
            className = className.substring(className.lastIndexOf('.') + 1);
        }
        String filename = className + ".class";
        InputStream in = MyClass.class.getResourceAsStream(filename);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int b;
        while ((b = in.read()) >= 0)
            out.write(b);
        return out.toByteArray();
    }
    
    
    public static void main(String[] argv)
    throws Exception
    {
        byte[] classData = loadClassFile(MyClass.class);
        List<ClassLoader> loaders = new ArrayList<ClassLoader>();

        for (int ii = 0 ; ii < 1000000 ; ii++)
        {
            MyClassLoader loader = new MyClassLoader(MyClass.class, classData);
            loaders.add(loader);
            loader.loadClass(MyClass.class.getName());
        }
    }
}
