import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;

public class ClassChurn {
    public static void main(String[] args) throws Exception {
        for (int t = 0; t < 20; t++) {
            new Thread(() -> {
                for (int i = 0; i < 200; i++) loadSome();
            }).start();
        }
        System.gc();
        System.out.println("Finished churning lots of classes. You can check the VM.symboltable to see what has leaked.");
        Thread.sleep(100_000_000L);
    }

    static AtomicLong total = new AtomicLong(0L);

    // Load (but do not retain) a bunch of dummy classes.
    static void loadSome() {
        try {
            ClassLoader loader = new MyClassLoader();
            for (int i = 0; i < 10_000; i++) {
                Class c = Class.forName("dummy" + (100_000_000 + total.incrementAndGet()), true, loader);
                if (total.get() % 100_000 == 0) System.out.println(total.get());
            }
            System.gc();
        } catch (Exception e) { throw new RuntimeException(e); }
    }

     static final byte[] CLASS = 
         new byte[]{
             (byte)0xca,(byte)0xfe,(byte)0xba,(byte)0xbe,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x34,
             (byte)0x00,(byte)0x0d,(byte)0x0a,(byte)0x00,(byte)0x03,(byte)0x00,(byte)0x0a,(byte)0x07,
             (byte)0x00,(byte)0x0b,(byte)0x07,(byte)0x00,(byte)0x0c,(byte)0x01,(byte)0x00,(byte)0x06,
             (byte)0x3c,(byte)0x69,(byte)0x6e,(byte)0x69,(byte)0x74,(byte)0x3e,(byte)0x01,(byte)0x00,
             (byte)0x03,(byte)0x28,(byte)0x29,(byte)0x56,(byte)0x01,(byte)0x00,(byte)0x04,(byte)0x43,
             (byte)0x6f,(byte)0x64,(byte)0x65,(byte)0x01,(byte)0x00,(byte)0x0f,(byte)0x4c,(byte)0x69,
             (byte)0x6e,(byte)0x65,(byte)0x4e,(byte)0x75,(byte)0x6d,(byte)0x62,(byte)0x65,(byte)0x72,
             (byte)0x54,(byte)0x61,(byte)0x62,(byte)0x6c,(byte)0x65,(byte)0x01,(byte)0x00,(byte)0x0a,
             (byte)0x53,(byte)0x6f,(byte)0x75,(byte)0x72,(byte)0x63,(byte)0x65,(byte)0x46,(byte)0x69,
             (byte)0x6c,(byte)0x65,(byte)0x01,(byte)0x00,(byte)0x13,(byte)0x64,(byte)0x75,(byte)0x6d,
             (byte)0x6d,(byte)0x79,(byte)0x31,(byte)0x32,(byte)0x33,(byte)0x34,(byte)0x35,(byte)0x36,
             (byte)0x37,(byte)0x38,(byte)0x39,(byte)0x2e,(byte)0x6a,(byte)0x61,(byte)0x76,(byte)0x61,
             (byte)0x0c,(byte)0x00,(byte)0x04,(byte)0x00,(byte)0x05,(byte)0x01,(byte)0x00,(byte)0x0e,
             (byte)0x64,(byte)0x75,(byte)0x6d,(byte)0x6d,(byte)0x79,(byte)0x31,(byte)0x32,(byte)0x33,
             (byte)0x34,(byte)0x35,(byte)0x36,(byte)0x37,(byte)0x38,(byte)0x39,(byte)0x01,(byte)0x00,
             (byte)0x10,(byte)0x6a,(byte)0x61,(byte)0x76,(byte)0x61,(byte)0x2f,(byte)0x6c,(byte)0x61,
             (byte)0x6e,(byte)0x67,(byte)0x2f,(byte)0x4f,(byte)0x62,(byte)0x6a,(byte)0x65,(byte)0x63,
             (byte)0x74,(byte)0x00,(byte)0x21,(byte)0x00,(byte)0x02,(byte)0x00,(byte)0x03,(byte)0x00,
             (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x01,(byte)0x00,
             (byte)0x04,(byte)0x00,(byte)0x05,(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x06,(byte)0x00,
             (byte)0x00,(byte)0x00,(byte)0x1d,(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x01,(byte)0x00,
             (byte)0x00,(byte)0x00,(byte)0x05,(byte)0x2a,(byte)0xb7,(byte)0x00,(byte)0x01,(byte)0xb1,
             (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x07,(byte)0x00,(byte)0x00,
             (byte)0x00,(byte)0x06,(byte)0x00,(byte)0x01,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01,
             (byte)0x00,(byte)0x01,(byte)0x00,(byte)0x08,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x02,
             (byte)0x00,(byte)0x09};

    static class MyClassLoader extends ClassLoader {
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.startsWith("dummy")) {
                // replace template class name and sourcefile name with the desired name, e.g. dummy100000001
                byte[] c = new byte[CLASS.length];
                System.arraycopy(CLASS, 0, c, 0, CLASS.length);
                System.arraycopy(name.getBytes(), 0, c, 0x4d, name.getBytes().length);
                System.arraycopy(name.getBytes(), 0, c, 0x68, name.getBytes().length);
                return defineClass(name, c, 0, c.length, null);
            } else {
                return super.loadClass(name);
            }
        }
    }
}
