0%

jar包的运行以及jre精简

工程打包

无论是什么IDE,打包都是一件十分简单的事。(这里不讨论自己手动打包的情形)


jar包的运行

如果环境变量中以及添加了jre的路径,那么java的执行如下:

1
$ java -jar xxx.jar arg1 arg2 XXX

如果不想每次都使用命令行来输入的话,就可以把上面的命令变成一条bat脚本:

例如取名加start.bat,内容为:

1
2
3
4
5
@echo off

start .\jre\bin\java -jar xxx.jar %1 %2

pause

其中@echo off意思为不打印输出,start意味着新建窗口执行后面的命令,pause表示暂停, 也就是可以在运行结束时出现请按任意键继续...,另外%1 %2表示两个输入参数。

注意:此时jar包的路径就是它的执行路径,有时候程序可能需要取这个路径来进行相对路径的写操作, java中可以使用下面语句来取到:

1
System.getProperty("user.dir")

jre不添加环境变量运行

上面考虑的是环境变量中以及添加了jre的情况,有时候我们可能需要把程序放到别的电脑上来运行, 这时候如果别人的电脑上没有java环境,那就会执行不了,总不能让别人现装一个吧。

考虑到最大化的简便使用者,就需要把jre拿出来,和jar包绑在一起发送给别人。

那么如何使用jre来直接运行java呢?使用jre中bin下的java.exe或者javaw.exe即可:

1
2
3
$ .\jre\bin\java -jar xxx.jar arg1 arg2 ...
或者
$ .\jre\bin\javaw -jar xxx.jar arg1 arg2 ...

它两之间没有什么不同,javaw会屏蔽打印信息,而java不会。

那么现在,就只需要将jre放到jar包同一个目录下,使用上面的命令就可以运行程序, 这样就能在没有java环境的电脑上运行,因为我们自带了java运行环境。

这里具体的目录结构当然不重要,看个人喜好了。


精简jre

这里使用的是jre 1.8,从jdk-8u131下取出来的,大小为188M。

显然,相比于我的程序来说,这实在是太大了,如果不精简jre,整个程序包就得有近200M, 这有一点不能接受,所以还是需要精简jre。

这里我参考了博客整理JRE瘦身或精简JRE

那么有没有一些精简工具呢?

搜索了半天,有一个greenvm,但是这程序好像已经非常久没有更新了, 这位作者是不是放弃它了,下载下来试了一下,好像有一点问题,总之我用的时候是报错了。

还有一个jrecreate,这个是oracle公司官方的,不过好像不是针对某个程序设计的?感觉就是提供了几个较小版本的jre?有待考证。

感觉大部分网友还是自己手动进行精简,那么如果要手动精简,首先整理几个点:

  1. jre文件下需要考虑的就是两个文件夹binlib
  2. bin文件夹下总的文件数不多,体积大概为80M,其中大部分东西都用不到。
  3. lib文件夹下有很多文件,体积大概有100M,其中有少量的jar包会用到。

有了上面几个点,下面就是第一步精简的策略:

首先对于bin文件夹的精简策略:

  1. 手动删除几个文件,然后执行jar包,看是否报错,就这样精简到不能再精简。

想着手动挺烦的,但是其实上这里大部分文件都可以删除,而且文件也不多,几分钟就能清理完毕。 (我清除完就剩下8M,主要是jvm.dll比较大)。

第二,对于lib文件夹的精简策略:

  1. 同样的,手动删。
  2. 这里可能需要留的文件比较多,所以建议直接按文件大小,很多小文件(1KB,2KB)就不用删了,试一试删除一些大文件。

我这里最后剩下两个较大的文件charsets.jarrt.jar,那么加起来还是有60M多,恩,还需要继续精简。


精简charsets.jar和rt.jar

charsets.jarrt.jar中有很多程序运行时需要调用的class文件,所以我们删不掉它们,那么很明显, 只要将程序不用的class文件从中删去,就能减小大小。

要这样做,首先就得知道自己的程序在运行过程中使用了哪些类,可以使用下面命令来打印:

1
$ java -jar -verbose:class XXX.jar

这样就能打印我们使用了哪些类,将这些信息重定向到文件中:

1
$ java -jar -verbose:class XXX.jar >> class.txt

那么这里就得到了class.txt,其中以[Load ...]开头的就是在加载类。

进一步,我们要将charsets.jarrt.jar类中的相关class提取出来:

  1. 将这两个文件给解压缩了,得到charsetsrt文件夹。
  2. 编写代码,取到class.txt中加载的类,对比的去charsetsrt文件夹中找,找到就复制到新文件夹。

这里的代码会附加到最后面,总之,这里得到两个新的charsetsrt文件夹,下面已经去除了不需要的class文件。

直接将这两个文件夹用zip打包(不要将charsets或者rt文件夹也给包了,这样就比原来的jar包多了一层目录了),改名为charsets.jarrt.jar,放回jre所在的位置中替换既可。

这样下来lib文件夹可以缩小到10M以下。

讲道理应该是可以的,但是我这里报错了,可能是我个人的问题。


实际上的精简操作

由于上面的方法最后在运行jar包时报错了,所以我手动添加了一些class回去。

事实上,对于charsets.jar,我没有改动内容,只是将它解压重新用zip压缩了一遍,这样的确可以缩小一点。

对于rt.jar,我将java和sun两个包下的类都保留了,因为不知道到底少了哪个class。

最终jre的大小精简到22M。


问题

不知道在后面的使用中还会不会有别的问题。

2018-06-15:使用时问题,我程序中需要有输入文件,输入文件的格式可能是.xml.xls.xlsx.csv, 但是在精简的时候我只使用了.csv文件测试,于是当输入文件变成.xls格式时,就出现了问腿。

所以,在不得已的时候还是不要尝试使用本方法精简jre,由于程序运行过程的变化,可能会出现问题。当然, 如果程序比较简单,没什么变化时,还是可以精简jre的。


代码

参考了整理JRE瘦身或精简JRE,进行了少量改动。

jarInputPath是输入文件夹,这里下面就有两个文件夹rt、charsets。(解压了的)

jarOutputPath是输出文件夹。

jarPackNames就是两个包名。

classListInputPath就是上面命令打印出来的信息。

classListOutputPath所有这里复制了的类的名字会保存到这个文件中。

另外:输入文件与输出文件的编码注意一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import java.util.ArrayList;
import java.io.*;


public class ParseClass {

private static final String jarInputPath = "./input";
private static final String jarOutputPath = "./output";

private static final String[] jarPackNames = {"rt", "charsets"};

private static final String classListInputPath = "class.txt";
private static final String classListOutputPath = "classClean.txt";

private static ArrayList<String> classList = new ArrayList<>();

public static void main(String[] args) {
try {
int totalLine = 0;
int totalLineOut = 0;

BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(classListInputPath), "unicode"));

BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(classListOutputPath), "UTF-8"));

String line = null;
while ((line = br.readLine()) != null) {
totalLine++;
if ( !line.startsWith("[L") ) {
continue;
}
String str = line.split(" ")[1];
classList.add(str);
}

// copy class
for ( String str : classList ) {
if ( copyClass(str) ) {
System.out.println( "copy : " + str);
bw.write(str);
bw.newLine();
totalLineOut++;
}
}

System.out.println("The number of input line: " + totalLine);
System.out.println("The number of out line: " + totalLineOut);
br.close();
bw.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}


private static boolean copyClass(String className) throws IOException {
className = className.replaceAll("\\.", "/");
boolean flag = false;
for ( String pack : jarPackNames ) {
String classPathIn = jarInputPath + "/" + pack + "/" + className + ".class";
String classPathOut = jarOutputPath + "/" + pack + "/" + className + ".class";
File fileIn = new File(classPathIn);
File fileOut = new File(classPathOut);
if ( !fileIn.exists() || fileOut.exists() ) {
continue;
}
helpCopyFile(classPathIn, classPathOut);
flag = true;
break;
}
return flag;
}

private static void helpCopyFile(String input, String output) throws IOException {
File outDir = new File(output.substring(0, output.lastIndexOf("/")));
if ( !outDir.exists() ) {
outDir.mkdirs();
}

FileInputStream fis = new FileInputStream(input);
FileOutputStream fos = new FileOutputStream(output);

byte buf[] = new byte[256];
int len = 0;
while ( (len = fis.read(buf)) != -1 ) {
fos.write(buf, 0, len);
}
fos.flush();

fis.close();
fos.close();
}
}