額,馬上就要面試了,java的底層肯定是需要瞭解的。網上找了找java的底層文章,做個記號。java底層主要是類的載入、串連和初始化。
本文主要分為四個方面:
(1)java底層概述
(2)new和newInstance()方法的區別
(3)深入探討java的載入機制
(4)一個完整java程式沖java xxx.class執行的完整過程
四個部分都是來自網上的資料,四個部分看完,應該對java的底層有些瞭解了。
下面文字來自《瘋狂java講義》:
1.類的載入、串連和初始化
當系統主動使用某個類,如果該類還未載入到記憶體中,系統會載入、串連、初始化三個步驟。
1.類的載入將類的Class檔案讀入記憶體中,並為之建立一個java.lang.Class對象。
Class檔案的來源:
(1)從本地載入class檔案
(2)從jar包中載入(系統api)
(3)從網路載入
2.類的串連:
串連階段負責將類的位元據合并到JRE中。
3.類的初始化
類的初始化時機:
(1)建立類的執行個體:new操作符、反射建立執行個體、通過還原序列化;
(2)調用某個類的靜態方法;
(3)訪問某個類的靜態屬性(final屬性除外)。
2.new操作符和newInstance()方法的區別
http://lvqingboy-163-com.iteye.com/blog/657599
在初始化一個類,產生一個執行個體的時候,newInstance()方法和new關鍵字除了一個是方法,一個是關鍵字外,最主要有什麼區別?它們的區別在於建立對象的方式不一樣,前者是使用類載入機制,後者是建立一個新類。那麼為什麼會有兩種建立對象方式?這主要考慮到軟體的可伸縮、可擴充和可重用等軟體設計思想。
Java中原廠模式經常使用newInstance()方法來建立對象,因此從為什麼要使用原廠模式上可以找到具體答案。 例如:
class c = Class.forName(“Example”);
factory = (ExampleInterface)c.newInstance();
其中ExampleInterface是Example的介面,可以寫成如下形式:
String className = "Example";
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();
進一步可以寫成如下形式:
String className = readfromXMlConfig;//從xml 設定檔中獲得字串
class c = Class.forName(className);
factory = (ExampleInterface)c.newInstance();
上面代碼已經不存在Example的類名稱,它的優點是,無論Example類怎麼變化,上述代碼不變,甚至可以更換Example的兄弟類Example2 , Example3 , Example4……,只要他們繼承ExampleInterface就可以。
從JVM的角度看,我們使用關鍵字new建立一個類的時候,這個類可以沒有被載入。但是使用newInstance()方法的時候,就必須保證:1、這個類已經載入;2、這個類已經串連了。而完成上面兩個步驟的正是Class的靜態方法forName()所完成的,這個靜態方法調用了啟動類載入器,即載入java API的那個載入器。
現在可以看出,newInstance()實際上是把new這個方式分解為兩步,即首先調用Class載入方法載入某個類,然後執行個體化。 這樣分步的好處是顯而易見的。我們可以在調用class的靜態載入方法forName時獲得更好的靈活性,提供給了一種降耦的手段。
最後用最簡單的描述來區分new關鍵字和newInstance()方法的區別:
newInstance: 弱類型。低效率。只能調用無參構造。
new: 強型別。相對高效。能調用任何public構造。
我的理解:
其實,newInstance()方法是明確式載入:
Class 類的 forName (String s)方法把自訂類 TestClass 載入進來,並通過 newInstance ()方法把執行個體初始化
new操作符是隱式載入:JRE在執行到 new 關鍵字的時候就會把對應的執行個體類載入進入記憶體。
3.深入討論java的類載入器機制
http://www.blogjava.net/William/archive/2006/08/25/65804.html
Java 語言是一種具有動態性的解釋型程式設計語言,當指定程式啟動並執行時候, JAVA 虛擬機器就將編譯產生的 . class 檔案按照需求和一定的規則載入進記憶體,並組織成為一個完整的 Java 應用程式。 Java 語言把每個單獨的類 Class 和介面 Implements 編譯成單獨的一個 . class 檔案,這些檔案對於 Java 運行環境來說就是一個個可以動態載入的單元。正是因為 Java 的這種特性,我們可以在不重新編譯其它代碼的情況下,只編譯需要修改的單元,並把修改檔案編譯後的 . class
檔案放到 Java 的路徑當中,等到下次該 JAVA 虛擬機器重新啟用時,這個邏輯上的 Java 應用程式就會因為載入了新修改的 .class 檔案,自己的功能也做了更新,這就是 Java 的動態性。
下面用一個簡單的例子讓大家對 Java 的動態載入有一個基本的認識:
class TestClassA{ publicvoid method(){ System.out.println("LoadingClassA"); }}public class ClassLoaderTest { publicstatic void main(String args[]){ TestClassAtestClassA = new TestClassA(); testClassA.method(); }}
編譯後輸入命令: java -verbose:class ClassLoaderTest ,執行檔案。 (java-verbose:class:查看class的載入情況)
輸出結構 (1)
圖( 1 )
從運行結果我們可以看到, JRE ( JavaRuntime Environment )首先載入 ClassLoaderTest檔案,然後再載入 TestClassA 檔案,從而實現了動態載入。
1. 積極式載入與依需求載入
Java 運行環境為了最佳化系統,提高程式的執行速度,在 JRE 啟動並執行開始會將 Java 運行所需要的基本類採用積極式載入( pre-loading )的方法全部載入要記憶體當中,因為這些單元在 Java 程式啟動並執行過程當中經常要使用的,主要包括 JRE 的 rt.jar 檔案裡面所有的 .class 檔案。
當 java.exe 虛擬機器開始運行以後,它會找到安裝在機器上的 JRE 環境,然後把控制權交給 JRE , JRE 的類載入器會將 lib 目錄下的 rt.jar 基礎類別檔案庫載入進記憶體,這些檔案是 Java 程式執行所必須的,所以系統在開始就將這些檔案載入,避免以後的多次 IO 操作,從而提高程式執行效率。
圖( 2 )我們可以看到多個基礎類被載入, java.lang.Object,java.io.Serializable 等等。
圖( 2 )
相對於積極式載入,我們在程式中需要使用自己定義的類的時候就要使用依需求載入方法( load-on-demand ),就是在 Java 程式需要用到的時候再載入,以減少記憶體的消耗,因為 Java 語言的設計初衷就是面向嵌入式領域的。
在這裡還有一點需要說明的是, JRE 的依需求載入究竟是在什麼時候把類載入進入內部的呢?
我們在定義一個類執行個體的時候,比如 TestClassA testClassA ,這個時候 testClassA 的值為 null ,也就是說還沒有初始化,沒有調用 TestClassA 的建構函式,只有當執行 testClassA = new TestClassA() 以後, JRE 才正真把TestClassA 載入進來。
2.隱式載入和明確式載入
隱式載入:使用new操作符;
顯示載入:使用forname()方法:Class 類的 forName (String s) 方法把自訂類 TestClass 載入進來,並通過 newInstance ()方法把執行個體初始化
Java 的載入方式分為隱式載入( implicit)和顯示載入( explicit ),上面的例子中就是用的隱式載入的方式。所謂隱式載入就是我們在程式中用 new 關鍵字來定義一個執行個體變數, JRE 在執行到 new 關鍵字的時候就會把對應的執行個體類載入進入記憶體。隱式載入的方法很常見,用的也很多, JRE 系統在後台自動的協助使用者載入,減少了使用者的工作量,也增加了系統的安全性和程式的可讀性。
相對於隱式載入的就是我們不經常用到的顯示載入。所謂顯示載入就是有程式員自己寫程式把需要的類載入到記憶體當中,下面我們看一段程式:
class TestClass{ publicvoid method(){ System.out.println("TestClass-method"); }} public class CLTest { publicstatic void main(String args[]) { try{ Classc = Class.forName("TestClass"); TestClassobject = (TestClass)c.newInstance(); object.method(); }catch(Exceptione){ e.printStackTrace(); } }}
我們通過 Class 類的 forName (String s) 方法把自訂類 TestClass 載入進來,並通過 newInstance ()方法把執行個體初始化。事實上 Class 類還很多的功能,這裡就不細講了,有興趣的可以參考 JDK 文檔。
Class 的 forName() 方法還有另外一種形式:Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要載入類的名稱,flag 表示在調用該函數載入類的時候是否初始化靜態區, classloader 表示載入該類所需的載入器。
forName (String s) 是預設通過ClassLoader.getCallerClassLoader() 調用類載入器的,但是該方法是私人方法,我們無法調用,如果我們想使用 Class forName(String s, boolean flag, ClassLoader classloader) 來載入類的話,就必須要指定類載入器,可以通過如下的方式來實現:
Test test = new Test();//Test 類為自訂的一個測試類別;
ClassLoader cl = test.getClass().getClassLoader();
//擷取 test 的類裝載器;
Class c =Class.forName("TestClass", true, cl);
因為一個類要載入就必需要有載入器,這裡我們是通過擷取載入 Test 類的載入器 cl 當作載入 TestClass 的類載入器來實現載入的。
3. 自訂類載入機制
之前我們都是調用系統的類載入器來實現載入的,其實我們是可以自己定義類載入器的。利用 Java 提供的 java.net.URLClassLoader 類就可以實現。下面我們看一段範例:
try{ URLurl = new URL("file:/d:/test/lib/"); URLClassLoaderurlCL = new URLClassLoader(new URL[]{url}); Classc = urlCL.loadClass("TestClassA"); TestClassAobject = (TestClassA)c.newInstance(); object.method(); }catch(Exceptione){ e.printStackTrace(); }
我們通過自訂的類載入器實現了 TestClassA 類的載入並調用 method ()方法。分析一下這個程式:首先定義 URL 指定類載入器從何處載入類, URL 可以指向網際網路上的任何位置,也可以指向我們電腦裡的檔案系統 ( 包含 JAR 檔案 ) 。上述範例當中我們從 file:/d:/test/lib/ 處尋找類;然後定義 URLClassLoader 來載入所需的類,最後即可使用該執行個體了。
4. 類載入器的階層體系
討論了這麼多以後,接下來我們仔細研究一下 Java 的類載入器的工作原理:
當執行 java ***.class 的時候, java.exe 會協助我們找到 JRE ,接著找到位於 JRE 內部的 jvm.dll ,這才是真正的 JAVA 虛擬機器 , 最後載入動態庫,啟用 JAVA 虛擬機器。虛擬機器啟用以後,會先做一些初始化的動作,比如說讀取系統參數等。一旦初始化動作完成之後,就會產生第一個類載入器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰寫而成,這個 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化動作之外,最重要的就是載入
Launcher.java 之中的 ExtClassLoader ,並設定其 Parent 為 null ,代表其父載入器為 BootstrapLoader 。然後 Bootstrap Loader 再要求載入 Launcher.java 之中的 AppClassLoader ,並設定其 Parent 為之前產生的 ExtClassLoader 實體。這兩個載入器都是以靜態類的形式存在的。這裡要請大家注意的是, Launcher$ExtClassLoader.class 與Launcher$AppClassLoader.class
都是由 Bootstrap Loader 所載入,所以 Parent 和由哪個類載入器載入沒有關係。
下面的圖形可以表示三者之間的關係:
父類
父類
載入
載入
BootstrapLoader
PARENT
AppClassLoader
PARENT
ExtClassLoader
這三個載入器就構成我們的 Java 類載入體系。他們分別從以下的路徑尋找程式所需要的類:
BootstrapLoader : sun.boot.class.path
ExtClassLoader: java.ext.dirs
AppClassLoader: java.class.path
這三個系統參量可以通過 System.getProperty() 函數得到具體對應的路徑。大家可以自己編程實現查看具體的路徑。
5. 總結
瞭解 Java 的類載入機制對我們熟練靈活運用 Java 語言,提高程式的運行效率有著非常重要的作用,知其然也要知其所以然,這樣才能從整體提高程式的品質。
4.深入java底層
http://bigwhite.blogbus.com/logs/579744.html
在一個朋友的書架上發現王森著的《Java深度曆險》一書,看了書的前言瞭解該書是關於Java底層技術內幕的。懷著好奇心瀏覽了一下,談不上有太多收穫,但也記下了一些自認為有益的兩點。
Java xxx
我們在命令列下敲入:“java xxx”後會發生什麼呢?
流程如下:
1. 找到JRE;
2. 找到JVM.dll;
3. 啟動JVM,並進行初始化;
4. 產生Bootstrap Loader;
5. 載入ExtClassLoader;(Ext – Extended)
6. 載入AppClassLoader;
7. 載入xxx類。
書中提到Bootstrap Loader、ExtClassLoader和AppClassLoader構成了Java的“類載入器繼承體系--class loader hierarchy”,其中BootstrapLoader是由C++編寫的,其他兩個是由Java寫的。之所以成為“繼承體系”是因為這三個loader之間是有聯絡的。Bootstrap Loader負責載入ExtClassLoader,後者ExtClassLoader就將其parent置為Bootstrap Loader。AppClassLoader較為特殊,雖然由Bootstrap載入,但是其parent卻置為ExtClassLoader。其原因是為了實現“委託模型”。簡述“委託模型”就是當類載入器有載入類的需求時,會先請求其parent使用其搜尋路徑協助載入,如果其parent找不到,才使用自己的搜尋路徑進行載入。如上述所說當ExtClassLoader想載入AppClassLoader類時它首先請求其parent
“Bootstrap Loader”幫忙,Bootstrap Loader將AppClassLoader載入後,由於這個載入是ExtClassLoader請求的,所以AppClassLoader的parent還是置為ExtClassLoader而不是Bootstrap Loader。
類的載入流程
類載入的時候遵循一個原則:“類載入器會依類的繼承體系從上至下依次載入”。舉個例子:“如果C繼承了B並實現了介面I,而B有繼承自A”,則類載入器在載入C時,載入的次序會是A-> B->I->C,(註:interface會如同class一樣被Java編譯器編譯為獨立的.class檔案)
其實我對底層的東西並未給與太多的關注,如果在哪個項目中需要我去瞭解底層的話,我會去很好的學習。
額,順便看看什麼事DLL
http://baike.baidu.com/view/887.htm
動態連結程式庫英文為DLL,是DynamicLink Library 的縮寫形式,DLL是一個包含可由多個程式同時使用的代碼和資料的庫,DLL不是可執行檔。動態連結提供了一種方法,使進程可以調用不屬於其可執行代碼的函數。函數的可執行代碼位於一個
DLL 中,該 DLL 包含一個或多個已被編譯、連結並與使用它們的進程分開儲存的函數。DLL 還有助於共用資料和資源。多個應用程式可同時訪問記憶體中單個DLL 副本的內容。DLL 是一個包含可由多個程式同時使用的代碼和資料的庫。