首頁 > 軟體

在Linux中安裝Oracle JDK 8以及JVM的類載入機制

2020-06-16 18:01:47

參考資料

  該文中的內容來源於 Oracle 的官方文件 Java SE Tools Reference 。Oracle 在 Java 方面的文件是非常完善的。對 Java 8 感興趣的朋友,可以直接找到這個總入口 Java SE 8 Documentation ,想閱讀什麼就點什麼。本部落格不定期從 Oracle 官網搬磚。

前言

  在 Linux 中使用 Java,我一般都是直接使用 Linux 發行版自帶的軟體包,一個命令即可搞定 JDK 的安裝。但是 Linux 發行版中自帶的 JDK 往往是 OpenJDK,雖說不影響使用,但是如果要按照 Oracle 的文件進行學習,還是安裝一個 Oracle JDK 比較好。OpenJDK 和 Oracle JDK 頗有淵源,其內容大部分都是一樣的,除了少數 Oracle JDK 中涉及到專利技術或授權因素的部分程式碼。另外,Oracle JDK 自帶的工具要更多一點,如商業化的 Java Mission Control。最後,就是商標不一樣了,請記住,Java 現在是 Oracle 的註冊商標哦。至於授權協定的不同,我們普通使用者不需要關心,既然 Oracle 提供免費下載,那我們只管用就是了。

  另外,在 Java SE 8 文件中,還有這樣一副圖片,完整地展示了 Java SE 的組成。在官網中的該圖片的每一個小方格都是一個超連結,如果想學習哪一塊的知識,點選相應的小方塊就可以了。我這裡只是一個截圖,沒有導航功能。把圖片放到這裡,就是讓自己對 Java 的核心技術有一個總攬。如下圖:
 

下載和安裝Oracle JDK

  要下載 Oracle JDK,肯定要去 Oracle官網 ,在官網裡面找到 Java 的下載頁面是沒有什麼難度的,我就不多說了。我選擇的版本是 JDK 8 For Linux 的 64 位版本。同時,我沒有選擇 rpm 包,而是選擇了 .tar.gz 這個壓縮包,如下圖:

  將壓縮包下載下來後,放到我自己的主目錄,解壓後就可以使用。這完全是我自己的私有版本,不會與系統中安裝的其它 Java 版本發生衝突。缺點就是當我要呼叫 javajavac 這樣的命令的時候,我必須指定完整的路徑。解壓的命令為 tar zxf jdk1.8.0u45-linux-x64.tar.gz

  熟悉 Java 的朋友肯定知道,以前安裝 JDK 的時候,必須設定 PATH 和 CLASSPATH 兩個環境變數。但是在 JDK 8 中,這都不是必須的。不設定 PATH 環境變數是因為在我的系統中存在 OpenJDK,即使將 ~/jdk1.8.0_45/bin 加入到 PATH,呼叫 java 命令的時候使用的依然是 OpenJDK。所以使用 Oracle JDK 的時候,我就直接輸完整路徑好了。不設定 CLASSPATH 的原因是 Java SE 8 可以自動探測 Class 所在的路徑。當然,設定 CLASSPATH 環境變數也是可以的,只是沒有那個必要了。如下圖:

  (截完圖後我又想到,其實要想覆蓋 OpenJDK 的 java 命令也是可以的,只要這樣設定環境變數 export PATH=~/jdk1.8.0_45/bin:$PATH,而不是 export PATH=$PATH:~/jdk1.8.0_45/bin 就可以了。使用 Oracle JDK 的時候就不用每次都輸入完整路徑了。)

  然後,寫個 HelloWorld 程式測試一下。這個程式實在是太簡單了,根本沒有必要動用 Eclipse 這樣龐大的 IDE,使用 Vim 足以。如下圖:

  寫這個測試程式就是為了證明 Oracle JDK 8 可以執行,特別是為了證明 JDK 8 不用設定 CLASSPATH 環境變數也可以執行。這個程式除了輸出“Hello, world!”以外,還輸出 Java 執行時預設的系統設定。如下圖:

  在這些預設的系統設定中,有幾個比較重要的值,我特意用紅色的框框將其標記出來了。Java 執行時就是從這些路徑中載入 class 的。如下圖:

  下面來看 Java 執行時是如何搜尋並載入 class 的。在以下文字中,我均使用 JDK 代替 ~/jdk1.8.0_45 目錄,表示 Oracle JDK 8 的安裝路徑。

類檔案如何被發現並載入

  先來說說關於 Java 的閒話,大家都知道,執行 Java 程式需要一個啟動器,比如 java 命令,大家也知道,在 Java 中一切都是類,只有屬於類的方法,而沒有單獨的函數。這一點王垠大神有過批判。確實,Java 是一門純物件導向的語言,太純了,這個世界怎麼可能所有的東西都是物件呢?本來就應該有一些東西應該存在於物件之外嘛,比如操作多個物件的純函數,比如程式的入口點 main 函數。所以從語言的角度講,Java 受到批判是理所當然的。但是, Java 的這種“純”,簡化了它的實現。因為 Java 中一切都是類,所以可以讓 Java 程式碼編譯後,每一個類生成一個 .class 檔案,然後將 .class 檔案放到相應的目錄中,或者打包成 .jar 壓縮包。程式執行的時候,用一個啟動器將相應的 .class 檔案載入,並執行其中的位元組碼即可。這種設計非常的簡單、方便。而 C# 並不採用這種組織類和位元組碼的方式,卻學 Java 讓所有的函數都成為類的方法,我認為相反是不妥的。

  JVM 按如下順序載入 .class 檔案:

  1. Bootstrap classes。這些類是 Java 平台的基礎。其中包含大家都熟悉的 rt.jar,還包含其它一些類或 jar 包。
  2. Extension classes。這些類擴充套件了 Java 平台,或者說,這些類利用了 Java 的擴充套件機制。這些類有兩個要求:其一是必須打包成 jar,不能是單獨的 .class 檔案,並且這些 jar 包必須存放於 JDK/jre/lib/ext 目錄下;其二是這些類不能參照 Bootstrap classes 和 Extension classes 之外的類。
  3. User classes。也就是使用者自定義類。我們自己寫的程式已經我們從其它地方下載的第三方庫都屬於這個範疇。JVM 最後載入這些類,到哪裡去找呢?這就需要用到 CLASSPATH 環境變數了,或者在程式啟動的時候使用 -classpath 引數。

  在以上三個過程中,使用者其實並不需要指定 Bootstrap classes 和 Extension classes 所在的目錄,只需要指定 User classes 所在的目錄就可以了。這是 JDK 8 和之前版本的區別。Bootstrap classes 和 Extension classes 所在的路徑可以自動搜尋。前面的圖片中我標出的 Java 系統設定中的 sun.boot.class.path 項就代表了 Bootstrap classes 所在的目錄,預設為 JDK/jre/lib 中的 rt.jar 和其它一些 jar 檔案。可以在啟動程式的時候使用 -Xbootclasspath 選項更改這個路徑。而 Extension classes 的路徑就是 JDK/jre/lib/ext 目錄,在前面的圖片中我也標出來了。

  關於 User classes 的搜尋路徑,如果不指定的話,預設就是當前目錄.,我前面圖片中標出的就是預設值。如果重新指定 User classes 的搜尋路徑的話,就不會從當前目錄進行搜尋了,如果要從當前目錄進行搜尋,必須將當前目錄明確地新增到 CLASSPATH 中。指定 User classes 的搜尋路徑有以下幾種方式:

  1. 設定 CLASSPATH 環境變數;
  2. 程式啟動時使用 -cp 或 -classpath 選項;
  3. 如果使用 java -jar 方式啟動程式,則從 jar 包中的 manifest 檔案中的 Class-Path 設定項中指定的路徑搜尋。

  搜尋路徑可以是目錄,也可以是 jar 包,也可以是它們之間的任意組合,每一個項之間用:分割。另外,路徑中還可以使用萬用字元*,但是該萬用字元只能匹配某一個目錄下的所有 jar 包,而不能匹配 .class 檔案,更加不能匹配子目錄。

  那麼問題來了,既然 CLASSPATH 中可以包含 jar 包,而 jar 包中又可以指定 Class-Path,而這個 Class-Path 中又可以指定其它的 jar 包,這樣會造成無限迴圈嗎?JVM 在搜尋 jar 包的時候有什麼規則嗎?有的,如下三條:

  1. jar包中指定的 Class-Path 會被當成 CLASSPATH 的組成部分,並且放在 jar 包中的其它 class 檔案之前,而 class 檔案的搜尋是按照其路徑出現在 CLASSPATH 中的順序進行的,先找到先得。
  2. 如果之前掃面過的 jar 包又出現了,就不進行重複掃描;
  3. 如果一個 jar 包是作為 Java 的擴充套件安裝的,也就是其在 JDK/jre/lib/ext 目錄中,就忽略它的 Class-Path 設定項。

  關於 javacjavadoc 命令和 tools.jar 又有一些例外,這些例外的存在是因為 javacjavadoc 這些程式的執行對 .class 檔案的雙重需求決定的。作為 JDK 的工具, javacjavadoc 的執行需要 tools.jar 中的類的支援,另外,當 javacjavadoc 編譯程式的時候,又需要解析原始碼中對其它類的參照。它的基本規則如下:

  1. 執行 javacjavadoc 這些命令時,這些命令本身使用的 Bootstrap classes 、Extension classes 和 tools.jar 是不能改變的,它們只使用它們所在的 JDK 中的版本;
  2. 如果是 javacjavadoc 需要解析的其它的程式碼中用到了 tools.jar 中的類,則必須將 tools.jar 加入到 User classes 的搜尋路徑中才能起作用;如果要讓它們解析的程式碼中參照不同版本的 Bootstrap classes 和 Extension classes,可以使用這兩個命令的 -bootclasspath 和 -extdirs 選項指定。

  最後,類的載入還受 Class Loader 和 安全策略(Security Policies)的影響。在 Java 中,我們可以使用不同的 Class Loader,也可以編寫自己的 Class Loader,但是大部分時候,我們都是使用的內建的 Class Loader。在 Java 中可以啟用不同的安全策略,我們信任的類就允許它載入,不信任的就不允許。如果沒有開啟安全策略,則所有的類都認為是被信任的。但是即使開啟了安全策略,也只會影響到 Extension classes 和 User classes,對 Bootstrap classes 是沒有影響的。

CLASSPATH和Package的關係

  在 Java 中還有一個讓人頭疼的地方,就是 .class 檔案的存放位置和 package 之間的關係。在 Java 語言中,為了不發生命名衝突,特意引入了 package 機制,將 Java 中的類劃分到不同的 package 之中。在前面我展示的 HelloWorld.java 中,我將它的 package 設定為 com.xkland.java_8_study,然後編譯這個程式的事後,我特意使用了 javac -d . 選項,設定將當前目錄 . 作為編譯後的目標位置。編譯完成後,我還特意使用 tree 命令檢視了一下目錄的結構。

  從結果中可以看出,HelloWorld.class 存放的位置為 ./com/xkland/java_8_study/HelloWorld.class。這個目錄層次和 package 的命名是完全一致的。而且必須一致,否則將會找不到 .class 檔案,也無法執行程式。所以,如果你的 package 的命名是 a.b.c.d 這樣的,則只能組織成 a/b/c/d/xx.class 這樣的目錄樹,並且從 a 目錄的上一級目錄開始搜尋這個 class 才找得到,也就是說,必須將 a 目錄的上一級目錄加入 CLASSPATH。對於原始碼沒有這個限制,但是使用同樣的方式組織原始碼是一個好的做法。如果原始碼目錄是扁平的,就像我前面的例子一樣,則呼叫 javac 的時候一定要指定 -d 引數,它會自動生成 .class 檔案的目錄結構。

總結

  沒什麼好總結的。就是看了 Oracle Java SE 8 官方文件後的一點記錄。 How classes are found? 還是值得偶爾複習一下的。

Ubuntu 14.04安裝JDK1.8.0_25與設定環境變數  http://www.linuxidc.com/Linux/2015-01/112030.htm

Ubuntu 14.04 LTS安裝Oracle JDK 1.8  http://www.linuxidc.com/Linux/2014-11/109216.htm

CentOS6.3安裝JDK和環境設定 http://www.linuxidc.com/Linux/2012-09/70780.htm

Ubuntu 14.04 安裝 JDK8  http://www.linuxidc.com/Linux/2014-09/106218.htm

Ubuntu下安裝JDK圖文解析 http://www.linuxidc.com/Linux/2014-09/107291.htm

本文永久更新連結地址http://www.linuxidc.com/Linux/2015-04/116491.htm


IT145.com E-mail:sddin#qq.com