Java、Python與PHP的虛擬機(jī)異同
Java-JVM
定義
- JDK(Java Development Kit) 是 Java 語言的軟件開發(fā)工具包(SDK)浑侥。JDK 物理存在姊舵,是 programming tools、JRE 和 JVM 的一個集合
- JRE(Java Runtime Environment)Java 運(yùn)行時環(huán)境寓落,JRE 物理存在括丁,主要由Java API 和 JVM 組成,提供了用于執(zhí)行 java 應(yīng)用程序最低要求的環(huán)境伶选。
- JVM(Java Virtual Machine) 是一種軟件實(shí)現(xiàn)史飞,執(zhí)行像物理機(jī)程序的機(jī)器(即電腦)尖昏。JVM 通過執(zhí)行 Java bytecode 可以使 java 代碼在不改變的情況下運(yùn)行在各種硬件之上。JVM是基于棧的构资。
JVM 執(zhí)行
- 加載代碼
- 驗(yàn)證代碼
- 執(zhí)行代碼
- 提供運(yùn)行環(huán)境
JVM 生命周期
- 啟動:任何一個擁有main函數(shù)的class都可以作為JVM實(shí)例運(yùn)行的起點(diǎn)
- 運(yùn)行:main函數(shù)為起點(diǎn)会宪,程序中的其他線程均有它啟動,包括daemon守護(hù)線程和non-daemon普通線程蚯窥。daemon是JVM自己使用的線程比如GC線程掸鹅,main方法的初始線程是non-daemon。
- 消亡:所有線程終止時拦赠,JVM實(shí)例結(jié)束生命巍沙。
JVM結(jié)構(gòu)及內(nèi)存模型
名詞解釋:
- Class Loader:類加載器負(fù)責(zé)加載程序中的類型(類和接口),并賦予唯一的名字荷鼠。為什么使用雙親委托模型——ClassLoader 隔離問題句携。
- Execution Engine:執(zhí)行引擎。執(zhí)行引擎以指令為單位讀取 Java 字節(jié)碼允乐。它就像一個 CPU 一樣矮嫉,一條一條地執(zhí)行機(jī)器指令。
- Runtime Data Areas::運(yùn)行時數(shù)據(jù)區(qū)牍疏。
- PS:想起面試的時候被問到過這樣的問題:你在使用java過程中是否遇到過OOM的情況蠢笋?當(dāng)時一陣懵比。現(xiàn)在總結(jié)下:
- PC寄存器(PC Register)是唯一一個在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域鳞陨。
- JVM 棧(Java Virtual Machine Stack):如果JVM Stack可以動態(tài)擴(kuò)展昨寞,但是在嘗試擴(kuò)展時無法申請到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的虛擬機(jī)棧時拋出厦滤。
- 本地方法棧(Native method stack):如果本地方法椩遥可以動態(tài)擴(kuò)展,并且擴(kuò)展的動作已經(jīng)嘗試過掏导,但是目前無法申請到足夠的內(nèi)存去完成擴(kuò)展享怀,或者在建立新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的本地方法棧,那Java虛擬機(jī)將會拋出一個OutOfMemoryError異常趟咆。
- 方法區(qū)(Method area):如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配請求添瓷,那Java虛擬機(jī)將拋出一個OutOfMemoryError異常。
- 運(yùn)行時常量池(Runtime constant pool):當(dāng)創(chuàng)建類和接口時忍啸,如果構(gòu)造運(yùn)行時常量池所需的內(nèi)存空間超過了方法區(qū)所能提供的最大內(nèi)存空間后就會拋出OutOfMemoryError
- 堆(Heap):如果實(shí)際所需的堆超過了自動內(nèi)存管理系統(tǒng)能提供的最大容量時拋出仰坦。
- 總結(jié)一下就是無法申請到足夠的內(nèi)存以及超出最大容量兩方面原因
垃圾回收
-
新生代
新生代由 Eden 與 Survivor Space(S0,S1)構(gòu)成计雌,大小通過-Xmn參數(shù)指定悄晃,Eden 與 Survivor Space 的內(nèi)存大小比例默認(rèn)為8:1,可以通過-XX:SurvivorRatio 參數(shù)指定,比如新生代為10M 時妈橄,Eden分配8M庶近,S0和S1各分配1M。
Eden:希臘語眷蚓,意思為伊甸園鼻种,在圣經(jīng)中,伊甸園含有樂園的意思沙热,根據(jù)《舊約·創(chuàng)世紀(jì)》記載叉钥,上帝耶和華照自己的形像造了第一個男人亞當(dāng),再用亞當(dāng)?shù)囊粋€肋骨創(chuàng)造了一個女人夏娃篙贸,并安置他們住在了伊甸園投队。
大多數(shù)情況下,對象在Eden中分配爵川,當(dāng)Eden沒有足夠空間時敷鸦,會觸發(fā)一次Minor GC,虛擬機(jī)提供了-XX:+PrintGCDetails參數(shù)寝贡,告訴虛擬機(jī)在發(fā)生垃圾回收時打印內(nèi)存回收日志扒披。
Survivor:意思為幸存者,是新生代和老年代的緩沖區(qū)域圃泡。
當(dāng)新生代發(fā)生GC(Minor GC)時碟案,會將存活的對象移動到S0內(nèi)存區(qū)域,并清空Eden區(qū)域洞焙,當(dāng)再次發(fā)生Minor GC時蟆淀,將Eden和S0中存活的對象移動到S1內(nèi)存區(qū)域。存活對象會反復(fù)在S0和S1之間移動澡匪,當(dāng)對象從Eden移動到Survivor或者在Survivor之間移動時,對象的GC年齡自動累加褒链,當(dāng)GC年齡超過默認(rèn)閾值15時唁情,會將該對象移動到老年代,可以通過參數(shù)-XX:MaxTenuringThreshold 對GC年齡的閾值進(jìn)行設(shè)置甫匹。
-
老年代
老年代的空間大小即-Xmx 與-Xmn 兩個參數(shù)之差甸鸟,用于存放經(jīng)過幾次Minor GC之后依舊存活的對象。當(dāng)老年代的空間不足時兵迅,會觸發(fā)Major GC/Full GC抢韭,速度一般比Minor GC慢10倍以上。
-
永久代
在JDK8之前的HotSpot實(shí)現(xiàn)中恍箭,類的元數(shù)據(jù)如方法數(shù)據(jù)刻恭、方法信息(字節(jié)碼,棧和變量大小)鳍贾、運(yùn)行時常量池鞍匾、已確定的符號引用和虛方法表等被保存在永久代中,32位默認(rèn)永久代的大小為64M骑科,64位默認(rèn)為85M橡淑,可以通過參數(shù)-XX:MaxPermSize進(jìn)行設(shè)置,一旦類的元數(shù)據(jù)超過了永久代大小咆爽,就會拋出OOM異常梁棠。
虛擬機(jī)團(tuán)隊在JDK8的HotSpot中,把永久代從Java堆中移除了斗埂,并把類的元數(shù)據(jù)直接保存在本地內(nèi)存區(qū)域(堆外內(nèi)存)掰茶,稱之為元空間。
這樣做有什么好處蜜笤?
有經(jīng)驗(yàn)的同學(xué)會發(fā)現(xiàn)濒蒋,對永久代的調(diào)優(yōu)過程非常困難,永久代的大小很難確定把兔,其中涉及到太多因素沪伙,如類的總數(shù)、常量池大小和方法數(shù)量等县好,而且永久代的數(shù)據(jù)可能會隨著每一次Full GC而發(fā)生移動围橡。而在JDK8中,類的元數(shù)據(jù)保存在本地內(nèi)存中缕贡,元空間的最大可分配空間就是系統(tǒng)可用內(nèi)存空間翁授,可以避免永久代的內(nèi)存溢出問題,不過需要監(jiān)控內(nèi)存的消耗情況晾咪,一旦發(fā)生內(nèi)存泄漏收擦,會占用大量的本地內(nèi)存。
判斷垃圾回收
引用計數(shù)法:在對象上添加一個引用計數(shù)器谍倦,每當(dāng)有一個對象引用它時塞赂,計數(shù)器加1,當(dāng)使用完該對象時昼蛀,計數(shù)器減1宴猾,計數(shù)器值為0的對象表示不可能再被使用。引用計數(shù)法實(shí)現(xiàn)簡單叼旋,判定高效仇哆,但不能解決對象之間相互引用的問題。
-
可達(dá)性分析法:通過一系列稱為 “GC Roots” 的對象作為起點(diǎn)夫植,從這些節(jié)點(diǎn)開始向下搜索讹剔,搜索路徑稱為 “引用鏈”,以下對象可作為GC Roots:
- 本地變量表中引用的對象
- 方法區(qū)中靜態(tài)變量引用的對象
- 方法區(qū)中常量引用的對象
- Native方法引用的對象
當(dāng)一個對象到 GC Roots 沒有任何引用鏈時,意味著該對象可以被回收辟拷。
垃圾回收算法
- 標(biāo)記-清除算法
對待回收的對象進(jìn)行標(biāo)記撞羽。
算法缺點(diǎn):效率問題,標(biāo)記和清除過程效率都很低衫冻;空間問題诀紊,收集之后會產(chǎn)生大量的內(nèi)存碎片北秽,不利于大對象的分配叨橱。 - 復(fù)制算法
復(fù)制算法將可用內(nèi)存劃分成大小相等的兩塊A和B,每次只使用其中一塊辜妓,當(dāng)A的內(nèi)存用完了为居,就把存活的對象復(fù)制到B碌宴,并清空A的內(nèi)存,不僅提高了標(biāo)記的效率蒙畴,因?yàn)橹恍枰獦?biāo)記存活的對象贰镣,同時也避免了內(nèi)存碎片的問題,代價是可用內(nèi)存縮小為原來的一半膳凝。 - 標(biāo)記-整理算法
在老年代中碑隆,對象存活率較高,復(fù)制算法的效率很低蹬音。在標(biāo)記-整理算法中上煤,標(biāo)記出所有存活的對象,并移動到一端著淆,然后直接清理邊界以外的內(nèi)存劫狠。
參考文章
Python
PVM
PVM是Python的運(yùn)行引擎。他通常表現(xiàn)為python系統(tǒng)的一部分永部。并且他是實(shí)際運(yùn)行腳本的組件独泞。
編譯器:將源碼編譯成運(yùn)行在虛擬機(jī)上執(zhí)行的opcode(pyc文件),pyc文件是在python虛擬機(jī)上執(zhí)行的一種跨平臺字節(jié)碼扬舒。
運(yùn)行時:虛擬機(jī)解釋器把opcode(pyc文件)解釋成具體機(jī)器的機(jī)器碼阐肤,執(zhí)行。
JVM與PVM
- Java代碼從源程序到執(zhí)行讲坎,要經(jīng)過的過程是:編譯器(javac)把源代碼轉(zhuǎn)化為字節(jié)碼,然后解釋器(Java.exe)把字節(jié)碼轉(zhuǎn)換為計算機(jī)理解的機(jī)器碼來執(zhí)行愧薛。其中編譯器和解釋器都是Java虛擬機(jī)(JVM)的一部分晨炕,由于針對不同的硬件與OS,Java解釋器有所不同毫炉,因此可以實(shí)現(xiàn)“一次編譯瓮栗、到處執(zhí)行”。所以JVM是Java跨平臺特性的關(guān)鍵所在。
- 對于Python费奸,其源代碼到執(zhí)行也要經(jīng)過如下過程:源代碼--->字節(jié)碼--->機(jī)器碼弥激。與Java不同的是,Python使用的虛擬機(jī)是基于其他語言實(shí)現(xiàn)的愿阐,比如我們一般使用的Python實(shí)際為Cpython微服,也就是其虛擬機(jī)由C實(shí)現(xiàn),這個虛擬機(jī)負(fù)責(zé)把Python源碼編譯為字節(jié)碼缨历,再解釋執(zhí)行以蕴。另外,還有Jypython辛孵、Ironpython等丛肮。
PHP-Zend&HHVM
- Zend引擎默認(rèn)做法,是先編譯為opcode魄缚,然后再逐條執(zhí)行宝与,通常每條指令對應(yīng)的是C語言級別的函數(shù)。如果我們產(chǎn)生大量重復(fù)的opcode(純PHP寫的代碼和函數(shù))冶匹,對應(yīng)的則是Zend多次逐條執(zhí)行這些C代碼习劫。
- HHVM生成和執(zhí)行PHP的中間字節(jié)碼(HHVM生成自己格式的中間字節(jié)碼),執(zhí)行時通過JIT(Just In Time徙硅,即時編譯是種軟件優(yōu)化技術(shù)榜聂,指在運(yùn)行時才會去編譯字節(jié)碼為機(jī)器碼)轉(zhuǎn)為機(jī)器碼執(zhí)行。JIT將大量重復(fù)執(zhí)行的字節(jié)碼在運(yùn)行的時候編譯為機(jī)器碼嗓蘑,達(dá)到提高執(zhí)行效率的目的须肆。通常,觸發(fā)JIT的條件是代碼或者函數(shù)被多次重復(fù)調(diào)用桩皿。
寫在最后
時間匆忙豌汇,囫圇吞棗,努力完善泄隔。
后端開發(fā)離不開Java拒贱,python和php,深入學(xué)習(xí)原理佛嬉,比較異同逻澳,最佳使用。
2017.06.23