【转】深入理解JVM
http://www.cnblogs.com/liupeizhi/articles/1942764.html
1.類的加載:
在java中可分為四類加載器
引導(dǎo)類加載器 Bootstrap Loader 用來(lái)加載%java_home%lib下的核心類庫(kù)像String、Date等等
擴(kuò)展類加載器 Extension Loader 用來(lái)加載%java_home%lib/ext下的擴(kuò)展api
系統(tǒng)類加載器 AppClassLoader 加載classpath下面的類文件,我們的所有類文件默認(rèn)都是由它來(lái)加載的,怎么樣,感覺(jué)親切吧
用戶自定義的類加載器
下面我們舉例說(shuō)明類的加載
public class A{
public static void main(String args[]){
B b=new B();
}
}
Public class B{
Public static String a="hello";
public static String b=getValue();
Static{
System.out.println("Hello World");
}
Public static void getValue(){
}
}
假如我們自己定義了一個(gè)名字為A 的類,當(dāng)我們啟動(dòng)虛擬機(jī)即(java A)的時(shí)候,會(huì)創(chuàng)建一個(gè)JVM實(shí)例。現(xiàn)在我們就看類B 的加載過(guò)程。由于B是被A 引用 的,所以B是由A的類加載器進(jìn)行加載。在這里我們不得不說(shuō)一下類加載器是比較孝順的孩子,為什么這么說(shuō)呢,因?yàn)轭惣虞d器在加載類的時(shí)候采用雙親委托機(jī)制。簡(jiǎn)單說(shuō)就是在加載類的時(shí)候,加載器會(huì)調(diào)用父類加載器來(lái)加載,父類再調(diào)用父類,依次類推。這個(gè)說(shuō)起來(lái)比較抽象,我們這里給出源代碼來(lái)表示一下其中的加載過(guò)程:
public Class loadClass(String name){
ClassLoader parent=this.getClassLoader().getParent();
Try{
Class c=findLoadedClass(name);
//如果這個(gè)類沒(méi)有被加載
If(c!=null){
//如果有父類加載器
If(parent!=null)
parent.loadClass(name);
Else
BootstrapLoader.loadClass(name)
}catch(FileNotFoundException ex){
//如果父類加載器找不到,就調(diào)用自己的findClass查找
this.findClass(name);
}
//如果這個(gè)類已經(jīng)被加載
Else
Return c;
}
這代碼是我自己寫(xiě)的,是對(duì)源代碼的簡(jiǎn)化表示,不要直接拷貝使用,如果想要知道詳細(xì)內(nèi)容,建議參源碼。這段可以完全清晰地表示出類加載器的調(diào)用關(guān)系了。但是里面有個(gè)問(wèn)題,相信各位都會(huì)發(fā)現(xiàn)了,就是 BootstrapLoader.loadClass(name).BootstrapLoader為什么不創(chuàng)建實(shí)例呢?因?yàn)锽ootstrapLoader并不是用java寫(xiě)的,是一個(gè)本地方法(native),也就是說(shuō)是用c/c++或者其他語(yǔ)言編寫(xiě)的方法。為什么要這么做呢?主要是因?yàn)槲覀兊念惣虞d器也是類,如果他們都是用java實(shí)現(xiàn),那么他們?nèi)绾渭虞d?所以,sun給了我們一個(gè)引導(dǎo)類加載器用來(lái)加載其他的類加載器,之后我們才能用這些類加載器加載我們的類文件。這里我們說(shuō)一下他們的父子關(guān)系。
我們自定義的類加載器的父類加載器是 AppClassLoader
AppClassLoader的父類加載器是Extension Loader
Extension Loader的父類加載器是 Bootstrap Loader
當(dāng)我們加載類B的時(shí)候,由于沒(méi)有指定它的類加載器,默認(rèn)由AppClassLoader進(jìn)行加載,調(diào)用loadClass()方法,AppClassLoader發(fā)現(xiàn)它的parent不是null,就會(huì)調(diào)用父類加載器(Extension Loader)加載,Extension Loader發(fā)現(xiàn)它的父母是null(因?yàn)锽ootstrapLoader 不是java寫(xiě)的,所以不會(huì)被Extension Loader訪問(wèn)到)于是就調(diào)用BootstrapLoader來(lái)加載,由于我們的B類是在我們的classpath中,所以必然會(huì)產(chǎn)生ClassNotFoundException ,接著調(diào)用自己的findClass進(jìn)行查找,ExtensionLoader訪問(wèn)的是%java_home%/lib/ext下面的類,必然也無(wú)法找到我們的B。于是會(huì)在AppClassLoader中捕獲到異常,然后接著調(diào)用AppClassLoader的findClass進(jìn)行加載,結(jié)果找到了。
終于啊,經(jīng)過(guò)這么復(fù)雜的遞歸調(diào)用和冒泡查找后找到了我們的類B 了。至于為什么要設(shè)計(jì)的這么復(fù)雜,直接加載不就完了嗎,干嘛搞得這么難受。這主要是出于安全性的考慮。你想想,這個(gè)過(guò)程中總是由Bootstrap來(lái)加載核心類,假如你自己寫(xiě)了一個(gè)名字叫String的類,里面含有攻擊性的代碼,如果能加載成功,必然會(huì)導(dǎo)致其他依賴此類的類導(dǎo)致錯(cuò)誤,整個(gè)JVM就會(huì)崩潰。然而這個(gè)類是無(wú)法加載到內(nèi)存中的,因?yàn)轭惖募虞d總是由BootstrapLoader開(kāi)始,當(dāng)他發(fā)現(xiàn)已經(jīng)加載了String,就不會(huì)再加載了,有效地保證了系統(tǒng)的安全性。類的加載過(guò)程基本就這樣,下面貼出一段代碼,自己實(shí)現(xiàn)的類加載器。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
private String path="F:\\JSP\\ClassLoaderDemo\\classes\\";
private String fileType=".class";
@Override
public Class findClass(String name){
byte bytes[]=this.loadClassData(name);
return this.defineClass(name, bytes, 0, bytes.length);
}
//加載類數(shù)據(jù),返回一個(gè)byte數(shù)組
public byte[] loadClassData(String name){
try {
FileInputStream fin=new FileInputStream(path+name+fileType);
ByteArrayOutputStream bout=new ByteArrayOutputStream();
int ch=0;
while((ch=fin.read())!=-1){
bout.write(ch);
}
return bout.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
//類加載器的測(cè)試類
public class TestLoader {
public static void main(String argsp[]){
MyClassLoader loader=new MyClassLoader();
try {
//在指定的目錄中加載HelloWorld.class文件
Class myclass=loader.loadClass("HelloWorld");
//加載完畢后進(jìn)行實(shí)例化,這個(gè)過(guò)程包含了對(duì)類的解析
myclass.newInstance();
System.out.println(myclass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
這段代碼可以直接拷貝運(yùn)行.
我們大篇幅的講述了類的加載過(guò)程,這是jvm運(yùn)行的第一步,建議各位讀到這里的時(shí)候在腦海中回顧一下類的整個(gè)加載過(guò)程,以便于理解下面我們要說(shuō)的類的鏈接。如果你覺(jué)得理解的沒(méi)有問(wèn)題,我們繼續(xù)說(shuō)一下類的連接階段。
當(dāng)我們將類加載到內(nèi)存中的時(shí)候,我們就需要對(duì)他進(jìn)行驗(yàn)證。這里我們先介紹一下JVM是如何進(jìn)行虛擬的。
想必大家都知道我們的CPU是由控制器,運(yùn)算器,存儲(chǔ)器,輸入輸出設(shè)備組成的,這些是我們程序運(yùn)行所必需的硬件環(huán)境。你可以這樣認(rèn)為,我們的JVM為我們的java程序虛擬出了完整的一套運(yùn)行環(huán)境,他的控制器由我們的JVM直接擔(dān)任,像垃圾處理了內(nèi)存分配了等等,運(yùn)算器當(dāng)然就是我們的cpu了,存儲(chǔ)器是jvm的運(yùn)行數(shù)據(jù)區(qū),輸入輸出設(shè)備就是硬件設(shè)備了。這里你可以發(fā)現(xiàn),我們的java是不能直接與硬件進(jìn)行交互的,底層功能的實(shí)現(xiàn)需要通過(guò)本地方法進(jìn)行實(shí)現(xiàn),這就是我們的java跨平臺(tái)的原因。我們的JVM會(huì)根據(jù)硬件環(huán)境的不同(這里主要是指CPU的指令集的不同),將我們的class文件解釋成cpu可以識(shí)別的指令碼,這樣,我們的CPU就能夠運(yùn)行我們的java程序了,這就是java的偉大之處。更確切的說(shuō)使我們JVM的偉大之處了,呵呵。
這里,你只需要大概的了解一下JVM的原理就OK了,之后我們會(huì)細(xì)細(xì)的講解。
我們現(xiàn)在再說(shuō)說(shuō)類的連接階段。
當(dāng)我們把類加載到內(nèi)存之后,我們?nèi)绾伪WC他的正確性呢,或者說(shuō)我們?nèi)绾伪WC加載進(jìn)來(lái)的二進(jìn)制碼是不是符合我們的Class類型的結(jié)構(gòu)呢?關(guān)于結(jié)構(gòu),要細(xì)細(xì)說(shuō)來(lái)需要很大的篇幅,這里你只需要這樣理解他Class就像是類的模板一樣,它包含類的所有信息,包括訪問(wèn)控制符號(hào)(public,private,友好型)、是類還是接口,直接父類是誰(shuí),實(shí)現(xiàn)的接口有什么,以及字段信息,方法信息 ,還有一個(gè)常量池。你看看,這不就是我們類所包含的所有信息嗎。他們按照一定的結(jié)構(gòu)組織在內(nèi)存中,我們把這樣的一塊內(nèi)存結(jié)構(gòu)稱為Class。就是我們常說(shuō)的類類型。
我們接著說(shuō),為了保證我們加載的二進(jìn)制代碼是Class結(jié)構(gòu),所以我們需要進(jìn)行校驗(yàn),很多地方稱為驗(yàn)證,我感覺(jué)稱之為校驗(yàn)更為合適。我們的校驗(yàn)程序校驗(yàn)完畢,發(fā)現(xiàn)它是我們需要的Class結(jié)構(gòu)的時(shí)候,就會(huì)通知JVM為我們Class在方法區(qū)域分配空間。
說(shuō)道這里,我們又要說(shuō)說(shuō)我們JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)了,也就是他的存儲(chǔ)結(jié)構(gòu),下面,我會(huì)用圖示的方法來(lái)解釋它。
我們的JVM將運(yùn)行數(shù)據(jù)區(qū)分為如下幾塊
堆:用來(lái)存儲(chǔ)我們創(chuàng)建的對(duì)象的地方
棧:JVM會(huì)為每個(gè)線程創(chuàng)建一個(gè)棧,用來(lái)存儲(chǔ)局部變量和操作數(shù),棧跟棧之間不能通信。存儲(chǔ)單位是棧幀。我們每調(diào)用一個(gè)方法,就新建一個(gè)棧幀壓入棧中。棧幀之間是屏蔽的,這就是為什么一個(gè)方法無(wú)法訪問(wèn)另一個(gè)方法中的變量。棧幀由局部變量區(qū)(用數(shù)組實(shí)現(xiàn)),操作數(shù)棧(棧結(jié)構(gòu)),幀數(shù)據(jù)(主要用來(lái)支持對(duì)類常量池的解析,方法的正常返回,異常處理)
方法區(qū):用來(lái)保存Class類型數(shù)據(jù)
JVM的內(nèi)存主要結(jié)構(gòu)就這么多了。
好了,我們接著說(shuō),也許你現(xiàn)在對(duì)這張圖還有很多疑問(wèn),稍后你就會(huì)明白了。我們的類現(xiàn)在已經(jīng)通過(guò)驗(yàn)證了,校驗(yàn)器告訴我們它符合我們的Class結(jié)構(gòu),而且在方法區(qū)域?yàn)樗峙淞丝臻g,我們非常高興。下面就是關(guān)乎初始化問(wèn)題了。有人會(huì)問(wèn),不對(duì)還有解析呢。呵呵,在寫(xiě)程序我們也知道了,這個(gè)階段是可選的,也就是說(shuō)你可以讓你的類加載后馬上初始化,也可以加載完畢不進(jìn)行初始化。在這里我們要讓我們的類初始化,下面即使解析階段了。
解析階段的主要任務(wù):將類變量的符號(hào)引用解析成直接地址引用。就拿我們的變量a來(lái)說(shuō),他的值是"hello",這個(gè)東西在Class中只是一個(gè)符號(hào)而已。然而,我們的Class需要將所有的常量都存放在常量池中,所以hello會(huì)被存儲(chǔ)到常量池中,然后提供一個(gè)入口地址給a。a 就能直接引用它了。這里得好好地理解理解。
我們的方法b引用的是一個(gè)方法,這個(gè)方法在Class中只是一個(gè)符號(hào)而已,這個(gè)方法的實(shí)際代碼存放在一張表中,這張表我們成為方法表。我們的b就指向了方法表的一個(gè)引用。
解析完畢之后,就要初始化了,初始化很簡(jiǎn)單,就是執(zhí)行靜態(tài)代碼塊中的內(nèi)容了。整個(gè)加載到此已經(jīng)完畢,想必大家已經(jīng)很清楚了吧。
然而,我們的JVM的任務(wù)才剛剛開(kāi)始。
下面我們說(shuō)一下對(duì)象的創(chuàng)建吧,想必這個(gè)問(wèn)題在很多人看來(lái)都是很不解的。那么我們馬上開(kāi)始吧。
對(duì)象的實(shí)例是什么呢?在內(nèi)存中的樣子是什么呢。
如果你知道了方法區(qū)中的東西,對(duì)于對(duì)象也就不難理解了。對(duì)象就像一種結(jié)構(gòu),其中存儲(chǔ)了指向方法的引用,實(shí)例變量,一個(gè)指向類常量池的引用(這就是為什么實(shí)例可以訪問(wèn)類變量和類方法)。這些數(shù)據(jù)按照一定的結(jié)構(gòu)(就像Class結(jié)構(gòu)一樣,只是簡(jiǎn)單很多)存儲(chǔ)在我們的堆區(qū),這就是我們耳熟能詳?shù)膶?duì)象。當(dāng)我們new的時(shí)候,JVM就會(huì)按照上面的過(guò)程,在堆區(qū)為我們構(gòu)造一個(gè)這樣的數(shù)據(jù)結(jié)構(gòu),然后將塊數(shù)據(jù)的引用存儲(chǔ)到棧里面。稍后我們會(huì)細(xì)細(xì)講解棧的結(jié)構(gòu)
說(shuō)到堆,我們不得不說(shuō)JVM的內(nèi)存管理機(jī)制,或者堆空間的垃圾處理機(jī)制。假如你自己寫(xiě)了一個(gè)JVM,你肯定會(huì)碰到這樣一個(gè)問(wèn)題,我們不斷的在堆里面創(chuàng)建對(duì)象,再大的內(nèi)存也有耗盡的時(shí)候,那我們?nèi)绾芜M(jìn)行垃圾處理呢。以前,在JDK1的時(shí)候采用的是對(duì)整個(gè)堆空間進(jìn)行掃描,查找不再被使用的對(duì)象將其回收,可想而知這種策略是多么的低效。后來(lái),我們聰明的java工程師給我們提供了這樣的存儲(chǔ)結(jié)構(gòu),他們將堆分為了兩大部分,新生區(qū)和永久區(qū)。
總結(jié)
以上是生活随笔為你收集整理的【转】深入理解JVM的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: jsmind
- 下一篇: 高通Sensor驱动学习笔记