引言: JRE是Java程序赖以运行的基础环境,目前JRE已经非常的庞大;即使为了运行一个简单的Hello World的程序,可能依然需要依赖整个JRE,将近百兆大小的依赖性。是否可以对特定Java程序依赖的JRE进行精简呢? 当然是可以,根据当前代码的需要,动态精简JRE,只依赖需要的class,而非全部。
1. 整体的思路
a. 首先找到当前Java程序依赖的所有class,包括自身类库/第三方类库,以及JRE中的类库。
b. 将JRE中不需要的类库文件移除掉,只保留需要的类库。
C. 将保留下的类库重新打包,替换已有的JRE文件
2. 寻找所需的JRE中的类库文件
在运行Java应用的过程中,可以针对JVM添加参数[-XX:+TraceClassLoading],则应用在启动过程中,会将所有的所需的class打印到控制台。
在上述的例子中,就列出所有依赖的类库。
2. 如何提取需要的类库或者移除不需要的类库
这里我们采用前者,只提取所需的类库。
jar xvf xxx.jar classname1 classname2 ....
这个命令就会把需要的class从jar中提取出来,复制到本地当前目录。
3. 将这些类库进行打包,替换掉JRE中对应的类库
jar cvf target.jar sourcefolder1 classfolder2 ...
打包命令,将classfolder中的类库,打包为target.jar.
4. 那代码上如何,利用上述的只是完成自动化打包 JRE对应的类库呢?
方案如下:
4.1. 基于运行过程中的Java参数-XX:+TraceClassLoading,打印出所用在JRE中用到的java类
4.2. 捕获从控制台输出的class列表
4.3. 利用jar自带的功能,从rt.jar中提取相应的所需要的class
4.4. 将rt.jar中提取的class进行打包,即可得到所需的jre核心jar包。
代码假定的前提:
1. Jre所在的路径
2. 目标java类已经编译成class.这里未考虑动态编译的情况
3. 将jre中的rt.jar打包在当前路径。
示例代码如下:
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.List;
public class RunClass {
public static void main(String[] args) throws IOException {
List<String> classes = new ArrayList<String>();
String[] cmdB = { "java", "-XX:+TraceClassLoading", "MainTest" };
Runtime runtime = Runtime.getRuntime();
Process process = Runtime.getRuntime().exec(cmdB);
// /process = Runtime.getRuntime().exec(cmdB);
LineNumberReader br = new LineNumberReader(new InputStreamReader(
process.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
String className = RunClass.parseClass(line);
if (className.length() > 0) {
sb.append(className.replace(".", "/")).append(" ");
classes.add(className.replace(".", "/"));
}
}
System.out.println("classes to be packed in size:" + classes.size());
classes.add(0, "/opt/jdk7/jre/lib/rt.jar");
classes.add(0, "xvf");
classes.add(0, "jar");
Process jarClass = runtime.exec((String[]) classes
.toArray(new String[classes.size()]));
LineNumberReader br1 = new LineNumberReader(new InputStreamReader(
jarClass.getInputStream()));
StringBuffer sb1 = new StringBuffer();
String line1;
while ((line1 = br.readLine()) != null) {
System.out.println("extracting:" + line1);
}
System.out.println(classes.size()
+ " classes have been extracted successfully");
String[] cmdJarPackage = { "jar", "cvf", "rt.jar", "com", "java",
"javax", "META-INF", "org", "sun", "sunw" };
Process jarProcess = runtime.exec(cmdJarPackage);
System.out
.println("Jar the classes into a package rt.jar successfully.");
}
public static String parseClass(String lineStr) {
String keyStr = "";
if (lineStr.startsWith("[Loaded")) {
String[] keys = lineStr.split(" ");
if (keys.length == 4) {
keyStr = keys[1];
}
}
return keyStr;
}
}
5. 总结
JRE在jDK8中已经对其进行了模块化设计,从而使按需加载和定制JRE成为可能。这里的示例代码只是简单示意了流程,离真正的工具化还有较大差距;主要的原因是大量使用了Runtime.exec方法来直接调用命令,这样不是很灵活和可控;比如打包和解压可以利用JarOuputStream, JarInputStream等来进行等,会更加可控和灵活。