From:深入理解Java虛擬機(jī)
- 目錄
BiBi - JVM -0- 開(kāi)篇
BiBi - JVM -1- Java內(nèi)存區(qū)域
BiBi - JVM -2- 對(duì)象
BiBi - JVM -3- 垃圾收集算法
BiBi - JVM -4- HotSpot JVM
BiBi - JVM -5- 垃圾回收器
BiBi - JVM -6- 回收策略
BiBi - JVM -7- Java類文件結(jié)構(gòu)
BiBi - JVM -8- 類加載機(jī)制
BiBi - JVM -9- 類加載器
BiBi - JVM -10- 虛擬機(jī)字節(jié)碼
BiBi - JVM -11- 編譯期優(yōu)化
BiBi - JVM -12- 運(yùn)行期優(yōu)化
BiBi - JVM -13- 并發(fā)
物理機(jī):執(zhí)行引擎直接建立在處理器、硬件娃善、指令集和操作系統(tǒng)層面上踪央。
虛擬機(jī):執(zhí)行引擎由自己實(shí)現(xiàn),可以自行制定指令集曲饱。
1. 棧楨
棧楨是虛擬機(jī)方法調(diào)用過(guò)程的數(shù)據(jù)結(jié)構(gòu),主要包括:局部變量表、操作數(shù)棧一疯、動(dòng)態(tài)鏈接存炮、方法返回地址等炬搭。在編譯代碼的時(shí)候,棧楨中需要多大的局部變量表穆桂,多深的操作數(shù)棧都已經(jīng)全部確定了宫盔,并且寫入到方法表的Code屬性之中【其中max_loclas代表局部變量表的最大容量】,因此一個(gè)棧楨需要分配多少內(nèi)存享完,不會(huì)受到程序運(yùn)行期變量數(shù)據(jù)的影響灼芭。
-
局部變量表
存放方法參數(shù)和方法內(nèi)部定義的局部變量。局部變量表的容量以【變量槽般又,Slot】為最小單位彼绷,一個(gè)Slot可以存放一個(gè)32位以內(nèi)的數(shù)據(jù)類型。對(duì)于long和double 64位的數(shù)據(jù)會(huì)以高位對(duì)齊的方式分配兩個(gè)連續(xù)的Slot空間茴迁。在方法執(zhí)行時(shí)寄悯,虛擬機(jī)使用局部變量表完成【參數(shù)值】到【參數(shù)變量列表】的傳遞。
Slot復(fù)用影響垃圾回收行為:
//方式一:變量b還在作用域之內(nèi)堕义,所以不會(huì)回收b的內(nèi)存猜旬。
public static void main(String[] args) {
byte[] b = new byte[1024 * 1024 * 64];
System.gc();
}
//方式二:由于Slot復(fù)用的原因脚曾,b不會(huì)被回收
public static void main(String[] args) {
{
byte[] b = new byte[1024 * 1024 * 64];
}
System.gc();
}
//方式三:b被回收
public static void main(String[] args) {
{
byte[] b = new byte[1024 * 1024 * 64];
}
int a = 0;
System.gc();
}
上面三種方式中b能否被回收的根本原因:局部變量表中的Slot是否還存有b數(shù)組對(duì)象的引用双肤。方式二雖然已經(jīng)離開(kāi)了b的作用域,但在此之后沒(méi)有任何對(duì)局部變量表的讀寫操作猛频,b原本所占用的Slot還沒(méi)有被其它變量所復(fù)用糖耸,所以作為GC Roots一部分的局部變量表仍然保持著對(duì)他的關(guān)聯(lián)秘遏。【也可以通過(guò)手動(dòng)設(shè)置b = null,來(lái)達(dá)到回收的效果】但嘉竟,方式二邦危,經(jīng)過(guò)JIT編譯后,可以回收舍扰,所以無(wú)需像方式三那樣處理倦蚪。
注意:局部變量沒(méi)有默認(rèn)值。
-
操作數(shù)棧
在編譯的時(shí)候?qū)⒆畲笊疃葘懭氲紺ode屬性的max_stacks數(shù)據(jù)項(xiàng)中边苹。兩個(gè)棧楨作為虛擬機(jī)元素陵且,是完全獨(dú)立的。但在大多虛擬機(jī)的實(shí)現(xiàn)里都會(huì)做一些優(yōu)化處理,令兩個(gè)棧楨一部分重疊慕购,這樣在方法調(diào)用時(shí)可以共用一部分?jǐn)?shù)據(jù)聊疲,無(wú)需進(jìn)行額外的參數(shù)復(fù)制傳遞。
Java虛擬機(jī)是基于棧的執(zhí)行引擎沪悲,其中所指的棧就是【操作數(shù)椈裰蓿】。
-
動(dòng)態(tài)連接
每個(gè)棧楨都包含一個(gè)指向運(yùn)行時(shí)【常量池】中該棧楨所屬方法的引用殿如,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接贡珊。Class文件的常量池中存有大量的【符號(hào)引用】,字節(jié)碼中的方法調(diào)用指令就是以常量池中指向方法的符號(hào)引用作為參數(shù)涉馁。這些符號(hào)引用一部分在類加載階段或第一次使用的時(shí)候就轉(zhuǎn)化為直接引用门岔,這種轉(zhuǎn)化稱為【靜態(tài)解析,如:靜態(tài)方法烤送、私有方法寒随、實(shí)例構(gòu)造器、父類方法胯努。即編譯期可知牢裳,運(yùn)行期間不可變】;另外一部分在每次運(yùn)行期間轉(zhuǎn)化為直接引用叶沛,這種轉(zhuǎn)換稱為【動(dòng)態(tài)連接】蒲讯。
2. 方法調(diào)用
invokestatic
invokespecial
invokevirtual
invointerface
invokedynamic【分派邏輯不是由虛擬機(jī)決定的,而是由程序員決定】
Class文件的編譯過(guò)程中不包含傳統(tǒng)編譯中的連接步驟灰署,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用判帮,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址。此特性可以在類加載期間溉箕,或在運(yùn)行期間再確定目標(biāo)方法的直接引用晦墙,使動(dòng)態(tài)擴(kuò)展能力更強(qiáng)。
靜態(tài)解析的字節(jié)碼指令:invokestatic 肴茄、 invokespecial【實(shí)例構(gòu)造器晌畅、私有方法、父類方法】和被final修飾的方法寡痰。在類加載的時(shí)候把符號(hào)引用解析為直接引用抗楔。
-
靜態(tài)分派【針對(duì)重載】
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
public void say(Human obj) {
System.out.println("human");
}
public void say(Man obj) {
System.out.println("man");
}
public static void main(String[] args) {
Human man = new Man();
StaticDispatch st = new StaticDispatch();
st.say(man); //human
}
}
輸出結(jié)果為:human。
在Human man = new Man()
中拦坠,Human為變量的【靜態(tài)類型】连躏;Man為變量的【實(shí)際類型】。靜態(tài)類型在編譯期可知贞滨,而實(shí)際類型在運(yùn)行期才可確定入热。重載是通過(guò)參數(shù)的靜態(tài)類型而不是實(shí)際類型作為判斷依據(jù)。所以,在編譯階段就決定了使用哪個(gè)重載版本勺良。
-
動(dòng)態(tài)分派【針對(duì)多態(tài)的覆蓋】
運(yùn)行期間根據(jù)【實(shí)際類型】確定方法執(zhí)行版本的分派過(guò)程稱為動(dòng)態(tài)分配绰播。
在類方法區(qū)中建立一個(gè)【虛方法表】,使用虛方法表索引來(lái)確定各個(gè)方法的實(shí)際入口郑气。具有相同簽名的方法幅垮,在父類腰池、子類的虛方法表中都具有一樣的索引號(hào)尾组。
3. 動(dòng)態(tài)語(yǔ)言支持
JDK7字節(jié)碼指令集中添加【invokedynamic指令】來(lái)支持動(dòng)態(tài)類型語(yǔ)言,也是為JDK8中的Lambda表達(dá)式做準(zhǔn)備示弓。
動(dòng)態(tài)語(yǔ)言關(guān)鍵特征:類型檢查在運(yùn)行期而不是編譯期讳侨。
特點(diǎn):變量無(wú)類型而變量值才有類型。對(duì)Java虛擬機(jī)而言奏属,它可以同時(shí)支持靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言【Groovy跨跨、JRuby】。
由于invokestatic囱皿、invokespecial勇婴、invokevirtual、invointerface的第一個(gè)參數(shù)都是【被調(diào)用方法的符號(hào)引用】嘱腥,該符號(hào)引用在編譯時(shí)產(chǎn)生耕渴,而動(dòng)態(tài)語(yǔ)言只有在運(yùn)行期間才能確定接收者的類型,這種底層問(wèn)題只有在虛擬機(jī)層次上去解決才是最合適的齿兔,因此才有invokedynamic指令誕生橱脸。
java.lang.invoke包在之前單純依靠符號(hào)引用來(lái)確定調(diào)用的目標(biāo)方法之外,提供了一種新的動(dòng)態(tài)目標(biāo)方法機(jī)制分苇,稱為【MethodHandle添诉,類似于函數(shù)指針】。
MethodHandler效果與Reflection反射類似都是在模擬方法調(diào)用医寿,其區(qū)別如下:
1)Reflection在模擬Java代碼層次的方法調(diào)用栏赴,而MethodHandler在模擬字節(jié)碼層次的方法調(diào)用。
2)Reflection是重量級(jí)的靖秩,它包含方法的簽名须眷、描述符、方法屬性表等盆偿;而MethodHandler是輕量級(jí)的柒爸,只包含執(zhí)行該方法相關(guān)信息。
3)Reflection只是為Java語(yǔ)言設(shè)計(jì)的事扭,而MethodHandler可服務(wù)于所有Java虛擬機(jī)上的語(yǔ)言捎稚。
- 問(wèn)題:通過(guò)super可以調(diào)用父類中的方法,如何調(diào)用祖父類中的方法呢?
package com.ljg;
import android.os.Build;
import android.support.annotation.RequiresApi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import static java.lang.invoke.MethodHandles.lookup;
public class Test {
class GrandFather {
void say() {
System.out.println("grandFather");
}
}
class Father extends GrandFather {
void say() {
System.out.println("father");
}
}
class Son extends Father {
void say() {
//調(diào)用父類中的方法
super.say();
//調(diào)用祖父類中的方法
try {
MethodType methodType = MethodType.methodType(void.class);
MethodHandle methodHandle =
lookup().findSpecial(GrandFather.class, "say", methodType, getClass());
methodHandle.invoke(this);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
}
4. 基于棧的指令集與基于寄存器的指令集
主流的PC機(jī)的指令集架構(gòu)是依賴【寄存器】進(jìn)行工作的今野。
1+1基于棧的指令集:
iconst_1
iconst_1
iadd
istore_0【把棧頂?shù)闹捣诺骄植孔兞勘淼牡?個(gè)Slot中】1+1基于寄存器的指令集:
mov eax, 1
add eax, 1【結(jié)果保存在EAX寄存器中】基于棧的指令集的【優(yōu)點(diǎn)】:
1)可移植葡公。寄存器是由硬件直接提供的,而使用棧架構(gòu)的指令集条霜,用戶程序不會(huì)直接使用寄存器催什,虛擬機(jī)可自行將一些訪問(wèn)頻繁的數(shù)據(jù)【如:程序計(jì)數(shù)器、棧頂緩存】放到寄存器中以獲得更好的性能宰睡。
2)代碼緊湊
3)編譯器實(shí)現(xiàn)簡(jiǎn)單基于棧的指令集的【缺點(diǎn)】:
執(zhí)行速度慢蒲凶。原因:
1)指令數(shù)量一般比寄存器架構(gòu)多,因?yàn)槌鰲H霔2僮骶蜁?huì)產(chǎn)生很多指令拆内。
2)棧實(shí)現(xiàn)在內(nèi)存中旋圆,頻繁的棧訪問(wèn)也就意味著頻繁的內(nèi)存訪問(wèn)。盡管虛擬機(jī)可以采用【棧頂緩存】的手段麸恍,把最常用的操作映射到寄存器中避免直接訪問(wèn)內(nèi)存灵巧,但這只是優(yōu)化措施而不是解決問(wèn)題的本質(zhì)。
所以抹沪,由于指令數(shù)量和內(nèi)存訪問(wèn)的原因刻肄,導(dǎo)致棧架構(gòu)的指令集執(zhí)行速度慢。