计算机 java_Java程序到底是如何运行的?
JVM內(nèi)存結(jié)構(gòu)
可以看出JVM從宏觀上可以分為 ‘內(nèi)部’及 ‘外部’ 兩個(gè)部分(便于記憶理解):
‘內(nèi)部’包含:線程共享(公有)數(shù)據(jù)區(qū) 和 線程隔離(私有)數(shù)據(jù)區(qū)
‘外部’包含:類加載子系統(tǒng)、垃圾回收器、執(zhí)行引擎、本地庫接口、本地方法庫
以上部件構(gòu)成了整個(gè)jvm,接下來我們一個(gè)一個(gè)零件拆開了看。
class文件
一個(gè)java文件會(huì)通過編譯工具(javac)編譯成class字節(jié)碼文件,通過jvm進(jìn)行加載運(yùn)行。因?yàn)閖vm屏蔽了底層操作系統(tǒng)的差異(平臺(tái)無關(guān)性),所以一次編譯到處運(yùn)行。
類加載子系統(tǒng)
類加載子系統(tǒng):負(fù)責(zé)查找和裝載class文件,將其中的二進(jìn)制數(shù)據(jù)加載到j(luò)vm中。
字節(jié)碼 --> 加載 --> 驗(yàn)證 --> 準(zhǔn)備 --> 解析 --> 初始化
加載:通過類的完全限定名找到類文件所在位置,根據(jù)其中的字節(jié)碼創(chuàng)建java.lang.Class對(duì)象,所以才會(huì)說萬物皆對(duì)象,我們也可以繼承ClassLoader,重寫findClass方法來自定義實(shí)現(xiàn)類加載器。默認(rèn)情況下我們都使用AppClassLoader
驗(yàn)證:確保加載的字節(jié)碼的是否符合虛擬機(jī)的要求,是java提供的一種自我保護(hù)機(jī)制,不讓其危害虛擬機(jī)安全。其主要包括四種驗(yàn)證,字節(jié)碼驗(yàn)證、文件格式驗(yàn)證,元數(shù)據(jù)驗(yàn)證、符號(hào)引用驗(yàn)證。
準(zhǔn)備:為類變量分配地址和初始化值,類變量會(huì)分配到方法區(qū)(元空間)中,這里的初始化是指該數(shù)據(jù)類型的默認(rèn)初始值,例如int對(duì)應(yīng)的是0,long對(duì)應(yīng)的0L,只有在初始化時(shí)才會(huì)動(dòng)顯示賦值
解析:把類中的二進(jìn)制數(shù)據(jù)中的符號(hào)引用轉(zhuǎn)換為直接引用;例如我們通過user.getInfo();
這里的.getInfo()就是符號(hào)引用,在解析階段會(huì)將它指向真正的內(nèi)存位置,這就是直接引用
初始化:主要為類的靜態(tài)變量賦予正確的值,比如int num = 10; 這里num的值會(huì)從準(zhǔn)備階段的0變?yōu)?0;并且若該類有父類,會(huì)對(duì)其進(jìn)行初始化操作;如果類中有初始化語句,系統(tǒng)會(huì)按照順序進(jìn)行初始化
雙親委派模式
雙親委派:自底向上檢查是否加載成功,自頂向下嘗試加載。
當(dāng)一個(gè)類加載器收到類加載請(qǐng)求,它不會(huì)自己進(jìn)行加載,而是將該請(qǐng)求丟給父類加載,如果父類還存在父類,則會(huì)依次向上請(qǐng)求,直到到達(dá)頂級(jí)加載器,如果父類加載器能加載完成就返回加載成功,否則子類加載器才會(huì)自己嘗試加載。
通過代碼驗(yàn)證,可以很輕松的了解 AppClassLoader -> ExtClassLoader -> BootstrapClassLoader 這三層的關(guān)系。
類加載的三種方式
1. new關(guān)鍵字加載
靜態(tài)加載,在運(yùn)行時(shí)候通過new關(guān)鍵字創(chuàng)建類實(shí)例
2. Class.forName()加載
動(dòng)態(tài)加載,通過Class.forName()來加載類,然后調(diào)用類的newInstance()方法實(shí)例化對(duì)象
3. ClassLoader 實(shí)例的 loadClass() 方法
動(dòng)態(tài)加載,可通過繼承ClassLoader實(shí)現(xiàn)自定義類加載器
線程私有和線程公有
JVM內(nèi)存區(qū)從宏觀上可以分為 線程私有和 線程公有 兩塊。
線程私有部分
這部分沒有線程安全問題,隨著線程執(zhí)行結(jié)束而結(jié)束;包含程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧三個(gè)部件。
程序計(jì)數(shù)器
程序計(jì)數(shù)器也叫PC寄存器,作用是cpu進(jìn)行切換的時(shí)候,指向當(dāng)前時(shí)刻需要獲取指令的位置。
特點(diǎn):
線程私有一塊較小的區(qū)域記錄程序執(zhí)行的位置不存在內(nèi)存溢出OutOfMemoryError虛擬機(jī)棧
棧數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),入口和出口只有一個(gè),稱之為入棧和出棧,先進(jìn)后出(FILO)
棧的作用主要是執(zhí)行方法,先執(zhí)行的方法在最下面,然后依次放入,方法執(zhí)行完畢之后從上往下依次退出;所以方法執(zhí)行就是壓棧,方法結(jié)束就是出棧(銷毀棧幀)。
虛擬機(jī)棧如何執(zhí)行
棧幀
棧幀存在Java虛擬機(jī)棧中,是虛擬機(jī)棧中的單位元素。方法執(zhí)行會(huì)創(chuàng)建棧幀,一個(gè)方法就是一個(gè)棧幀,一個(gè)棧幀分為四個(gè)部分:
1. 局部變量表
存放方法參數(shù)或者內(nèi)部定義的一組變量列表;例如方法中聲明的對(duì)象:
2. 操作數(shù)棧
執(zhí)行字節(jié)碼指令的時(shí)候使用,通俗的講就是方法的執(zhí)行在操作數(shù)棧中進(jìn)行,通過壓棧和出棧進(jìn)行訪問
3. 動(dòng)態(tài)鏈接
Java運(yùn)行期間是動(dòng)態(tài)鏈接的,需要將指向方法的符號(hào)引用轉(zhuǎn)換為直接引用(內(nèi)存地址);在類加載解析階段,將符號(hào)引用轉(zhuǎn)換為直接引用稱之為靜態(tài)解析。而此處正好就是動(dòng)態(tài)鏈接user.getInfo(); //找到這個(gè)getInfo()方法的內(nèi)存位置
4. 返回地址
方法不管正常執(zhí)行結(jié)束還是異常退出,需要返回方法被調(diào)用的位置
以上四個(gè)部分對(duì)應(yīng)方法執(zhí)行的過程。虛擬里面包含很多個(gè)棧幀,每個(gè)方法對(duì)應(yīng)一個(gè)棧幀。
將一個(gè)class文件,通過bin/javap.exe文件進(jìn)行反匯編可以查看出以上四個(gè)部分。
棧溢出:當(dāng)棧的深度大于虛擬機(jī)允許會(huì)報(bào)StackOverflowError,-Xss可設(shè)置大小
內(nèi)存溢出:當(dāng)棧需要擴(kuò)展而無法申請(qǐng)空間會(huì)報(bào)OutOfMemoryError
本地方法棧
本地方法棧和虛擬機(jī)棧類似,區(qū)別在于虛擬機(jī)棧主要為jvm執(zhí)行字節(jié)碼服務(wù),而本地方法棧為Native方法服務(wù),即本地方法服務(wù);所以本地方法棧也是一塊內(nèi)存私有區(qū)域,與虛擬機(jī)棧相同也有同樣的異常問題。
特點(diǎn):
與虛擬機(jī)?;绢愃茀^(qū)域在于本地方法棧為Native方法服務(wù)(windows下調(diào)用dll文件)Sun HotSpot將虛擬機(jī)棧和本地方法棧合并有StackOverflowError和OutOfMemoryError線程公有部分
這部分存在線程安全問題,平常我們所指的內(nèi)存優(yōu)化,溢出等問題都是需要關(guān)注這個(gè)區(qū)域。包含堆、方法區(qū)(也叫元空間)兩個(gè)部件。
方法區(qū)(元空間)
類加載器加載類的時(shí)候,會(huì)將一些類的元數(shù)據(jù)信息(字節(jié)碼)保存在這個(gè)區(qū)域,例如:類變量,靜態(tài)方法,普通方法等,方法區(qū)是線程共享的,多個(gè)線程能用到同一個(gè)類
jdk1.7合并方法區(qū)到了堆里面
jdk1.8保留了方法區(qū)的概念,只不過實(shí)現(xiàn)方式不同,jdk1.8稱為元空間,與堆不相連,但是與堆共享物理內(nèi)存,邏輯上可以認(rèn)為是在堆中
特點(diǎn):
線程共享存儲(chǔ)類信息、常量、靜態(tài)變量、方法描述等信息HotSpot虛擬機(jī)中稱之為永久代GC很少回收這個(gè)區(qū)域存在OutOfMemoryError,可以通過-XX:MaxPermSize設(shè)置大小堆
堆中用于存放所有實(shí)例化對(duì)象和數(shù)組,堆中信息線程共享,所有jvm部件中分配內(nèi)存中最大的區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)就創(chuàng)建,垃圾回收器主要管理該區(qū)域,堆分為新生代(占堆內(nèi)存1/3)和老年代(占堆內(nèi)存2/3),新生代更細(xì)致可以分為Eden、From Survivor、To Survivor空間,比例8:1:1 ;可以通過-Xmx、-Xms設(shè)置大小在堆中產(chǎn)生了一個(gè)實(shí)例對(duì)象或數(shù)組,可以在棧中聲明一個(gè)變量,用于指向堆中的對(duì)象,該變量的取值等于堆中對(duì)象的內(nèi)存地址,所以我們?cè)诖蛴∽兞棵臅r(shí)候是一串內(nèi)存地址
萬物皆對(duì)象,當(dāng)我們?cè)趯?shí)際開發(fā)中,創(chuàng)建了許多對(duì)象,為了防止內(nèi)存泄露,java確保有效的使用內(nèi)存,會(huì)由java虛擬機(jī)自動(dòng)垃圾回收器來管理;且把堆分為新生代和老年代進(jìn)行管理
新生代與老年代
新生代是Java對(duì)象出生的地方,是新對(duì)象分配內(nèi)存的地方,大部分對(duì)象存活時(shí)間都不需要太久,這個(gè)區(qū)域會(huì)頻繁觸發(fā)MinorGC進(jìn)行垃圾回收;
而老年代存放的都是存活時(shí)間較久或者內(nèi)存較大的對(duì)象,所以Full GC不會(huì)頻繁執(zhí)行。
Minor GC
發(fā)生在新生代中的垃圾回收機(jī)制,采用復(fù)制算法(掃描存活對(duì)象,復(fù)制到一塊新內(nèi)存空間中),From Survivor 和 to Survivor是相對(duì)的,也就是說Minor GC發(fā)生時(shí),Eden區(qū)和其中一個(gè)Survivor區(qū)會(huì)把一些仍然存活的對(duì)象放置另外一個(gè)Survivor 區(qū),然后清理Eden區(qū)和之前的Survivor 區(qū),下次同理,當(dāng)達(dá)到一定 ‘年齡’ 后,新生代會(huì)把對(duì)象放入老年代(每發(fā)生一次Minor GC增加1歲,默認(rèn)15歲)
Full GC
發(fā)生在老年代中的垃圾回收機(jī)制,采用標(biāo)記-清除(標(biāo)記存活的對(duì)象,清除未標(biāo)記的對(duì)象,即需要回收的對(duì)象),因?yàn)槔夏甏械膶?duì)象較穩(wěn)定,所以發(fā)生Full GC的頻率相對(duì)Minor GC較少,但是一次回收的時(shí)間會(huì)比Minor GC更長
總結(jié)
以上是生活随笔為你收集整理的计算机 java_Java程序到底是如何运行的?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 减法_java 加减法2
- 下一篇: win10怎么调文本大小 Win10如何