在网络上搜索下便能看到有不少讨论这个问题的文章。个人认为,JRE最好是能向下兼容,于是不同的程序可以在自己的程序中配置所需要的JRE版本。这样就不再会因为用户机器上的JRE版本问题而烦恼。同时,Java开发者所能做的就是尽量精简JRE以满足自己产品运行为最低要求。
本文將介绍我成功简化rt.jar包的过程。
正文:
操作流程简介:
首先,在Windows环境下,准备好需要精简的rt.jar所属的JRE文件夹。
接着,需要在Windows环境下,通过命令在准备好的的JRE环境中运行我们所需要的程序,程序的每个功能最好都能执行,以满足每个需要被调用的包能被自动记录在一个文本文件中。
然后,编写一个程序按照记录將解压缩後的rt.jar包中所需要的类及其所属文件目录结构完整的复制到一个新的rt文件夹中(默认命名为ort(Objecct rt的意思))。
最后,打包ort文件为rt.jar并替换之前准备好的JRE/lib/ 下的rt.jar文件。(原来的rt.jar文件有40多MB,简化後最小可能只有几MB)。
操作流程详述:
(其中用到了来自网络的程序代码,当然经过我大量修改後才成功执行了任务。由于事隔几日,没有记下原代码作者信息,如果原代码作者看到,可以联系我,我将会加上引用注释)
重新搜索了下,那篇文章被到处引用,如:
http://www.kaiyuan8.org/Article/syaKuiwQVGqhnFgjCwcZ.aspx
http://www.cnitblog.com/Walter/articles/59164.html(仅仅作为举例,别无它意)
已经难寻出处了。
下面来讲讲我根据这篇文章精简的经历吧。
首先,在Windows下准备好for Windows版的完整JRE文件。(我的在C:/Program Files/Java/jdk1.6.0_20/jre,其中jre/bin/中的Java程序是.exe格式的)。所要精简的rt.jar在/jre/lib/目录下。先不拷贝这个jre文件。
我们需要在jre文件见的父文件夹(即,上级文件夹)下创建.txt文本文件,并写入:
start jre/bin/java -jar -verbose:class 【改写成你打包成.jar的产品程序包路径,并去掉这句话两端的中括号】>>usedClasses.txt
pause
再將其.txt修改成.cmd。并执行。
“这样程序使用的就是当前目录下的jre,程序运行后,最好把所有的功能使用一遍,这样输出了一个文件usedClasses.txt,里面有所有需要的class,其格式如下:
[Opened D:\data\dict\jre\lib\rt.jar]
[Loaded java.lang.Object from D:\data\dict\jre\lib\rt.jar]
[Loaded java.io.Serializable from D:\data\dict\jre\lib\rt.jar]
[Loaded java.lang.Comparable from D:\data\dict\jre\lib\rt.jar]
[Loaded java.lang.CharSequence from D:\data\dict\jre\lib\rt.jar]
[Loaded org.apache.lucene.index.CompoundFileReader$FileEntry from file:/D:/data/dict/dict.jar]
我们依照这个文件来裁剪rt.jar:“
由于在Ubuntu下我使用GUN Emacs 23来修改usedClasses.txt文件的内容,以满足之后程序读取的需要(这算是我第一次正式使用Emacs吧)。第一步,我用Gun Emacs 23打开usedClasses.txt文件。
第二步,选择Edit->Replace->Replace String…,键入将被替换的[Loaded 并确认,再键入用于替换它的回车(即,等同于删去[Loaded 这个关键字段)。
第三步,选择Edit->Replace->Replace Regexp…,通过正则表达式from.*来查找from到]的语句。并替换
第四步,删掉最开头的 [Opened 。删掉其他多余的不包含.class类的语句。
第五步,保存文件。这样usedClasses.txt便准备好了。
注释:在Emacs中 `\n’ here doesn’t match a newline; to do that, type C-q C-j instead(C代表Ctrl键)按下Ctrl不放,再接着依次按qj键。(根多关于Emacs可以参考:http://hi.baidu.com/limp_t/blog/item/3bc60f54d868b0143a2935bf.html)
下面修改之前提到的文章中的Java代码。因为我发现他们还不能够成功拷贝二进制文件。
首先,我分享将要用到的,由我封装的IO读写类的部分用到的方法的代码,其他的代码可以省去,足够作为一个完整的类。
然后,我在分享我修改後的拷贝.class文件及其目录结构的代码。
InputOutput.java:
package com.wordpress.iwillaccess.classes.global;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public
class InputOutput {
private
static String _fileLocation;
private
static String _fileName;
private
static String _fileDirectory;
private
static String _fileContent;
private
static
byte[] _byte;
/**
*
@return
the _fileDirection
*/
public String get_fileLocation() {
return _fileLocation;
}
/**
*
@return
the _fileName
*/
public String get_fileName() {
return _fileName;
}
/**
*
@return
the _fileDirectory
*/
public String get_fileDirectory() {
return _fileDirectory;
}
/**
*
@return
the _fileContents
*/
public String get_fileContent() {
return _fileContent;
}
/**
*
@return
the _byte
*/
public
byte[] get_byte() {
return _byte;
}
/**
*
@param
fileDirection
* the _fileDirection to set
*/
private
static
synchronized
void set_fileLocation(String fileLocation) {
_fileLocation = fileLocation;
}
/**
*
*
@param
fileName
* the _fileName to set
*/
private
static
synchronized
void set_fileName(String fileName) {
_fileName = fileName;
}
/**
*
*
@param
fileLocation
* the _fileLocation to set
*
@param
fileName
* the _fileName to set
<
br
>
*
<
br
>
* fileLocation and fileName is the _fileDirectory to set.
*/
public
synchronized
void set_fileDirectory(String fileLocation,
String fileName) {
set_fileLocation(fileLocation);
set_fileName(fileName);
_fileDirectory = fileLocation + fileName;
}
/**
*
*
@param
fileDirectory
* the _fileDirectory to set
<
br
>
*
<
br
>
* _fileDirectory = _fileLocation + _fileName;
<
br
>
* _fileLocation and _fileName will be set automatically in this
* method.
<
br
>
*/
public
synchronized
void set_fileDirectory(String fileDirectory) {
set_fileLocation(fileDirectory.substring(
0, fileDirectory
.lastIndexOf(
“/”) +
1));
set_fileName(fileDirectory
.substring(fileDirectory.lastIndexOf(
“/”) +
1));
_fileDirectory = fileDirectory;
}
/**
*
@param
fileContents
* the _fileContents to set
*/
public
synchronized
void set_fileContent(String fileContent) {
_fileContent = fileContent;
}
/**
*
@param
_byte
* the _byte to set
*/
private
synchronized
void set_byte(
byte[] _byte) {
InputOutput._byte = _byte;
}
public InputOutput() {
//
TODO
Auto-generated constructor stub
}
/**
*
check if the file is exist or not
*
*
@param
fileDirectory
* the _fileDirectory to set, and the directory of the file will
* be checked.
*
@return
true: file is exist;
<
br
>
* false: file is not exist.
*/
public
boolean checkIfFileExist(String fileDirectory) {
set_fileDirectory(fileDirectory);
File file =
new File(get_fileDirectory());
// System.out.println(“file.exists():” + file.exists());
return file.exists();
}
//…………
//……省去的无关方法的代码………
//…………
/**
*
input get_byte() to get_fileDirectory.
If object file is exist, it will
* be replaced without warning.
<
br
>
*
<
br
>
* NOTE:
<
br
>
*
<
blockquote
>
use the set_fileDirectory(), set_byte() and
* checkIfFileExist(String fileDirectory) methods first
<
br
>
* to set a righdis.readFully(b)t file directory and file content
<
br
>
* before using this method;
<
br
>
*
<
blockquote
>
*/
public
synchronized
void DataInputFully() {
if (get_fileLocation() !=
null && get_fileName() !=
null
&& get_fileContent() !=
null && get_byte() !=
null) {
try {
FileOutputStream fos =
new FileOutputStream(get_fileDirectory());
DataOutputStream dos =
new DataOutputStream(fos);
dos.write(get_byte());
dos.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
else {
System.out.println(
“fileLocation:” + get_fileLocation()
+
“; or fileName:” + get_fileName() +
“; or byte[]:”
+ get_byte() +
” is null.”);
}
}
/**
*
input get_byte() to get_fileDirectory.
If object file is exist, it will
* be replaced without warning.
<
br
>
*
<
br
>
* NOTE:
<
br
>
*
<
blockquote
>
please use set_fileDirectory() to set the file directory,
* and use checkIfFileExist(String fileDirectory) first
<
br
>
* before using this method.
<
br
>
*
</
blockquote
>
*
*
@param
b
* the _byte to set
*/
public
void DataInputFully(
byte[] b) {
set_byte(b);
DataInputFully();
}
/**
*
input get_byte() to get_fileDirectory.
If object file is exist, it will
* be replaced without warning.
<
br
>
*
<
br
>
* NOTE:
<
br
>
*
<
blockquote
>
create a file, whose directory as
<
code
>
fileDirectory
</
code
>
* and file content as
<
code
>
get_byte()
</
code
>
.
<
br
>
* Use checkIfFileExist(String fileDirectory) first
<
br
>
*
</
blockquote
>
*
*
@param
fileDirectory
* the _fileDirectory to set
<
br
>
*
@param
b
* the _byte to set, contains the content of file.
*/
public
void DataInputFully(String fileDirectory,
byte[] b) {
set_fileDirectory(fileDirectory);
set_byte(b);
DataInputFully();
}
/**
*
input get_byte() to get_fileDirectory.
If object file is exist, it will
* be replaced without warning.
<
br
>
*
<
br
>
* NOTE:
<
br
>
*
<
blockquote
>
create file named
<
code
>
fileName
</
code
>
in
*
<
code
>
fileLocation
</
code
>
input
<
code
>
get_byte()
</
code
>
to file named as
*
<
code
>
fileName
</
code
>
in
<
code
>
fileLocation
</
code
>
.
<
br
>
* Use checkIfFileExist(String fileDirectory) first
<
br
>
*
</
blockquote
>
*
*
@param
fileLocation
* the _fileLocation to set and will be used to set the
* _fileDirectory
*
@param
fileName
* the _fileName to set and will be used to set the
* _fileDirectory
*
@param
b
* the _byte to set and will be write to the file with
* _fileDirectory directory. _byte Contains the content of file.
*/
public
void DataInputFully(String fileLocation, String fileName,
byte[] b) {
set_fileDirectory(fileLocation, fileName);
set_byte(b);
DataInputFully();
}
/**
*
Out put a file by using DataOutputStream class.
<
br
>
*
<
br
>
* NOTE:
<
br
>
*
<
blockquote
>
use the set_fileDirectory() method first
<
br
>
* to set a right file directory
<
br
>
* before using this method;
<
br
>
*
</
blockquote
>
*
*
@return
null:if fileDirectory is null;
<
br
>
* file content byte[];
*/
public
synchronized
byte[] DataOutputFully() {
if (get_fileDirectory() !=
null) {
File file =
new File(get_fileDirectory());
if (file.exists()) {
try {
FileInputStream fis;
fis =
new FileInputStream(get_fileDirectory());
DataInputStream dis =
new DataInputStream(fis);
byte[] b =
new
byte[dis.available()];
while (dis.available() >
0) {
dis.readFully(b);
// System.out.println(” :” + b.length);
}
set_byte(b);
String tmpStr =
“”;
for (
int i =
0; i < b.length; i++) {
tmpStr += String.valueOf(b[i]);
}
set_fileContent(tmpStr);
// System.out.println(“\n” + get_fileContent() + “\n”
// + get_fileLocation() + “\n” + get_fileName());
}
catch (FileNotFoundException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
catch (IOException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
catch (Exception e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
return get_byte();
}
else {
System.out.println(
“fileDirectory:” + get_fileDirectory()
+
“; and the file is not exist.”);
return
null;
}
}
else {
System.out.println(
“fileDirectory:” + get_fileDirectory()
+
“; is null”);
return
null;
}
}
/**
*
Out put a file, which directory as
<
code
>
fileDirectory
</
code
>
,
<
br
>
*
by using DataOutputStream class.
<
br
>
*
*
@param
fileDirectory
* the _fileDirectory to set and will be used to find the file,
* which is supposed to be opened.
<
br
>
*
@return
byte[] _byte() the content of the chose file.
*/
public
byte[] DataOutputFully(String fileDirectory) {
set_fileDirectory(fileDirectory);
return DataOutputFully();
}
/**
*
Out put a file, which directory as
<
code
>
fileLocation
</
code
>
plus
*
<
code
>
fileName
</
code
>
,
<
br
>
*
by using DataOutputStream class.
<
br
>
*
*
@param
fileLocation
* the _fileLocation to set and will be used to set the
* _fileDirectory
*
@param
fileName
* the _fileName to set and will be used to set the
* _fileDirectory
*
@return
byte[[] get_byte() the content of the chose file.Data
*/
public
byte[] DataOutputFully(String fileLocation, String fileName) {
set_fileDirectory(fileLocation, fileName);
return DataOutputFully();
}
public
static
void main(String[] args) {
InputOutput io =
new InputOutput();
io.set_fileDirectory(
“/home/knowyourself1010/Tracker.class”);
io.set_fileContent(
“1234567890 abcdefg ABCDEFG 好的繁体字简体字复杂么?”);
// io.DataInput();
System.out.println(io.DataOutput());
io.set_fileDirectory(
“/home/knowyourself1010/file.class”);
io.DataInput();
// io.set_fileContent(“rfgadssssssssssssssdsaaaadwefewfefeferegrqedwdq”);
// System.out.println(io.get_fileContent());
}
}
CopyUsefulClasses.java:
/**
*
*/
package com.wordpress.iwillaccess.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import com.wordpress.iwillaccess.classes.global.InputOutput;
/**
*
@author
knowyourself1010
*
*/
public
class CopyUsefulClasses {
// 文件拷贝
private
static
boolean copy(String sourceFileLocation,
String objectFileLocation, String fileName) {
try
// must try and catch,otherwise will compile error
{
if (sourceFileLocation.substring(sourceFileLocation.length() –
1) !=
“/”) {
sourceFileLocation +=
“/”;
}
if ((objectFileLocation.substring(objectFileLocation.length() –
1)) !=
“/”) {
objectFileLocation +=
“/”;
}
InputOutput inputOutput =
new InputOutput();
byte[] b = inputOutput
.DataOutputFully(sourceFileLocation, fileName);
inputOutput.DataInputFully(objectFileLocation, fileName, b);
return
true;
// if success then return true
}
catch (Exception e) {
System.out.println(
“Error!”);
return
false;
// if fail then return false
}
}
// 读取路径,copy
private
static
int dealClass(String needfile, String sdir, String odir)
throws IOException {
int sn =
0;
// 成功个数
if (odir.length() >
0 && sdir.length() >
0) {
if ((sdir.substring(sdir.length() –
1)) !=
“/”) {
sdir +=
“/”;
}
if (odir.substring(odir.length() –
1) !=
“/”) {
odir +=
“/”;
}
File usedclass =
new File(needfile);
if (usedclass.canRead()) {
String line =
null;
LineNumberReader reader =
new LineNumberReader(
new InputStreamReader(
new FileInputStream(usedclass),
“UTF-8”));
while ((line = reader.readLine()) !=
null) {
line = line.trim();
if (line.contains(
“.”) || line.contains(
“/”)) {
// format the direction from package name to path
String dir = line.replace(
“.”,
“/”);
// filter the file name.
String tmpdir = dir.substring(
0, dir.lastIndexOf(
“/”));
String sourceFileLocation = sdir + tmpdir;
String objectFileLocation = odir + tmpdir;
String fileName = dir.substring(
dir.lastIndexOf(
“/”) +
1, dir.length())
+
“.class”;
File fdir =
new File(objectFileLocation);
if (!fdir.exists())
fdir.mkdirs();
boolean copy_ok = copy(sourceFileLocation,
objectFileLocation, fileName);
if (copy_ok)
sn++;
else {
System.out.println(line);
}
}
else {
sn = –
1;
}
}
}
}
return sn;
}
/**
*
@param
args
*/
public
static
void main(String[] args) {
//
TODO
Auto-generated method stub
try {
BufferedReader lineOfText =
null;
// get need classes log file direction
System.out
.println(
“needfile (file logged the full name of useful classes ):”);
lineOfText =
new BufferedReader(
new InputStreamReader(System.in));
String needfile = lineOfText.readLine();
// get source folder direction
System.out
.println(needfile
+
“
\n
Source Folder Direction (the direction of the folder filled the classes need to be filtered.):”);
lineOfText =
new BufferedReader(
new InputStreamReader(System.in));
String sdir = lineOfText.readLine();
// get object folder direction
System.out
.println(sdir
+
“
\n
Object Folder Direction (the direction of the folder used to fill the filtered classes.):”);
lineOfText =
new BufferedReader(
new InputStreamReader(System.in));
String odir = lineOfText.readLine();
System.out.println(odir +
“
\n
“);
int sn = dealClass(needfile, sdir, odir);
System.out.print(sn);
}
catch (IOException e) {
//
TODO
自动生成 catch 块
e.printStackTrace();
}
}
}
(在此感谢irc: ##java上朋友提示读写Binary文件的注意事项:不要用Reader方法!Char也不太支持哦。)
运行CopyUsefulClasses.java。首先输入usedClasses.txt的绝对路径,回车,在输入jre/lib/rt.jar解压後的rt文件夹所在的路径,回车,再输入ort(所要存放拷贝过来的有用的.class文件的文件夹)。然后等上几分中,期间会提示,你的产品程序所用到的jre不包含的类不存在,不用管,因为我们呢只拷贝rt文件中的.class文件。
最后將ort文件夹下面的所有文件加压缩为rt.zip文件,并改为rt.jar。
注意:1.是选中ort文件夹下所有子文件夹,而非选中ort文件夹,并压缩。
2.在Ubuntu10.10下如果选择压缩为jar格式文件,可能会少压缩进java.lang.Object.class等文件(估计是个bug),所以需要压缩为.zip再该类型为.jar。
最后,將要简化的jre中的rt.jar替换为这里压缩得到的rt.jar文件。我的只有2MB。
用 java -jar [您的jar程序路径]
来测试简化後的jre是否成功吧。