???? 學(xué)習(xí)交流群:
??1:這是孫哥suns給大家的福利!
??2:我們免費分享Netty、Dubbo、k8s、Mybatis、Spring...應(yīng)用和源碼級別的視頻資料
????3:QQ群:583783824 ? ???? ?工作微信:BigTreeJava 拉你進微信群,免費領(lǐng)取!
????4:本文章內(nèi)容出自上述:Spring應(yīng)用課程!????
????5:以上內(nèi)容,進群免費領(lǐng)取呦~ ????????
文章目錄
一:方法的調(diào)用
1:概述
2:靜態(tài)鏈接
3:動態(tài)鏈接
二:方法的綁定
1:綁定概念
2:早期綁定
3:晚期綁定
三:晚期綁定示例
1:編寫代碼
2:jclasslib查看內(nèi)容
四:早期綁定示例?
1:編寫代碼
2:jclasslib查看內(nèi)容
五:總結(jié)說明
一:方法的調(diào)用
? ? ? ? 我們每天都在寫方法的調(diào)用,但是我們能搞明白其中的原理和JVM當中的操作步驟么?這就是本文的意義。
1:概述
? ? ? ? 官方說法:
? ? ? ? 在JVM中,將符號引用轉(zhuǎn)換為調(diào)用方法的直接引用這個操作是跟JVM當中方法的綁定機制息息相關(guān)的。
? ? ? ? 說人話:
? ? ? ? 上邊這段話是什么意思?我這里給大家解釋一下,我們javap整理完畢字節(jié)碼文件之后,我們會可以在任意一個方法中查看code下的字節(jié)碼指令,很多字節(jié)碼指令的后邊都會跟#數(shù)字這么一個概念,這個就是符號引用,這個引用指向常量池。
? ? ? ? 所謂將符號引用轉(zhuǎn)換為方法的直接引用,就是將這個字節(jié)碼指令后邊的符號引用,轉(zhuǎn)變?yōu)檎鎸嵉姆椒ā?/p>
? ? ? ? 下列中的#3就是符號引用。
public void methodB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String methodB().....
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: invokevirtual #7 // Method methodA:()V
12: aload_0
13: dup
14: getfield #2 // Field num:I
17: iconst_1
18: iadd
19: putfield #2 // Field num:I
22: return
? ? ? ? 從上述找一個例子的話,就是將偏移地址為9的字節(jié)碼指令后邊的#7這個符號引用用真實的方法字面量代替
2:靜態(tài)鏈接
? ? ? ? 官方說法:
????????當一個字節(jié)碼文件被裝載進JVM內(nèi)部時,如果被調(diào)用的目標方法在編譯期可知且運行期保持不變時。這種情況下將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用的過程稱之為靜態(tài)鏈接。
? ? ? ? 說人話:
? ? ? ? 靜態(tài)鏈接:這種方式在編譯階段就已經(jīng)把符號引用直接轉(zhuǎn)換為了直接引用。
3:動態(tài)鏈接
? ? ? ? 官方說法:
????????如果被調(diào)用的方法在編譯期無法被確定下來,也就是說,只能夠在程序運行期將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用,由于這種引用轉(zhuǎn)換過程具備動態(tài)性,因此也就被稱之為動態(tài)鏈接。
? ? ? ? 說人話:
? ? ? ? 動態(tài)鏈接:這種方式在運行階段才能把符號引用直接轉(zhuǎn)換為直接引用。
二:方法的綁定
1:綁定概念
????????綁定是一個字段、方法或者類在符號引用被替換為直接引用的過程,這僅僅發(fā)生一次。這個不論是編譯器確定還是運行期確定都只會發(fā)生一次,不會修改。
????????對應(yīng)的方法的綁定機制為:早期綁定 (Early Bindng)和晚期綁定(Late Binding)。
2:早期綁定
? ? ? ? 官方說法:
????????早期綁定就是指被調(diào)用的目標方法如果在編譯期可知,且運行期保持不變時即可將這個方法與所屬的類型進行綁定,這樣一來,由于明確了被調(diào)用的目標方法究竟是哪一個,因此也就可以使用靜態(tài)鏈接的方式將符號引用轉(zhuǎn)換為直接引用。
? ? ? ? 說人話:
? ? ? ? 早期綁定是和我們的靜態(tài)綁定相對應(yīng)的。
3:晚期綁定
? ? ? ? 官方說法:
????????如果被調(diào)用的方法在編譯期無法被確定下來,只能夠在程序運行期根據(jù)實際的類型綁定相關(guān)的方法,這種綁定方式也就被稱之為晚期綁定
? ? ? ? 說人話:
? ? ? ? 晚期綁定是和我們的動態(tài)綁定相對應(yīng)的。
三:晚期綁定示例
1:編寫代碼
class Animal {
public void eat(){
System.out.println("動物進食");
}
}
interface Huntable{
void hunt();
}
class Dog extends Animal implements Huntable{
@Override
public void eat(){
System.out.println("狗吃骨頭");
}
@Override
public void hunt() {
System.out.println("捕食耗子,多管閑事");
}
}
class Cat extends Animal implements Huntable{
@Override
public void eat(){
System.out.println("貓吃魚");
}
@Override
public void hunt() {
System.out.println("捕食耗子,天經(jīng)地義");
}
}
public class AnimalTest{
public void showAnimal(Animal animal){
animal.eat();//晚期綁定
}
public void showHunt(Huntable h){
h.hunt();//晚期綁定
}
}
2:jclasslib查看內(nèi)容
四:早期綁定示例?
1:編寫代碼
class Animal {
public void eat(){
System.out.println("動物進食");
}
}
interface Huntable{
void hunt();
}
class Dog extends Animal implements Huntable{
@Override
public void eat(){
super.eat();//早期綁定
System.out.println("狗吃骨頭");
}
@Override
public void hunt() {
System.out.println("捕食耗子,多管閑事");
}
}
class Cat extends Animal implements Huntable{
public Cat(){
super();//早期綁定
}
public Cat(String name){
this();//早期綁定
}
@Override
public void eat(){
System.out.println("貓吃魚");
}
@Override
public void hunt() {
System.out.println("捕食耗子,天經(jīng)地義");
}
}
public class AnimalTest{
public void showAnimal(Animal animal){
animal.eat();//晚期綁定
}
public void showHunt(Huntable h){
h.hunt();//晚期綁定
}
}
2:jclasslib查看內(nèi)容
? ? ? ? 光標放到cat這個類上查看他的jclasslib
?????????invokeSpecial是早期綁定字節(jié)碼指令,invokevirtual是晚期綁定的字節(jié)碼指令。
五:總結(jié)說明
????????隨著高級語言的橫空出世,類似于Java一樣的基于面向?qū)ο蟮木幊陶Z言如今越來越多,盡管這類編程語言在語法風格上存在一定的差別,但是它們彼此之間始終保持著一個共性,那就是都支持封裝、繼承和多態(tài)等面向?qū)ο筇匦?/p>
????????既然這一類的編程語言具備多態(tài)特性,那么自然也就具備早期綁定和晚期綁定兩種綁定方式。
????????Java中任何一個普通的方法其實都具備虛函數(shù)的特征,也就是運行期才能確定下來,它們相當于c++語言中的虛函數(shù) (c++中則需要使用關(guān)鍵字virtual來顯式定義)。
????????如果在Java程序中不希望某個方法擁有虛函數(shù)的特征時,則可以使用關(guān)鍵字final來標記這個方法。也就是一個方法不想被晚期綁定,直接把他給final修飾即可。
?
文章目錄
一:程序計數(shù)器?
1:概念
2:官方說法
3:圖解
4:特點
二:PC寄存器使用舉例
1:定義一個簡單的類
2:?重新編譯這個類
3: 進入class文件目錄
4:進行反編譯
5:重點關(guān)注區(qū)域?
三:PC寄存器兩個面試問題
1:問題1
2:問題2?
3:cpu時間片
4:并行、串行、并發(fā)
一:程序計數(shù)器?
1:概念
????????JVM中的程序計數(shù)寄存器 (Program Counter Register)中(程序計數(shù)寄存器), Register 的命名源于CPU的寄存器,寄存器存儲指令相關(guān)的現(xiàn)場信息,?CPU只有把數(shù)據(jù)裝載到寄存器才能夠運行
????????這里,并非是廣義上所指的物理寄存器,或許將其翻譯為PC計數(shù)器(或指令計數(shù)器)會更加貼切(也稱為程序鉤子)并且也不容易引起一些不必要的誤會。JVM中的PC寄存器是對物理PC寄存器的一種抽象模擬。 是一個軟件層面的概念
? ? ? ? 鉤子怎么理解呢?烤鴨的時候一個鉤子鉤一只鴨子。在我們的程序中一行一行代碼也是由PC寄存器這個鉤子鉤著,需要的時候提溜出來直接執(zhí)行就好啦。
2:官方說法
????????PC寄存器用來存儲指向下一條指令的地址也即將要執(zhí)行的指令代碼。由執(zhí)行引擎讀取下一條指令
3:圖解
? ? ? ? PC寄存器是每一個新城有一份。?在方法入棧之后,對應(yīng)的是一個又一個的棧幀入棧。棧幀分為局部變量表、操作數(shù)棧、動態(tài)鏈接、方法返回值(一個棧幀對應(yīng)一個方法)。棧幀中每一個指令呢,都會有一個行號的標識,PC寄存器存儲了下一條指令的地址。執(zhí)行引擎根據(jù)PC寄存器中的指令獲取下一條指令,執(zhí)行完畢之后再去PC寄存器中去取指令執(zhí)行。
4:特點
????????它是一塊很小的內(nèi)存空間儲區(qū)域。小到幾乎可以忽略不計(因為只存儲下一條指令的地址)。也是運行速度最快的存儲區(qū)域。
????????在JVM規(guī)范中,每個線程都有它自己的程序計數(shù)器,是線程私有的。生命周期與線程的生命周期保持一致(線程私有的都會和線程聲明周期一致)
????????任何時間一個線程都只有一個方法在執(zhí)行,也就是所謂的當前方法(上圖中的紅框區(qū)域)。程序計數(shù)器會存儲當前線程正在執(zhí)行的Java方法的JVM指令地址?;蛘?,如果是在執(zhí)行native方法,則是未指定值 (undefined)
? ? ? ? 怎樣理解棧的這個先進后出,后進先出呢?想想手槍中的子彈就明白了!
????????它是程序控制流的指示器,分支、循環(huán).跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。
????????字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。
????????它是唯一一個在Java 虛擬機規(guī)范中沒有規(guī)定任何outotMemoryError情況的區(qū)域。
二:PC寄存器使用舉例
1:定義一個簡單的類
public class Dashu {
public static void main(String[] args) throws IOException {
int i = 10;
int j = 20;
int k = i +j;
}
}
2:?重新編譯這個類
3: 進入class文件目錄
2023/10/24 22:45 <DIR> .
2023/10/09 11:20 <DIR> ..
2023/10/09 11:24 143,226 aaa.txt
2023/10/24 22:45 474 Dashu.class
2 個文件 143,700 字節(jié)
2 個目錄 116,607,049,728 可用字節(jié)
4:進行反編譯
D:\code\study\hadoop\shit\target\classes>javap -verbose Dashu.class
Classfile /D:/code/study/hadoop/shit/target/classes/Dashu.class
Last modified 2023-10-24; size 474 bytes
MD5 checksum 87503ee08b4b8f0b59290d1832fe49c7
Compiled from "Dashu.java"
public class Dashu
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#23 // java/lang/Object."<init>":()V
#2 = Class #24 // Dashu
#3 = Class #25 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 LDashu;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 i
#16 = Utf8 I
#17 = Utf8 j
#18 = Utf8 k
#19 = Utf8 Exceptions
#20 = Class #26 // java/io/IOException
#21 = Utf8 SourceFile
#22 = Utf8 Dashu.java
#23 = NameAndType #4:#5 // "<init>":()V
#24 = Utf8 Dashu
#25 = Utf8 java/lang/Object
#26 = Utf8 java/io/IOException
{
public Dashu();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LDashu;
public static void main(java.lang.String[]) throws java.io.IOException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 i I
6 5 2 j I
10 1 3 k I
Exceptions:
throws java.io.IOException
}
SourceFile: "Dashu.java"
D:\code\study\hadoop\shit\target\classes>
5:重點關(guān)注區(qū)域?
public static void main(java.lang.String[]) throws java.io.IOException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10 //取出來一個數(shù)10
2: istore_1 //保存到索引為1的位置
3: bipush 20 //取出來一個數(shù)20
5: istore_2 //保存到索引為2的位置
6: iload_1 //從索引1處取值
7: iload_2 //從索引2處取值
8: iadd //進行相加的操作
9: istore_3 //存到索引為3的位置
10: return //表示main方法結(jié)束
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 9: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 i I
6 5 2 j I
10 1 3 k I
Exceptions:
throws java.io.IOException
}
? ? ? ? 上邊我們注釋的地方有一行一行的數(shù)字,這個數(shù)字就是指令地址或者叫偏移地址。?偏移地址或者指令地址就是PC寄存器中存儲的結(jié)構(gòu)。
? ? ? ? 指令地址的右側(cè)就是執(zhí)行引擎需要執(zhí)行的具體的指令。具體的指令我們后邊都會分析到。
? ? ? ? PC寄存器當中存儲的就是這些數(shù)字。執(zhí)行引擎會去我們指令地址對應(yīng)的指令位置取出來具體的指令內(nèi)容(去我們的局部變量表中取指令)。然后去操作咱們的棧結(jié)構(gòu)中的局部變量表、操作數(shù)棧實現(xiàn)數(shù)據(jù)的存取,然后將我們的字節(jié)碼指令翻譯成為機器指令,就可以讓我們的CPU幫我們做運算了。
三:PC寄存器兩個面試問題
1:問題1
問題1:使用PC寄存器存儲字節(jié)碼指令地址有什么用呢?
問題2:為什么使用PC寄存器記錄當前線程的執(zhí)行地址呢?
? ? ? ? 這兩個本質(zhì)上是一個問題:
????????因為CPU需要不停的切換各個線程,這時候切換回來以后,就得知道接著從哪開始繼續(xù)執(zhí)行。
????????JVM的字節(jié)碼解釋器就需要通過改變PC寄存器的值來明確下一條應(yīng)該執(zhí)行什么樣的字節(jié)碼指令
2:問題2?
問題2:PC寄存器為什么會被設(shè)定為線程私有??
????????我們都知道所謂的多線程在一個特定的時間段內(nèi)只會執(zhí)行其中某一個線程的方法,CPU會不停地做任務(wù)切換,這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù),如何保證分毫無差呢?為了能夠準確地記錄各個線程正在執(zhí)行的當前字節(jié)碼指令地址,最好的辦法自然是為每一個線程都分配一個PC寄存器,這樣一來各個線程之間便可以進行獨立計算,從而不會出現(xiàn)相互干擾的情況。
????????由于CPU時間片輪限制,眾多線程在并發(fā)執(zhí)行過程中,任何一個確定的時刻,一個處理器或者多核處理器中的一個內(nèi)核,只會執(zhí)行某個線程中的一條指令。
????????這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù),如何保證分毫無差呢?每個線程在創(chuàng)建后,都會產(chǎn)生自己的程序計數(shù)器和棧幀,程序計數(shù)器在各個線程之間互不影響。
3:cpu時間片
????????CPU 時間片即 CPU 分配給各個程序的時間,每個線程被分配一個時間段,稱作它的時間片。
????????在宏觀上:我們可以同時打開多個應(yīng)用程序,每個程序并行不悖,同時運行。
????????但在微觀上:由于只有一個 CPU,一次只能處理程序要求的一部分如何處理公平,一種方法就是引入時間片,每個程序輪流執(zhí)行。
4:并行、串行、并發(fā)
? ? ? ? 并行就是多核CPU,每一個CPU都在執(zhí)行具體的線程,這個叫做并行。
? ? ? ? 串行就是單核情況下,線程排好隊,一次只有一個線程再跑文章來源:http://www.zghlxwxcb.cn/news/detail-720584.html
? ? ? ? 并發(fā)就是一個核CPU,多個線程來回切換搶占CPU資源。文章來源地址http://www.zghlxwxcb.cn/news/detail-720584.html
到了這里,關(guān)于深入理解JVM虛擬機第十三篇:詳解JVM中的程序計數(shù)器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!