Java與C++之間有一堵由內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)去童芹,墻里面的人想出來(lái)涮瞻。
什么是跨平臺(tái)?
我之前一直在想一個(gè)問(wèn)題假褪,一直在說(shuō)Java可以跨平臺(tái)署咽,但是C代碼可以放到 windows 平臺(tái)執(zhí)行也可以放到 linux 平臺(tái)里面執(zhí)行,為什么不說(shuō)C語(yǔ)言跨平臺(tái)呢生音?
跨平臺(tái)定義
跨平臺(tái)定義:首先這是基于源碼的跨平臺(tái)宁否。也就是說(shuō),只寫(xiě)一套代碼缀遍,但是在各個(gè)平臺(tái)如 Windows慕匠,Linux,unix 都能順利運(yùn)行域醇,這就是跨平臺(tái)台谊。
我們知道 Java 是運(yùn)行在虛擬機(jī)里面的蓉媳,不管你的服務(wù)器是windows系統(tǒng)還是linux系統(tǒng),只要在各個(gè)平臺(tái)上面安裝 java虛擬機(jī)锅铅,那么就可以愉快的運(yùn)行Java代碼酪呻,所以說(shuō)Java是平臺(tái)無(wú)關(guān)的語(yǔ)言即可跨平臺(tái)。
然而 C&C++ 語(yǔ)言盐须,他們是平臺(tái)有關(guān)的語(yǔ)言玩荠,我們?cè)?Windows 系統(tǒng)下面編寫(xiě)好了代碼,運(yùn)行的很快樂(lè)贼邓,但是拿到 Linux 系統(tǒng)運(yùn)行卻不一定能成功可能報(bào)錯(cuò)阶冈。例如一段打開(kāi)文件的代碼實(shí)現(xiàn),我隨便百度一下兩個(gè)平臺(tái)的代碼實(shí)現(xiàn)如下圖立帖,分為 windows 實(shí)現(xiàn)和 linux 實(shí)現(xiàn),也就是說(shuō)C語(yǔ)言平臺(tái)有關(guān)悠砚,不能跨平臺(tái)運(yùn)行晓勇。
C語(yǔ)言代碼能不能跨平臺(tái)運(yùn)行呢?
當(dāng)然可以了灌旧,有兩種方法可以讓C程序代碼實(shí)現(xiàn)跨平臺(tái)運(yùn)行绑咱,
寫(xiě)好兼容代碼。
只要你在代碼里面寫(xiě)好兼容代碼枢泰,同時(shí)寫(xiě)一套 windows 的實(shí)現(xiàn)描融,再寫(xiě)一套 linux 的實(shí)現(xiàn),那么這套代碼就可以同時(shí)在 windows 和 linux 系統(tǒng)下執(zhí)行衡蚂。假如一個(gè)打開(kāi)文件的操作在windows和linux里面的實(shí)現(xiàn)不一樣窿克,偽代碼如下:
if (當(dāng)前系統(tǒng) == windows){
? open1 file ;
} else if (當(dāng)前系統(tǒng) == linux) {
? open2 file ;
} else {
? open3 file ;
}
用了洪荒之力寫(xiě)完這篇,有收獲的朋友點(diǎn)個(gè)在看或者分享鼓勵(lì)一下吧毛甲,十分感謝~
公眾號(hào)下篇內(nèi)容預(yù)告:對(duì)象的創(chuàng)建年叮,內(nèi)存布局以及訪(fǎng)問(wèn)定位
這樣看,整個(gè)類(lèi)信息主要是拆開(kāi)來(lái)玻募,為了方便管理只损,存儲(chǔ)和調(diào)度從而劃分成了這幾個(gè)區(qū)域。
本地方法棧由于是調(diào)用的c代碼七咧,是通過(guò)動(dòng)態(tài)鏈接的方式而不是傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)中的棧結(jié)構(gòu)跃惫,所以也抽出來(lái)進(jìn)行特殊處理,劃分一個(gè)本地方法棧艾栋。
程序計(jì)數(shù)器方便調(diào)度字節(jié)碼指令爆存,讓程序動(dòng)起來(lái),即代碼按照代碼順序執(zhí)行蝗砾;
堆區(qū)存放對(duì)象终蒂,對(duì)象變化比較大蜂林,涉及到垃圾回收因此也單獨(dú)劃分區(qū)域來(lái)存儲(chǔ),便于管理和回收拇泣;
棧是存放活的動(dòng)起來(lái)的東西噪叙,專(zhuān)門(mén)對(duì)類(lèi)函數(shù)方法來(lái)設(shè)置的,入棧出棧也不用垃圾回收內(nèi)存什么的(試想一下霉翔,類(lèi)里面的方法很多睁蕾,而且是方法進(jìn)入方法返回,都有進(jìn)有出债朵,在數(shù)據(jù)結(jié)構(gòu)里面只有棧這種結(jié)構(gòu)能滿(mǎn)足設(shè)計(jì)子眶,棧幀當(dāng)然就是根據(jù)方法里面的局部變量什么的來(lái)設(shè)計(jì)的);
方法區(qū)其實(shí)就是存放一些“死的東西”序芦,不會(huì)動(dòng)的東西臭杰,例如類(lèi)的一些死的信息(類(lèi)名,常量等)谚中;
從設(shè)計(jì)者角度根據(jù)類(lèi)的內(nèi)容來(lái)劃分JVM內(nèi)存:
JVM堆渴杆,棧,方法區(qū)對(duì)應(yīng)結(jié)構(gòu)
這部分內(nèi)容是具備動(dòng)態(tài)性的宪塔,運(yùn)行期間可以放入新的常量磁奖,例如String類(lèi)的intern()方法,以及new String(“123”)的時(shí)候某筐,String類(lèi)型先會(huì)先去常量池看123存在不比搭,存在的話(huà)直接在堆區(qū)生成對(duì)象并且引用他,如果不存在會(huì)先去常量池創(chuàng)建一個(gè)“123”再去堆引用指向他南誊。
運(yùn)行時(shí)常量池是方法區(qū)的一部分身诺,class文件除了類(lèi)的版本,字段抄囚,方法戚长,接口等描述信息之外,還有一項(xiàng)信息是常量池怠苔,用于存放編譯期生成的各種字面量和符號(hào)引用同廉,這些內(nèi)容將在類(lèi)加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
運(yùn)行時(shí)常量池
這個(gè)說(shuō)法是建立在HotPot虛擬機(jī)的柑司,其設(shè)計(jì)團(tuán)隊(duì)把GC分代收集擴(kuò)展到了方法區(qū)迫肖,也即是用永久代來(lái)實(shí)現(xiàn)方法區(qū)以至于垃圾收集器可以像管理堆一樣管理方法區(qū)內(nèi)存,對(duì)其他的虛擬機(jī)來(lái)說(shuō)是不存在永久代的概念的攒驰。
很多人都把方法區(qū)稱(chēng)為永久代蟆湖,本質(zhì)上兩者不等價(jià)。
傳說(shuō)中的永久代
已被虛擬機(jī)加載的類(lèi)信息
常量
靜態(tài)變量
即時(shí)編譯器(JIT)編譯后的代碼
用于存儲(chǔ):
由圖看出此部分主要有靜態(tài)的常量(類(lèi)信息不會(huì)變)玻粪,和運(yùn)行時(shí)常量池隅津。
方法區(qū)
StackOverFlowError:棧溢出诬垂,線(xiàn)程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度(代碼實(shí)現(xiàn)可以寫(xiě)一個(gè)遞歸方法,然后不給遞歸出口伦仍,調(diào)用遞歸方法每次遞歸都會(huì)產(chǎn)生新的棧幀直到把棧區(qū)打滿(mǎn)溢出)
OutOfMemoryError:虛擬機(jī)棧擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存(代碼實(shí)現(xiàn)可以一直循環(huán)new對(duì)象结窘,直到把堆區(qū)打滿(mǎn)內(nèi)存溢出)
異常:
Navtive 方法是 Java 通過(guò) JNI 直接調(diào)用本地 C/C++ 庫(kù),可以認(rèn)為是 Native 方法相當(dāng)于 C/C++ 暴露給 Java 的一個(gè)接口充蓝,Java 通過(guò)調(diào)用這個(gè)接口從而調(diào)用到 C/C++ 方法隧枫。當(dāng)線(xiàn)程調(diào)用 Java 方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)棧幀并壓入 Java 虛擬機(jī)棧谓苟。然而當(dāng)它調(diào)用的是 native 方法時(shí)官脓,虛擬機(jī)會(huì)保持 Java 虛擬機(jī)棧不變,也不會(huì)向 Java 虛擬機(jī)棧中壓入新的棧幀涝焙,虛擬機(jī)只是簡(jiǎn)單地動(dòng)態(tài)連接并直接調(diào)用指定的 native 方法卑笨。
這里簡(jiǎn)單提一下,他與Java虛擬機(jī)棧所發(fā)揮的作用是非常相似的仑撞,其區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù)赤兴,而本地方法棧則是為虛擬機(jī)使用到的 Native 方法服務(wù)。
本地方法棧
局部變量表的內(nèi)存空間在編譯期間就完成了分配派草,進(jìn)入一個(gè)方法的時(shí)候搀缠,這個(gè)方法需要在幀里面分配多少局部變量空間是確定的铛楣,不會(huì)改變近迁。
存放了編譯期可知的各種基本數(shù)據(jù)類(lèi)型(boolean,byte纯露,char媳荒,short跳夭,int...)
存放對(duì)象引用(注意不是對(duì)象本身,是引用搏存,即指針)
存放字節(jié)碼指令地址 returnAddress 類(lèi)型(即方法返回地址,方法出口)
說(shuō)白了就是:
局部變量表
操作數(shù)棧
動(dòng)態(tài)鏈接
棧幀包括的內(nèi)容:
每一個(gè)方法從調(diào)用開(kāi)始到完成的過(guò)程矢洲,就是一個(gè)棧幀在在虛擬機(jī)中入棧到出棧的過(guò)程
棧區(qū)
Java堆是被所有線(xiàn)程共享的一塊內(nèi)存區(qū)域璧眠,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建;
此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例读虏,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存责静;
OutOfMemoryError異常。如果在堆中沒(méi)有內(nèi)存完成實(shí)例分配盖桥,并且堆也無(wú)法再擴(kuò)展時(shí)會(huì)拋出此異常灾螃。
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),Java堆(Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊揩徊,主要記住三點(diǎn):
Java堆是垃圾收集器管理的主要區(qū)域腰鬼,因此很多時(shí)候也被稱(chēng)作“GC堆”嵌赠,幸好國(guó)內(nèi)沒(méi)翻譯成“垃圾堆”。
java堆(Heap)
注:程序計(jì)數(shù)器是唯一一個(gè)在JVM規(guī)范中沒(méi)有規(guī)定任何 OutOfMemoryError 的區(qū)域熄赡。
ps:這個(gè)地方我解釋這么清楚是因?yàn)槠渌拇蠹叶勢(shì)^多姜挺,很容易理解,但是這個(gè)區(qū)域至少我大學(xué)的時(shí)候這個(gè)地方就不是很理解本谜。
我們都知道在計(jì)算機(jī)里面CPU是從指令寄存器拿到執(zhí)行指令進(jìn)行工作初家,當(dāng)指令寄存器里面一條指令被CPU拿走執(zhí)行,那么寄存器就會(huì)把程序計(jì)數(shù)器里面指定的下一個(gè)需要執(zhí)行的字節(jié)碼指令對(duì)應(yīng)的CPU指令拿進(jìn)來(lái)乌助,讓CPU進(jìn)行執(zhí)行溜在,所以實(shí)現(xiàn)字節(jié)碼指令都可以做到有序執(zhí)行,需要注意的是程序計(jì)數(shù)器存放的都是下一個(gè)字節(jié)碼指令的地址他托,這樣才可以一直往下執(zhí)行嘛掖肋。
放上一篇文章的圖,這是 jvm 的一些指令赏参,最終會(huì)和計(jì)算機(jī)的相關(guān)指令相對(duì)應(yīng)志笼。
程序計(jì)數(shù)器存放的是下一條字節(jié)碼指令執(zhí)行的地址,存放地址的地方把篓,因此只需要一塊較小的內(nèi)存空間纫溃,它的作用是當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼行號(hào)指示器。
程序計(jì)數(shù)器
線(xiàn)程私有:Java虛擬機(jī)的多線(xiàn)程實(shí)現(xiàn)韧掩,是通過(guò)線(xiàn)程輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的紊浩,再任何一個(gè)確定的時(shí)刻,一個(gè)處理器都只會(huì)執(zhí)行一條線(xiàn)程中的指令疗锐。因此為了線(xiàn)程切換之后能恢復(fù)到正確的的執(zhí)行位置坊谁,每條線(xiàn)程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線(xiàn)程之間計(jì)數(shù)器不影響?yīng)毩⒋鎯?chǔ)滑臊,我們稱(chēng)這類(lèi)內(nèi)存區(qū)域?yàn)椤熬€(xiàn)程私有”內(nèi)存口芍。
方法區(qū)和堆區(qū)是所有線(xiàn)程共享的,棧和程序計(jì)數(shù)器都是線(xiàn)程私有各自管各自的雇卷。
運(yùn)行時(shí)數(shù)據(jù)區(qū)總覽
運(yùn)行之前的解析讀者可以直接看我前面寫(xiě)的幾篇鬓椭,本文主要想說(shuō)的是運(yùn)行時(shí)數(shù)據(jù)區(qū)。
Java在 JVM 中的運(yùn)行生命周期和類(lèi)加載詳細(xì)過(guò)程路徑直達(dá)java類(lèi)在JVM中的生命周期
java 編譯的字節(jié)碼解析路徑直達(dá)從JVM設(shè)計(jì)者的角度來(lái)看.class文件結(jié)構(gòu)关划,一文弄懂.class文件的身份地位
可以看到Java源代碼先是經(jīng)過(guò)編譯器進(jìn)行編譯小染,變成.class文件,由類(lèi)加載器加載進(jìn)內(nèi)存運(yùn)行祭玉。
JVM內(nèi)存結(jié)構(gòu)
這樣看來(lái)JVM內(nèi)存結(jié)構(gòu)是很重要把跤场!
然而C/C++是直接使用物理硬件和操作系統(tǒng)的內(nèi)存模型脱货,因此會(huì)由于不同平臺(tái)的內(nèi)存模型不同而產(chǎn)生差異岛都。
Java虛擬機(jī)定義了一種Java內(nèi)存模型(Java memory model, JMM)來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪(fǎng)問(wèn)差異律姨,簡(jiǎn)單理解也就是說(shuō)Java虛擬機(jī)相當(dāng)于是在源碼和平臺(tái)之間抽象了一層出來(lái),專(zhuān)門(mén)處理一些平臺(tái)之間訪(fǎng)問(wèn)的兼容問(wèn)題臼疫,使得源碼可以一次編譯到處運(yùn)行择份。
Java跨平臺(tái)的原因
可以看到編譯器是關(guān)鍵,再拿C語(yǔ)言為例烫堤,Linux下直接使用 gcc編譯器 編譯C程序荣赶,在Windows下使用對(duì)應(yīng)的 mingw 編譯C程序,這樣用兩套不同的編譯器來(lái)在不同的平臺(tái)進(jìn)行編譯鸽斟,不同的編譯器都是封裝了各自平臺(tái)對(duì)C語(yǔ)言的處理拔创,但是這樣也很麻煩啊,所以Java虛擬機(jī)的價(jià)值就更加突顯了富蓄。
它的執(zhí)行過(guò)程是:預(yù)處理->編譯->匯編->鏈接->機(jī)器碼
因?yàn)橹С諧++語(yǔ)言的各個(gè)平臺(tái)的架構(gòu)不同(比如CPU能夠處理的指令集不一樣)剩燥,所以一份C++源代碼要想在另一個(gè)操作系統(tǒng)平臺(tái)上執(zhí)行,就必須用該平臺(tái)相對(duì)應(yīng)的C++代碼編譯器對(duì)C++源代碼重新進(jìn)行編譯立倍,生成該平臺(tái)可以直接執(zhí)行的機(jī)器代碼灭红。
移植編譯器
可以想象,如果你是要實(shí)現(xiàn)一個(gè)大的工程有很多代碼口注,你得寫(xiě)多少兼容代碼变擒,而且測(cè)試的時(shí)候還需要同時(shí)放到兩個(gè)平臺(tái)去測(cè)試,這是多么的夸張寝志,使得程序員原本就不茂密的頭發(fā)更加雪上添霜娇斑。