Java 精簡Jre jar打包成exe


 

 

 

#開始

  最近幾天都在忙一個事情,那就是嘗試精簡jre,我想不明白為什么甲骨文官方不出exe打包工具... 

  網絡上精簡jre的文章很多,但是原創的似乎沒幾個,絕大多數都是轉發同一個博客, 這里借鑒了不少思路以及方法 不知道還算不算原創了

  這兩天收獲也是挺大的,在這里和大家分享一下我的成果.以及原理....

  謝謝網絡上大神們分享的教程受益良多 : )

  雖然這篇博文比較長 但是實際上是因為我太啰嗦了........並不難做 並且建議先看一遍大體流程

#成果

  

  這個是我前不久寫的一個聊天室的客戶端, 源碼在這里 : https://github.com/LonelySinging/JavaChatRoom-client.git

  整個jre以及成品jar包加起來打包成exe之后只有11MB了 不過 說真的 還是覺得很大 因為jar包本身只有13KB 那么也就是說 jre環境是真的很大了 完整的jre是197MB了 QAQ

#開始

  那么現在就開始吧

  假設現在我們有

  1. 需要打包的jar包 (我這里是client.jar)

  2. 一台裝有Java環境的win10 64位電腦 JDK是1.8版本

  3. 能運行java代碼的環境 (我這里是eclipse)

  4. 火絨劍(我是內置版的)

  5. notepad++ 

#分析jre目錄 (我也才剛學習java沒多久 對於原理性的東西了解的也不多 所以出錯了別打我 QAQ)

  這是jdk1.8里面的jre目錄 最主要的就是bin和lib目錄

 

  #bin目錄

  bin目錄就是java.exe等幾個命令所在地了,應該也是JVM虛擬機的本體所在, 我的里面是這樣的

  bin目錄

  這里有 java.exe 也即是啟動jar的關鍵所在 以及一大堆的dll文件 精簡bin目錄的關鍵就是精簡這些dll文件,思路就是滿足java.exe的運行就可以了

  那么 接下來就是想辦法知道java.exe到底依賴那些dll了

 

   

  #lib目錄

  我的lib目錄是這樣的

  

  大家打開可以看到是一堆不知用途的文件以及各種jar包 那么這里的jar包里面就是我們在寫代碼的時候調用的類以及方法.

  還有字體文件(font文件夾里) 圖標資源(images文件夾里面) 還有一個最大的rt.jar文件 大小居然都是60多MB了 所以 精簡lib目錄的核心就是精簡rt.jar文件

  精簡的思路依舊是刪除不需要的就好了

 

#精簡開始

  #精簡bin目錄

  1. 把准備打包的jar包復制到bin 

  2. 新建一個批處理文件(新建文本文檔) 添加內容如下 (意思是 運行jar文件 並且把所有輸出信息導出到class.txt文件里)

    

java -jar -verbose:class client.jar >> class.txt

  保存 更改名字為 run.bat (注意 這是改了后綴名的 如果沒有后綴名 看下面)

  

 

 

  3. 雙擊運行 run.bat 然后對你的應用進行各種操作 是所有的功能都使用一遍(這里指的是,即使是一個編輯框 你也要嘗試輸入不同的東西 使用方向鍵操作光標等等 )這個非常重要!!!!!!!!!!! 為的是把所有使用到的類都輸出到class.txt文件 (class.txt文件是用來精簡lib目錄的 這里不管他) 

  4. 打開火絨劍 (其他的行為檢測軟件也可以 作用相同就行)  點擊進程 -> 然后選擇java.exe 然后就可以找到java.exe加載的所有dll文件

    

 

  5. 新建一個在桌面上新建一個文件夾 然后 新建兩個文件夾(bin和lib) 如圖

    

 

   6. 在上面火絨劍里面查看所用到的dll以及他們的路徑 找到他們 復制到新建的文件夾里的bin文件夾 得到如下 發現瞬間少了很多 (如果不是jre目錄下的dll文件不復制)

    

  7. 在上圖空白處按着shift然后右鍵菜單 在此處打開power shell 運行得到如下結果 原因是缺少jvm.cfg 這是JVM虛擬機的配置文件 然后直接復制完整的jre的lib目錄下的所有文件到自己精簡的lib目錄下

  然后再次運行一次./java (值得注意的是 power shell的話 必須加上./ 才會從當前目錄下找文件)

  做完這些之后 應該是可以在這里運行你准備打包的jar文件的 

  在bin目錄下新建一個批處理文件 內容如下

java -jar client.jar
pause

  復制你的jar文件 在這里我的是client.jar 

  雙擊run.bat就可以 運行你的jar文件了 如果不行 就是jar文件的原因

    

    

  8. 如果都可以了 那么 bin目錄終於精簡完了 TAT (打字好累)

 

  #精簡lib文件夾

  1. 這里我做的比較水...我對lib文件夾不是太了解 所以精簡的方法可能比較低級

  2. 思路就是 上面不是可是已經能夠運行jar文件了嗎 那么 我們刪除一個文件 就運行一次(這就是上面用批處理運行jar的原因 QAQ )

  3. 主要是刪除jar文件 從大到小 還可以嘗試刪除一些其他的文件 比如圖片文件 字體文件 不過可能會引起一些bug

  4.  這是我刪除之后

  

  5. 這樣的話 這是比較笨的方法 但是效果也還不錯 然后我們發現 最大的就是rt.jar文件了 那么接下來就就是精簡rt.jar

  6. 拓展方法 我在看博客的時候發現可以從class.txt文件里面找到用到的jar文件 但是,,,並不能記錄不是jar的文件  有些文件可能是配置文件 丟失了就不能用了 不過有人會的話 請在下面留言一下 我更新

 

  #精簡rt.jar文件

  1. 記得上面的到的class.txt文件嗎,把他復制到與你新建的jre目錄下 也即是與bin和lib目錄同目錄 (這里是為了方便 不復制也行)

  2. 現在 處理class.txt文件 用notepad++打開它 你會發現會有好多好多的東西TAT 我就被這個嚇到了 但是沒關系 接下來用notepad++的話 就會很簡單

  3. 刪除掉所有的 不帶方括號的行 

  4. Ctrl + F 然后搜索  "[Opened "   然后把所有帶"[Opened "個的行刪除掉 (是刪除一整行)

  5. Ctrl + F 然后搜索  "[Loaded "  點擊計數,然后點擊替換選項卡 全部替換 (替換的時候注意點一下計數,對比一下行數 應該是一樣的,,,,)

  6. 點擊"正則表達式"的單選框 在查找目標里面填上正則表達式 點擊計數,然后 替換->全部替換

    

  7.這樣 我們就得到了如下的東西 保存

  

 

  8. jar文件本質上是zip文件 那么 我們把rt.jar文件復制到class.txt同一個目錄 這樣的話 精簡的jre目錄下就有 bin、lib、class.txt、rt.jar文件或文件夾了 然后右鍵 "解壓到rt/" (我這里用的360壓縮) 這樣我么就得到了一個rt文件夾 里面是rt.jar的完整內容

  9. 然后新建一個文件夾名字是ort文件夾 用來放需要的class文件

  10. 然后執行如下代碼 這些代碼是復制別人博客的 再次非常感謝原作者 [https://blog.csdn.net/kkkwewewaqsdfas/article/details/11829349?t=1489849302000] :

    記得修改第一行 package ch1;

    另外 代碼中的InputOutput那個會出錯 因為缺少那個類 在上面原作者的鏈接里面可以找到InputOutput的java文件
    原作者的 InputOutput文件里面的main()方法會出錯 刪除即可

    2019年2月15補充: 如果不行折騰 以下代碼已經編譯成jar包 可以直接運行: 需求類復制工具 點擊下載即可 運行就知道怎么用了

package ch1;


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 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("要讀取的class.txt文件的絕對路徑  :");
           lineOfText = new BufferedReader(new InputStreamReader(System.in));
           String needfile = lineOfText.readLine();
           // get source folder direction
           System.out
                   .println(needfile
                           + "\n輸入jre/lib/rt.jar解壓後的rt文件夾所在的路徑 :");
           lineOfText = new BufferedReader(new InputStreamReader(System.in));
           String sdir = lineOfText.readLine();

           // get object folder direction
           System.out
                   .println(sdir
                           + "\n再輸入ort(所要存放拷貝過來的有用的.class文件的文件夾):");
           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();
       }
   }
}

  運行的時候填寫這樣的內容

    

  首先輸入usedClasses.txt的絕對路徑,回車,在輸入jre/lib/rt.jar解壓後的rt文件夾所在的路徑,回車,再輸入ort(所要存放拷貝過來的有用的.class文件的文件夾)。然后等上幾分中,期間會提示,你的產品程序所用到的jre不包含的類不存在,不用管,因為我們呢只拷貝rt文件中的.class文件 (這一段話復制自上面代碼所在博客)

  11. 這樣的話 在ort文件夾里面就有了很多文件夾 像是java、javax什么的 全選這些文件夾 右鍵 添加到 ort.zip 

    11.5: 將ort打包的方法還有一個 將cmd工作目錄切換到 有[ort]文件夾的哪里 然后執行命令 [jar cvf rt.jar -C ort/ .] (方括號里面的全部內容) 即可 方法來自評論區:水瓶座鬼才

  12. 得到ort.zip文件之后改名為rt.jar

  13. 刪除lib目錄下的rt.jar文件 用上面的rt.jar文件(也就是我們自己打包的)替換

  14. 在bin目錄下運行run.bat文件 然后如果成功了就測試一下各種功能 確認沒問題的話 jre環境就已經精簡成功了 可以直接看下面的exe打包階段了

  15 如果有錯誤 應該是這樣的 那么應該是這樣的 這里的話 明顯是class文件缺失

     

  16. 在精簡的rt.jar文件上面右鍵 用360壓縮打開

  17. 打開之前解壓的rt.jar文件夾(完整的rt.jar文件解包之后的rt文件夾)

  18. 根據上圖中提示的丟失class文件的路徑, 在360壓縮窗口和rt文件夾中找到丟失的class文件 然后拖到360壓縮窗口里 需要保證class文件的位置是一樣的 (比如在rt文件夾里面的class文件路徑是java/lang 目錄下 那么在360壓縮里也應該是這個目錄)如果添加出錯 那就關閉上圖的窗口

  19.  再運行bin目錄下的run.bat批處理文件 可能還會有錯 有錯的話 就繼續上一個步驟 直到沒有錯誤

  20. 這樣的話 jre環境就已經精簡成功了 : ) 接下來就是打包成exe了

 

#打包成exe 

  1. 這個就比較簡單了 使用現成的工具就可以了 就是      winrar

  2. 不過呢 這個軟件似乎是沒有執行命令行的作用的 解決方法就是用C語言寫一個啟動器 QAQ  (其他語言也行 只要是個exe就行了) 但是呢 直接用C語言執行 java -jar client.jar 這個命令會有一個問題 就是會有一個控制台窗口 (我這里是圖形界面 如果沒有圖形界面的話 那就不用考慮了)  這個據說用javaw 運行就沒問題 不會出現控制台窗口 但是我失敗了/QAQ 不知道為啥 所以接下來需要這樣做 

  3. 編譯一個exe文件 C語言代碼如下  說明一下 C語言如果是控制台程序的話 運行的時候還是有控制台窗口的 所有建立的工程是win32工程 也就是默認帶窗口的那種 如果有小伙伴不知道怎么弄 請在下面留言 我會補充一下

#include "stdafx.h"
#include "無窗口的vbs運行.h"

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)

{
    system("run.vbs");
    return 0;
}

  核心代碼就是是system("run.vbs");

  4. 那么接下來就編寫run.vbs 內容如下 作用是運行一個名為run.bat的批處理 並且把窗口隱藏起來

Set ws = CreateObject("Wscript.Shell")   
ws.run "cmd /c run.bat",vbhide

  5. 修改bin目錄下的run.bat文件 修改內容如下 然后復制到與bin、lib文件夾同目錄

cd bin
java -jar client.jar

  6. 上面兩步完成了GUI界面Java程序 隱藏控制台窗口運行 然后把上面編譯的啟動器exe放在與bin、lib文件夾同目錄 最終是這樣的 雙擊run.vbs或者啟動器.exe應該都是可以無控制台窗口運行的

  7. 終於到了最終打包的時候了 TAT  下載winrar 安裝

  8. 全選上圖所有的文件及文件夾 壓縮為rar格式的壓縮包

  9. 用winrar打開rar壓縮包 然后選擇自解壓格式

 

 

 

 

 

10. 然后點擊確定 再點確定 就可以得到 在上圖高級設置里面還可以修改生成exe的圖標

  看到了嗎 整個環境加上 jar打包成exe之后只有11MB 不到了

  最終  雙擊運行一下 TAT 驚喜的發現 成功了!!!!!!!!!!!!!!!!!!!!!!!!!! 

  如果不能打包成功 請打開  https://blog.csdn.net/fz835304205/article/details/46942589

  可以看看這個博文 謝謝這位博主分享經驗 ^_^

#期間可能遇到的問題

  1. class文件丟失太多了 這個主要是在生成class.txt文件的時候沒有把所有程序功能使用一遍造成的 所謂的使用包括點擊窗口 以及各個組件的功能

  2. 在往rt.jar文件里面添加class文件的時候添加不進去 這個可能是因為你的程序還在運行 需要關閉控制台窗口

  3. 有時候可能往rt.jar里面添加class文件也不能解決問題 那么就把相關的整個文件夾替換進去

  4. 完成之后你的程序可能會有各種bug那么 你得讓控制台窗口出來 然后查看異常 看看丟失了那個class文件 把他 添加到rt.jar中相同的文件夾下

  5. 打包完了發現不能雙擊運行提示丟失DLL文件 那么 你得安裝運行庫 百度運行庫合集就可以了

  6. 這樣精簡的jre對其他系統(win7 winxp)的兼容性比較差 甚至不能運行 解決方案就是在目標系統上運行一次 得到class.txt 整合所有用到的class文件應該就可以了(其實我這個沒有嘗試 是個思路) 

  7. 如果還有問題請在下方留言 有建議也請留言 謝謝 : )

   

感謝以下幾篇文章:

  https://blog.csdn.net/kkkwewewaqsdfas/article/details/11829349?t=1489849302000

  https://blog.csdn.net/xiaoping8411/article/details/6973887

  https://blog.csdn.net/ema1995cylove/article/details/52792361

  https://blog.csdn.net/fz835304205/article/details/46942589 (winrar打包方式)

  還有幾個沒收藏 謝謝了 ^_^

                      -------轉發請寫明出處 謝謝

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM