01.JAVA/Java2008. 11. 28. 17:32
반응형

Extract All Classes Loaded in the JVM into a Single JAR

Today I needed to optimize the file size for a commercial Java applet (Web form signer) that my team is developing in the last month. We use few external libraries (Bouncy Castle Crypto API and few others) and these libraries are above 2 MB in several JAR files. If we were developing server side application, it would not be a problem, but when we are building an applet, the size of the applet and all its JARs matters.

I needed to remove all unused classes from the JARs that my applet was including a part of itself. For example the Bouncy Castle JARs were about 1,6 MB but the applet used only a small part of all algorithms and standards implemented by these JARs.

Extracting All Classes Loaded in the JVM

My final goal was not only to remove all unused classes from all JAR files but also merge these JARs along with the applet classes into a single JAR file that has the smallest possible size. I came with the idea to run the applet, to go through all its functionality and to get a list of all classes currently loaded into the JVM executing the applet. At this moment all classes required by the applet for its normal work will be loaded in the JVM and all classes that was never used by the applet will not be loaded in the JVM. If I package all these classes into a new JARs, it will contain the minimal set of classes nedded by the applet along with the applet classes.

As fas as I know how the JVM and the class loaders behave, this should be correct - we can expect all classes required by the applet to be loaded in the JVM after its entire functionality is accessed at least once.

I had a serious problem: how to get a list of all classes loaded in the JVM.

List All Classes Loaded in the JVM

Geting a list of all classes that are loaded in the JVM at some moment is not easy job. We can write Java agent through the java.lang.instrument API but I needed to do this at runtime (just to add few lines to the applet). I found in Google a very nice class for accessing all classes loaded in the JVM written by Vladimir Roubtsov and published in Java World (http://www.javaworld.com/javaworld/javaqa/2003-07/01-qa-0711-classsrc.html). With few modifications it successfully listed all classes loaded in my applet.

Create a Single JAR with All Classes Loaded in the JVM

The next step was to create a single JAR with all classes loaded in the JVM. This was not complex. I created a class with few methods for copying all currently loaded classes into some directory specified as parameter. Here is the source code:

  1. import java.io.File;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.net.URL;  
  7.  
  8. /***  
  9.  * This class extracts all classes loaded in the JVM and their binary contents  
  10.  * (.class files) into given directory so that you can create JAR archive later.  
  11.  * @author Svetlin Nakov - http://www.nakov.com  
  12.  */ 
  13. public class AllClassesInJVMExtractor {  
  14.  
  15.     private final static int BUFFER_SIZE = 4096;  
  16.  
  17.     public static void extractAllClassesFromJVM(String destFolder)  
  18.             throws IOException {  
  19.         ClassLoader appLoader = ClassLoader.getSystemClassLoader();  
  20.         ClassLoader currentLoader = AllClassesInJVMExtractor.class.getClassLoader();  
  21.  
  22.         ClassLoader[] loaders = new ClassLoader[] { appLoader, currentLoader };  
  23.         final Class< ?>[] classes = ClassScope.getLoadedClasses(loaders);  
  24.         for (Class< ?> cls : classes) {  
  25.             String className = cls.getName();  
  26.             URL classLocation = ClassScope.getClassLocation(cls);  
  27.             System.out.println("Extracting class: " + className + " from " +   
  28.                     classLocation);  
  29.             String destFileName = destFolder + "/" 
  30.                     + className.replace(".", "/") + ".class";  
  31.             copyFile(classLocation, destFileName);  
  32.         }  
  33.     }  
  34.  
  35.     private static void copyFile(URL sourceURL, String destFileName)  
  36.             throws IOException {  
  37.         File destFile = new File(destFileName);  
  38.         File destDirectory = destFile.getParentFile();  
  39.         destDirectory.mkdirs();  
  40.         InputStream srcStream = sourceURL.openStream();  
  41.         try {  
  42.             OutputStream destStream = new FileOutputStream(destFile);  
  43.             try {  
  44.                 copyStreams(srcStream, destStream);  
  45.             } finally {  
  46.                 destStream.close();  
  47.             }  
  48.         } finally {  
  49.             srcStream.close();  
  50.         }  
  51.     }  
  52.  
  53.     private static void copyStreams(InputStream srcStream,  
  54.             OutputStream destStream) throws IOException {  
  55.         byte[] buf = new byte[BUFFER_SIZE];  
  56.         while (true) {  
  57.             int bytesRead = srcStream.read(buf);  
  58.             if (bytesRead == -1) {  
  59.                 // End of stream reached  
  60.                 return;  
  61.             }  
  62.             destStream.write(buf, 0, bytesRead);  
  63.         }  
  64.     }  
  65.  
  66. }  
  67.  

It is not a rocket science. I go through all classes loaded by the current class loader and by the system class loader, get their fully qualified name (e.g. org.bouncycastle.cms.CMSSignedData) and their source URL location (e.g. jar:file:/C:/PROJECTS/GeneratePKCS7andVerify/lib/bcmail-jdk15-140.zip!/org/bouncycastle/cms/CMSSignedData.class) and I copy their binary contents (from the URL) to the destination folder (into a .class file). In the mean time I recreate the package structure (following the full class name with all its packages). Finally I get a directory containing all class files loaded in the JVM at the time of caling my method and I can manually package them in a JAR (removing beforehand all system Java classes). That’s all. I use slightly modified version of ClassScope.java.

You can download a fully functional example here (Eclipse project): ExtractAllClassesFromJVMIntoJAR.zip.

Posted by nakov in java, blog

Comments are closed.

Posted by 1010