关于精简JRE文件之精简rt.jar包

来源:互联网 发布:网络攻击 论文 编辑:程序博客网 时间:2024/06/05 16:16
  在网络上搜索下便能看到有不少讨论这个问题的文章。个人认为,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
                            + "\nSource 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
                            + "\nObject 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是否成功吧。