Java 很多不同運行方式,但 都離不開JRE 务甥,Java 運行環(huán)境牡辽。
- 開發(fā)工具運行
- 雙擊執(zhí)行Jar 文件運行
- 命令行運行
- 網(wǎng)頁中運行
JRE : 包含必要組件,Java虛擬機敞临,Java 核心類庫态辛。
JDK : Java 開發(fā)工具包,包含JRE 挺尿,附帶一系列開發(fā)奏黑、診斷工具
Java 執(zhí)行系統(tǒng)主流實現(xiàn)以及設(shè)計決策
C++ 可以直接編譯成機器碼運行炊邦,Java為什么還需要虛擬機中運行呢?
- Java 是高級程序語言熟史,語法非常復(fù)雜馁害,抽象度很高,執(zhí)行在硬件運行復(fù)雜的程序不現(xiàn)實蹂匹,所以運行之前需要轉(zhuǎn)換
轉(zhuǎn)換設(shè)計思路:
設(shè)計面向 java 語言特性的虛擬機碘菜,并通過編譯器將Java 程序轉(zhuǎn)換成該虛擬機能識別的 指令序列。就是Java 字節(jié)碼限寞。
取名來歷: 因為Java 字節(jié)碼 指令的操碼 (opcode) 被固定位一個字節(jié)忍啸。
Java 虛擬機可以由硬件實現(xiàn),但更多是軟件實現(xiàn)履植,一旦程序被轉(zhuǎn)換成Java 字節(jié)碼计雌,便可以在不同平臺虛擬機里面運行,我們說的 一次編寫玫霎,到處運行凿滤。
帶來了一個托管環(huán)境,能代替我們處理一些代碼冗長而且容易出錯的部分鼠渺。
其中當屬自動內(nèi)存管理鸭巴,與 垃圾回收。
除此之外拦盹,托管環(huán)境還提供諸如數(shù)組越界鹃祖、動態(tài)類型、安全權(quán)限等動態(tài)監(jiān)測普舆,免于書寫這些業(yè)務(wù)無關(guān)的邏輯代碼恬口。
Java虛擬機具體怎么運行的java 代碼?
以標準JDK 中的 HotSpot 虛擬機為例沼侣,從虛擬機以及底層硬件兩個角度祖能,講Java 虛擬機具體是怎么運行Java 字節(jié)碼的。
- 虛擬機 視角
執(zhí)行Java 代碼蛾洛,要將它編譯而成的Class 文件加載到Java 虛擬機中养铸。
加載后Java 類會存放在 方法去(Method Area) 中。實際運行時轧膘,虛擬機會執(zhí)行方法區(qū)內(nèi)的代碼钞螟。
Java 虛擬機在內(nèi)存中劃分出堆和棧來存儲運行時的數(shù)據(jù)。
將棧細分為Java方法的Java方法棧谎碍,和面向本地方法(用C++ 寫的native)的本地方法棧鳞滨。
以及存放各個線程執(zhí)行位置的PC 寄存器。
運行過程中蟆淀,每當調(diào)用一個Java 方法拯啦,Java 虛擬機會在當前線程的Java 方法棧中生成一個棧幀澡匪,用以存放局部變量以及字節(jié)碼操作數(shù)。
棧幀大小提前計算好的褒链,虛擬機不要錢棧幀在內(nèi)存工具里連續(xù)分布唁情。當退出當前執(zhí)行方法時,不管正常返回還是異常返回碱蒙,虛擬機均會彈出當前線程的當前棧幀荠瘪,并將其舍棄。
- 從硬件 視角
- Java 字節(jié)碼無法直接執(zhí)行赛惩,Java 虛擬機需要將字節(jié)碼翻譯成機器碼。
在Hotspot 里面趁餐,翻譯過程有兩種形式:
第一種:解釋執(zhí)行喷兼,即逐條將字節(jié)碼翻譯成機器碼并執(zhí)行。
第二種:即時編譯(Just-In-Time compilation 后雷,JIT) 季惯,將一個方法中包含的所有字節(jié)碼貶義詞機器碼后再執(zhí)行。
前者優(yōu)勢在于無需等待編譯臀突,而后者優(yōu)勢在于實際運行速度更快勉抓。
Hotspot 默認采用混合模式,綜合解釋執(zhí)行和即時編譯的優(yōu)點候学。 會先解釋執(zhí)行字節(jié)碼藕筋,而后將其中反復(fù)執(zhí)行的熱點代碼,以方法為單位進行即時編譯梳码。
運行效率如何隐圾?
Hotspot 采用了多種技術(shù)提升啟動性能以及峰值性能,即時編譯是其中最重要的技術(shù)之一掰茶。
即時編譯 建立在程序符合二八定律的假設(shè)上暇藏,就是20%代碼占據(jù)了80%的計算資源。
80%不常用的代碼濒蒋,需要耗費時間將其編譯成機器碼盐碱,采取解釋執(zhí)行方式運行。理論上 即時編譯后 Java 程序的執(zhí)行效率 可能超過 c++ 程序的沪伙,
因為瓮顽,與靜態(tài)編譯相比,即時編譯擁有程序的運行時信息焰坪,并能根據(jù)信息作出相應(yīng)的優(yōu)化趣倾。
舉例,虛方法用來實現(xiàn)對象多態(tài)性的某饰,一個虛方法調(diào)用儒恋,盡管很多目標方法善绎,但實際運行過程可能只調(diào)用其中一個,這個信息被即時編譯利用诫尽,來規(guī)避虛方法調(diào)用開銷禀酱,從而達到比靜態(tài)編譯的C++ 程序更高的性能。
如何規(guī)避的牧嫉?是把所有目標方法提前全部即時編譯?
- 為了滿足不同用戶場景需要Hotspot 內(nèi)置了多個即時編譯器剂跟,C1 C2 和Graal。
引入多個即時編譯器酣藻,
為了編譯時間和生成代碼的執(zhí)行效率進行取舍曹洽。
- C1: Client 編譯器 ,面向的是對啟動性能有要求的客戶端GUI 程序辽剧,采用的優(yōu)化手段相對簡單送淆,所以編譯時間較短。
C2:Server 編譯器怕轿,面向的是對峰值性能有要求的服務(wù)器端程序偷崩,采用的優(yōu)化手段相對復(fù)雜,因此編譯時間較長撞羽,但同時生成代碼的執(zhí)行效率較高阐斜。Java 7 開始分層編譯,熱點方法首先C1 編譯诀紊,然后熱點方法中的熱點會進一步被C2編譯
為了不干擾應(yīng)用的正常運行谒出,Hotspot即時編譯是放在額外的編譯線程中進行的。
Hotspot根據(jù)CPU 的數(shù)量設(shè)置編譯線程的數(shù)目渡紫,并且按1:2 的比例配置給 C1 及 C2 編譯器到推。在計算資源充足的情況下,字節(jié)碼解釋執(zhí)行和即時編譯同時進行惕澎,編譯完成后機器碼會在下次調(diào)用該方法時啟用莉测,以替換原本的解釋執(zhí)行。
作業(yè):思考Java語言 和Java虛擬機看到Boolean類型的方式是否不同唧喉。
$ echo '
public class Foo {
public static void main(String[] args) {
boolean flag = true;
if (flag) System.out.println("Hello, Java!");
if (flag == true) System.out.println("Hello, JVM!");
}
}' > Foo.java
$ javac Foo.java
$ java Foo
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.1
$ awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.1 > Foo.jasm
$ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm
$ java Foo
----------------------- 解釋 ----------------------------
jvm把boolean當做int來處理
flag = iconst_1 = true
awk把stackframe中的flag改為iconst_2
if(flag)比較時ifeq指令做是否為零判斷捣卤,常數(shù)2仍為true,打印輸出
if(true == flag)比較時if_cmpne做整數(shù)比較八孝,iconst_1是否等于flag董朝,比較失敗,不再打印輸出
精彩評論:
解釋執(zhí)行 執(zhí)行時才翻譯成機器指令干跛,無需保存不占內(nèi)存子姜。但即時編譯類似預(yù)編譯,編譯之后的指令需要保存在內(nèi)存中楼入,這種方式吃內(nèi)存哥捕,按照二八原則這種混合模式最恰當?shù)哪脸椋瑹狳c代碼編譯之后放入內(nèi)存避免重復(fù)編譯,而其他運行次數(shù)較少代碼則解釋執(zhí)行遥赚,避免占用過多內(nèi)存
為什么不把代碼全部編譯成機器碼扬舒?
問得好!事實上JVM確實有考慮做AOT (ahead of time compilation) 這種事情凫佛。AOT能夠在線下將Java字節(jié)碼編譯成機器碼讲坎,主要是用來解決啟動性能不好的問題。
對于這種發(fā)布頻率不頻繁(也就是長時間運行吧愧薛?)的程序晨炕,其實選擇線下編譯和即時編譯都一樣,因為至多一兩個小時后該即時編譯的都已經(jīng)編譯完成了毫炉。另外府瞄,即時編譯器因為有程序的運行時信息,優(yōu)化效果更好碘箍,也就是說峰值性能更好。
- 熱點代碼區(qū)別
看到有人說熱點代碼的區(qū)別鲸郊,在git里面涉及到的熱點代碼有兩種算法丰榴,
基于采樣的熱點探測和基于計數(shù)器的熱點探測。
一般采用的都是基于計數(shù)器的熱點探測秆撮,兩者的優(yōu)缺點百度一下就知道了四濒。
基于計數(shù)器的熱點探測又有兩個計數(shù)器:
方法調(diào)用計數(shù)器,回邊計數(shù)器职辨,他們在C1和C2又有不同的閾值盗蟆。????
- 什么時候使用C1,什么時候使用C2舒裤,他是怎么區(qū)分熱點方法的呢喳资?
作者回復(fù): 剛剛看到一個同學(xué)總結(jié)了。JVM會統(tǒng)計每個方法被調(diào)用了多少次腾供,超過多少次仆邓,那就是熱點方法。(還有個循環(huán)回邊計數(shù)器伴鳖,用來編譯熱循環(huán)的节值。)
默認的分層編譯應(yīng)該是達到兩千調(diào)C1,達到一萬五調(diào)C2榜聂。
- 棧幀
老師搞疗,那個pc寄存器,本地方法棧须肆,以及方法棧匿乃,java方法棧這三個組成的就是我們常統(tǒng)稱的棧吧桩皿,然后也叫棧幀?
作者回復(fù): JVM里的棧指的應(yīng)該是Java方法棧和本地方法棧扳埂。每個方法調(diào)用會在棧上劃出一塊作為棧幀(stack frame)业簿。棧是由多個棧幀構(gòu)成的,就好比電影是由一個個幀構(gòu)成的阳懂。
- 為啥是"理論"上比cpp快...這樣看起來 如果都編譯成機器碼了 應(yīng)該就是挺快的呀... 那干啥不像Go一樣 直接編譯成目標平臺的機器碼... 咋感覺繞了一圈..
作者回復(fù): 因為實際上會插入一些虛擬機相關(guān)的代碼梅尤,稍微拉低了運行效率。
至于為什么不采用直接編譯的方法岩调,在峰值性能差不多的這個前提下巷燥,線下編譯和即時編譯就是兩種選項,各有優(yōu)缺點号枕。JVM這樣做缰揪,主要也是看重字節(jié)碼的可移植性,而犧牲了啟動性能葱淳。
另外呢钝腺,現(xiàn)代工程語言實現(xiàn)都是抄來抄去的。JVM也引入了AOT編譯赞厕,在線下將Java代碼編譯成可鏈接庫艳狐。
- 老師,問一下這個asmtools是做什么用的
作者回復(fù): 就是Java字節(jié)碼的反匯編器和匯編器皿桑。
- 解釋執(zhí)行是將字節(jié)碼翻譯為機器碼毫目,JIT也是將字節(jié)碼翻譯為機器碼,為什么JIT就比解釋執(zhí)行要快這么多诲侮?
如果說JIT檢測到是熱點代碼并且進行優(yōu)化镀虐,那么為什么解釋執(zhí)行不直接就用這種優(yōu)化去解釋字節(jié)碼?
一些比較淺的問題沟绪,希望老師能指點一二
作者回復(fù): 1. 就單條加法字節(jié)碼而言刮便,解釋執(zhí)行器需要識別字節(jié)碼,然后將兩個操作數(shù)從Java方法棧上讀取出來并相加近零,最后將結(jié)果存入Java方法棧中诺核。而JIT生成的機器碼就只是一個CPU加法指令。
- 因為JIT比較費時久信。如果字節(jié)碼需要JIT后才跑窖杀,那么啟動性能會很糟糕
- 對于占據(jù)大部分的不常用的代碼,我們無需耗費時間將其編譯成機器碼裙士,而是采取解釋執(zhí)行的方式運行入客;
這是否意味著不常用的代碼的多次調(diào)用就要多次進行解釋執(zhí)行
作者回復(fù): 調(diào)用到一定次數(shù)就會觸發(fā)即時編譯的
- 對不起,聽了29篇文章了,至今不太清楚hotspot和openjdk兩者之間的關(guān)系桌硫。
作者回復(fù): HotSpot是JVM里的引擎夭咬,可以理解為JDK中用C++寫的部分。Oracle JDK/OpenJDK包括HotSpot铆隘。
- 搞不懂卓舵,沒有講清楚堆棧到底如何共享?有些文章說棧數(shù)據(jù)共享膀钠,但又說每個線程都會有一個堆棧掏湾,那堆棧的數(shù)據(jù)還如何共享?還有堆有時候說數(shù)據(jù)不共享肿嘲,但又說線程間數(shù)據(jù)共享融击?這老師能解答一下嗎?
作者回復(fù): 線程各自的楒撸空間是不共享的尊浪,但可以通過堆空間來共享數(shù)據(jù)。如果只有一個線程知道某個數(shù)據(jù)存放在堆的哪個位置封救,那也相當于不共享拇涤。注意不是等同于不共享,因為其它線程可以掃描整個堆誉结,來找到這個位置工育。
- 方法區(qū)是不是屬于堆的一部分?
作者回復(fù): 不屬于搓彻。JVM中的堆是用來存放Java對象的。
-老師你好嘱朽,我有個地方還是想不通旭贬,為什么java采用一次編譯,到處運行的這種方式搪泳,而不是C++的不同平臺都進行編譯稀轨, java這樣設(shè)計 加了中間層 反而執(zhí)行效率降低,那這種設(shè)計的初衷是什么呢岸军?
作者回復(fù): 個人感覺應(yīng)該是靜態(tài)編譯的各種語言中C++比較突出奋刽,一次編譯到處運行的各種語言中Java比較典型。
你可以用LLVM把C++編譯成bitcode到處運行艰赞,也可以用AOT把Java編譯成機器碼佣谐。只不過不是那么”流行”
-老師,即時編譯是啥算法方妖?編譯哪些代碼狭魂?何時編譯完成?為啥我每次壓測啟動后,top命令查看雌澄,同樣的代碼編譯線程工作時長不太一樣斋泄?
作者回復(fù): 即時編譯就是一個編譯器,里面有很多不同的優(yōu)化镐牺,對應(yīng)不同的算法炫掐。觸發(fā)即時編譯用的是JVM維護的統(tǒng)計方法調(diào)用次數(shù)的計數(shù)器。編譯時間取決于編譯器自己的效率睬涧。由于程序的不確定性募胃,在多線程環(huán)境下即時編譯器干的活可能多可能少。