JVM学习笔记之-运行时数据区概述及线程概述,程序计数器(PC寄存器),虚拟机栈(栈,局部变量表,操作数栈,动态连接,方法调用,方法返回地址等),本地方法接口,本地方法栈
運行時數(shù)據(jù)區(qū)概述及線程概述
內(nèi)存是非常重要的系統(tǒng)資源,是硬盤和CPU的中間倉庫及橋梁,承載著操作系統(tǒng)和應(yīng)用程序的實時運行。JVM內(nèi)存布局規(guī)定了Java在運行過程中內(nèi)存申請、分配、管理的策略,保證了JVM的高效穩(wěn)定運行。不同的JVM對于內(nèi)存的劃分方式和管理機制存在著部分差異。結(jié)合JVM虛擬機規(guī)范,來探討一下經(jīng)典的JVM內(nèi)存布局。
Java虛擬機定義了若千種程序運行期間會使用到的運行時數(shù)據(jù)區(qū),其中有一些會隨著虛擬機啟動而創(chuàng)建,隨著虛擬機退出而銷毀。另外一些則是與線程一一對應(yīng)的,這些與線程對應(yīng)的數(shù)據(jù)區(qū)域會隨著線程開始和結(jié)束而創(chuàng)建和銷毀。
灰色的為單獨線程私有的,紅色的為多個線程共享的。即:
關(guān)于線程間共享的說明
每個JVM只有一個Runtime實例。即為運行時環(huán)境,相當于內(nèi)存結(jié)構(gòu)的中間的那個框框:運行時環(huán)境。
線程
線程是一個程序里的運行單元。JVM允許一個應(yīng)用有多個線程并行的執(zhí)行。
在Hotspot JVM里,每個線程都與操作系統(tǒng)的本地線程直接映射。當一個Java線程準備好執(zhí)行以后,此時一個操作系統(tǒng)的本地線程也同時創(chuàng)建。Java線程執(zhí)行終止后,本地線程也會回收。
操作系統(tǒng)負責所有線程的安排調(diào)度到任何一個可用的CPU上。一旦本地線程初始化成功,它就會調(diào)用Java線程中的run ()方法。
JVM 系統(tǒng)線程
如果你使用jconsole或者是任何一個調(diào)試工具,都能看到在后臺有許多線程在運行。這些后臺線程不包括調(diào)用public static void main (string[ ])的main線程以及所有這個main線程自己創(chuàng)建的線程。
這些主要的后臺系統(tǒng)線程在Hotspot JVM里主要是以下幾個:
虛擬機線程:這種線程的操作是需要JVM達到安全點才會出現(xiàn)。這些操作必須在不同的線程中發(fā)生的原因是他們都需要JVM達到安全點,這樣堆才不會變化。這種線程的執(zhí)行類型包括"stop-the-world"的垃圾收集,線程棧收集,線程掛起以及偏向鎖撤銷。周期任務(wù)線程∶這種線程是時間周期事件的體現(xiàn)(比如中斷),他們一般用于周期性操作的調(diào)度執(zhí)行。GC線程:這種線程對在JVM里不同種類的垃圾收集行為提供了支持。>編譯線程:這種線程在運行時會將字節(jié)碼編譯成到本地代碼。信號調(diào)度線程∶這種線程接收信號并發(fā)送給JVM,在它內(nèi)部通過調(diào)用適當?shù)姆椒ㄟM行處理。程序計數(shù)器(PC寄存器)
PC Register(寄存器)介紹
官方介紹鏈接
https://docs.oracle.com/javase/specs/jvms/se8/html/
JVM中的程序計數(shù)寄存器(Program counter Register)中, Register 的命名源于CPU的寄存器,寄存器存儲指令相關(guān)的現(xiàn)場信息。CPU只有把數(shù)據(jù)裝載到寄存器才能夠運行。
這里,并非是廣義上所指的物理寄存器,或許將其翻譯為PC計數(shù)器(或指令計數(shù)器)會更加貼切(也稱為程序鉤子),并且也不容易引起一些不必要的誤會。JVM中的PC寄存器是對物理PC寄存器的一種抽象模擬。
PC 寄存器作用:
PC寄存器用來存儲指向下一條指令的地址,也即將要執(zhí)行的指令代碼。由執(zhí)行引擎讀取下一條指令。
它是一塊很小的內(nèi)存空間,幾乎可以忽略不記。也是運行速度最快的存儲區(qū)域。
在JVM規(guī)范中,每個線程都有它自己的程序計數(shù)器,是線程私有的,生命周期與線程的生命周期保持一致。
任何時間一個線程都只有一個方法在執(zhí)行,也就是所謂的當前方法。程序計數(shù)器會存儲當前線程正在執(zhí)行的Java方法的JVM指令地址;或者,如果是在執(zhí)行native方法,則是未指定值(undefined)。
它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。
字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。
它是唯一一個在Java 虛擬機規(guī)范中沒有規(guī)定任何GC(垃圾回收)與OOM(outOtMemoryError內(nèi)存溢出)情況的區(qū)域。
舉例說明
命令:
反編譯字節(jié)碼文件
代碼
package com.fs.jvm.pcRegister;/*** PC寄存器demo**/ public class PCRegisterTest {public static void main(String[] args) {int a = 10;int b = 20;int c = a+b;String s = "helloJVM";System.out.println(a);System.out.println(b);} }命令結(jié)果 javap -v ***.class
E:\code\test\target\classes\com\fs\jvm\pcRegister>javap -v PCRegisterTest.class Classfile /E:/code/test/target/classes/com/fs/jvm/pcRegister/PCRegisterTest.classLast modified 2021-5-11; size 690 bytesMD5 checksum 027c06f298a7914e61632dcd112aa741Compiled from "PCRegisterTest.java" public class com.fs.jvm.pcRegister.PCRegisterTestminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #6.#26 // java/lang/Object."<init>":()V#2 = String #27 // helloJVM#3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;#4 = Methodref #30.#31 // java/io/PrintStream.println:(I)V#5 = Class #32 // com/fs/jvm/pcRegister/PCRegisterTest#6 = Class #33 // java/lang/Object#7 = Utf8 <init>#8 = Utf8 ()V#9 = Utf8 Code#10 = Utf8 LineNumberTable#11 = Utf8 LocalVariableTable#12 = Utf8 this#13 = Utf8 Lcom/fs/jvm/pcRegister/PCRegisterTest;#14 = Utf8 main#15 = Utf8 ([Ljava/lang/String;)V#16 = Utf8 args#17 = Utf8 [Ljava/lang/String;#18 = Utf8 a#19 = Utf8 I#20 = Utf8 b#21 = Utf8 c#22 = Utf8 s#23 = Utf8 Ljava/lang/String;#24 = Utf8 SourceFile#25 = Utf8 PCRegisterTest.java#26 = NameAndType #7:#8 // "<init>":()V#27 = Utf8 helloJVM#28 = Class #34 // java/lang/System#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream;#30 = Class #37 // java/io/PrintStream#31 = NameAndType #38:#39 // println:(I)V#32 = Utf8 com/fs/jvm/pcRegister/PCRegisterTest#33 = Utf8 java/lang/Object#34 = Utf8 java/lang/System#35 = Utf8 out#36 = Utf8 Ljava/io/PrintStream;#37 = Utf8 java/io/PrintStream#38 = Utf8 println#39 = Utf8 (I)V {public com.fs.jvm.pcRegister.PCRegisterTest();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 7: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/fs/jvm/pcRegister/PCRegisterTest;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=5, args_size=10: bipush 102: istore_13: bipush 205: istore_26: iload_17: iload_28: iadd9: istore_310: ldc #2 // String helloJVM12: astore 414: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;17: iload_118: invokevirtual #4 // Method java/io/PrintStream.println:(I)V21: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;24: iload_225: invokevirtual #4 // Method java/io/PrintStream.println:(I)V28: returnLineNumberTable:line 9: 0line 10: 3line 11: 6line 13: 10line 14: 14line 15: 21line 16: 28LocalVariableTable:Start Length Slot Name Signature0 29 0 args [Ljava/lang/String;3 26 1 a I6 23 2 b I10 19 3 c I14 15 4 s Ljava/lang/String; } SourceFile: "PCRegisterTest.java"兩個常見問題
使用PC寄存器存儲字節(jié)碼指令地址有什么用呢?為什么使用PC寄存器記錄當前線程的執(zhí)行地址呢?這是兩個問題是一個問題
因為CPU需要不停的切換各個線程,這時候切換回來以后,就得知道接著從哪開始繼續(xù)執(zhí)行。
JVM的字節(jié)碼解釋器就需要通過改變Pc寄存器的值來明確下一條應(yīng)該執(zhí)行什么樣的字節(jié)碼指令。
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ù)器在各個線程之間互不影響。
CPU時間片
CPU時間片即 CPU分配給各個程序的時間,每個線程被分配一個時間段,稱作它的時間片。
在宏觀上:我們可以同時打開多個應(yīng)用程序,每個程序并行不悖,同時運行。
但在微觀上:由于只有一個CPU,一次只能處理程序要求的一部分,如何處理公平,一種方法就是引入時間片,每個程序輪流執(zhí)行。
虛擬機棧
一 虛擬機棧概述
虛擬機棧出現(xiàn)的背景
由于跨平臺性的設(shè)計,Java的指令都是根據(jù)棧來設(shè)計的。不同平臺CPU架構(gòu)不同,所以不能設(shè)計為基于寄存器的。
優(yōu)點是跨平臺,指令集小,編譯器容易實現(xiàn),缺點是性能下降,實現(xiàn)同樣的功能需要更多的指令。
內(nèi)存中的堆與棧
有不少Java開發(fā)人員一提到Java內(nèi)存結(jié)構(gòu),就會非常粗粒度地將JVM中的內(nèi)存區(qū)理解為僅有Java堆(heap)和Java棧(stack)?為什么?
棧是運行時的單位,而堆是存儲的單位。
即:棧解決程序的運行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù)。堆解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒。
虛擬機棧基本內(nèi)容
Java虛擬機棧是什么?
Java虛擬機棧(Java virtual Machine stack),早期也叫Java棧。每個線程在創(chuàng)建時都會創(chuàng)建一個虛擬機棧,其內(nèi)部保存一個個的棧幀(Stack Frame),對應(yīng)著一次次的Java方法調(diào)用。是線程私有的
生命周期
生命周期和線程一致。
作用
主管Java程序的運行,它保存方法的局部變量(8種基本數(shù)據(jù)類型,對象的引用地址)、部分結(jié)果,并參與方法的調(diào)用和返回。
局部變量 vs 成員變量(或?qū)傩?基本數(shù)據(jù)類型變量 vs 引用類型變量(類,數(shù)組,接口)棧的特點(優(yōu)點)
1.棧是一種快速有效的分配存儲方式,訪問速度僅次于程序計數(shù)器。
2.JVM直接對Java棧的操作只有兩個:
每個方法執(zhí)行,伴隨著進棧(入棧、壓棧)
執(zhí)行結(jié)束后的出棧工作
3.對于棧來說不存在垃圾回收問題,但是有OOM
GC(沒有垃圾回收) ; OOM(有棧溢出操作)
棧中可能出現(xiàn)的異常
面試題:開發(fā)中遇到的異常有哪些?
Java虛擬機規(guī)范允許Java棧的大小是動態(tài)的或者是固定不變的。
stackOverflowError異常(棧溢出異常)。
如果采用固定大小的Java虛擬機棧,那每一個線程的Java虛擬機棧容量可以在線程創(chuàng)建的時候獨立選定。如果線程請求分配的棧容量超過Java虛擬機棧允許的最大容量,Java虛擬機將會拋出一個stackOverflowError異常。
stackOverflowError異常(內(nèi)存不足異常)。
如果Java虛擬機棧可以動態(tài)擴展,并且在嘗試擴展的時候無法中請到足夠的內(nèi)存,或者在創(chuàng)建新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的虛擬機棧,那Java虛擬機將會拋出一個outofMemoryError異常。
演示stackOverflowError異常
設(shè)置棧內(nèi)存大小
我們可以使用參數(shù)-xss選項來設(shè)置線程的最大棧空間,棧的大小直接決定了函數(shù)調(diào)用的最大可達深度。
如何設(shè)置棧內(nèi)存大小官方文檔
https://docs.oracle.com/en/java/javase/11/tools/tools-and-command-reference.html
二 棧的存儲單位
棧中存儲什么?
每個線程都有自己的棧,棧中的數(shù)據(jù)都是以 棧幀(stackFrame)的格式存在。
在這個線程上正在執(zhí)行的==每個方法都各自對應(yīng)一個棧幀(stack Frame) ==。
棧幀是一個內(nèi)存區(qū)塊,是一個數(shù)據(jù)集,維系著方法執(zhí)行過程中的各種數(shù)據(jù)信息。
OOP基本概念:類 對象
類中基本結(jié)構(gòu):field(屬性,字段,域),method
棧運行原理
JVM直接對Java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循“先進后出”/“后進先出”原則。在一條活動線程中,一個時間點上,只會有一個活動的棧幀。即只有當前正在執(zhí)行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為當前棧幀(current Frame),與當前棧幀相對應(yīng)的方法就是當前方法(CurrentMethod),定義這個方法的類就是當前類(current class) 。執(zhí)行引擎運行的所有字節(jié)碼指令只針對當前棧幀進行操作。如果在該方法中調(diào)用了其他方法,對應(yīng)的新的棧幀會被創(chuàng)建出來,放在棧的頂端,成為新的當前幀。
代碼解釋
package com.fs.demo;public class StackFrameTest {public static void main(String[] args) {StackFrameTest stackFrameTest = new StackFrameTest();stackFrameTest.methodOne();}public void methodOne(){System.out.println("methodOne開始執(zhí)行---為當前棧幀");methodTwo();System.out.println("methodOne執(zhí)行結(jié)束===出棧");}public int methodTwo(){System.out.println("methodTwo開始執(zhí)行---為當前棧幀");int i = 10;double v = methodThree();System.out.println("methodTwo即將結(jié)束===出棧");return i+(int)v;}public double methodThree(){System.out.println("methodThree開始執(zhí)行---為當前棧幀");double d = 20.0D;System.out.println("methodThree即將結(jié)束===出棧");return d;} }不同線程中所包含的棧幀是不允許存在相互引用的,即不可能在一個棧幀之中引用另外一個線程的棧幀。
如果當前方法調(diào)用了其他方法,方法返回之際,當前棧幀會傳回此方法的執(zhí)行結(jié)果給前一個棧幀,接著,虛擬機會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀。
Java方法有兩種返回函數(shù)的方式,一種是正常的函數(shù)返回,使用return指令;另外一種是拋出異常。不管使用哪種方式,都會導(dǎo)致棧幀被彈出。
方法結(jié)束分為兩種:一 正常結(jié)束:以return為代表二 方法執(zhí)行中出現(xiàn)未捕獲的處理的異常,以拋出異常的方式結(jié)束反編譯上面代碼一下
E:\IdeaProjects\JavaEE_WEB\jvmStudy\target\classes\com\fs\demo>javap -v StackFrameTest.class Classfile /E:/IdeaProjects/JavaEE_WEB/jvmStudy/target/classes/com/fs/demo/StackFrameTest.classLast modified 2021-5-13; size 1259 bytesMD5 checksum 118846304ce6aeb0cb2621f8ea49b081Compiled from "StackFrameTest.java" public class com.fs.demo.StackFrameTestminor version: 0major version: 49flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #17.#42 // java/lang/Object."<init>":()V#2 = Class #43 // com/fs/demo/StackFrameTest#3 = Methodref #2.#42 // com/fs/demo/StackFrameTest."<init>":()V#4 = Methodref #2.#44 // com/fs/demo/StackFrameTest.methodOne:()V#5 = Fieldref #45.#46 // java/lang/System.out:Ljava/io/PrintStream;#6 = String #47 // methodOne開始執(zhí)行---為當前棧幀#7 = Methodref #48.#49 // java/io/PrintStream.println:(Ljava/lang/String;)V#8 = Methodref #2.#50 // com/fs/demo/StackFrameTest.methodTwo:()I#9 = String #51 // methodOne執(zhí)行結(jié)束===出棧#10 = String #52 // methodTwo開始執(zhí)行---為當前棧幀#11 = Methodref #2.#53 // com/fs/demo/StackFrameTest.methodThree:()D#12 = String #54 // methodTwo即將結(jié)束===出棧#13 = String #55 // methodThree開始執(zhí)行---為當前棧幀#14 = Double 20.0d#16 = String #56 // methodThree即將結(jié)束===出棧#17 = Class #57 // java/lang/Object#18 = Utf8 <init>#19 = Utf8 ()V#20 = Utf8 Code#21 = Utf8 LineNumberTable#22 = Utf8 LocalVariableTable#23 = Utf8 this#24 = Utf8 Lcom/fs/demo/StackFrameTest;#25 = Utf8 main#26 = Utf8 ([Ljava/lang/String;)V#27 = Utf8 args#28 = Utf8 [Ljava/lang/String;#29 = Utf8 stackFrameTest#30 = Utf8 methodOne#31 = Utf8 methodTwo#32 = Utf8 ()I#33 = Utf8 i#34 = Utf8 I#35 = Utf8 v#36 = Utf8 D#37 = Utf8 methodThree#38 = Utf8 ()D#39 = Utf8 d#40 = Utf8 SourceFile#41 = Utf8 StackFrameTest.java#42 = NameAndType #18:#19 // "<init>":()V#43 = Utf8 com/fs/demo/StackFrameTest#44 = NameAndType #30:#19 // methodOne:()V#45 = Class #58 // java/lang/System#46 = NameAndType #59:#60 // out:Ljava/io/PrintStream;#47 = Utf8 methodOne開始執(zhí)行---為當前棧幀#48 = Class #61 // java/io/PrintStream#49 = NameAndType #62:#63 // println:(Ljava/lang/String;)V#50 = NameAndType #31:#32 // methodTwo:()I#51 = Utf8 methodOne執(zhí)行結(jié)束===出棧#52 = Utf8 methodTwo開始執(zhí)行---為當前棧幀#53 = NameAndType #37:#38 // methodThree:()D#54 = Utf8 methodTwo即將結(jié)束===出棧#55 = Utf8 methodThree開始執(zhí)行---為當前棧幀#56 = Utf8 methodThree即將結(jié)束===出棧#57 = Utf8 java/lang/Object#58 = Utf8 java/lang/System#59 = Utf8 out#60 = Utf8 Ljava/io/PrintStream;#61 = Utf8 java/io/PrintStream#62 = Utf8 println#63 = Utf8 (Ljava/lang/String;)V {public com.fs.demo.StackFrameTest();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/fs/demo/StackFrameTest;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #2 // class com/fs/demo/StackFrameTest3: dup4: invokespecial #3 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #4 // Method methodOne:()V12: returnLineNumberTable:line 6: 0line 7: 8line 9: 12LocalVariableTable:Start Length Slot Name Signature0 13 0 args [Ljava/lang/String;8 5 1 stackFrameTest Lcom/fs/demo/StackFrameTest;public void methodOne();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #6 // String methodOne開始執(zhí)行---為當前棧幀5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: aload_09: invokevirtual #8 // Method methodTwo:()I12: pop13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;16: ldc #9 // String methodOne執(zhí)行結(jié)束===出棧18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V21: return // 這里說明方法沒有返回值,實際也是有return的LineNumberTable:line 12: 0line 13: 8line 14: 13line 15: 21LocalVariableTable:Start Length Slot Name Signature0 22 0 this Lcom/fs/demo/StackFrameTest;public int methodTwo();descriptor: ()Iflags: ACC_PUBLICCode:stack=3, locals=4, args_size=10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #10 // String methodTwo開始執(zhí)行---為當前棧幀5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: bipush 1010: istore_111: aload_012: invokevirtual #11 // Method methodThree:()D15: dstore_216: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;19: ldc #12 // String methodTwo即將結(jié)束===出棧21: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V24: iload_125: dload_226: d2i27: iadd28: ireturn //這里說明這個方法返回是int類型的就為 ireturnLineNumberTable:line 18: 0line 19: 8line 20: 11line 21: 16line 22: 24LocalVariableTable:Start Length Slot Name Signature0 29 0 this Lcom/fs/demo/StackFrameTest;11 18 1 i I16 13 2 v Dpublic double methodThree();descriptor: ()Dflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #13 // String methodThree開始執(zhí)行---為當前棧幀5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: ldc2_w #14 // double 20.0d11: dstore_112: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;15: ldc #16 // String methodThree即將結(jié)束===出棧17: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V20: dload_121: dreturn //這里說明返回的是double類型的就為 dreturnLineNumberTable:line 26: 0line 27: 8line 28: 12line 29: 20LocalVariableTable:Start Length Slot Name Signature0 22 0 this Lcom/fs/demo/StackFrameTest;12 10 1 d D } SourceFile: "StackFrameTest.java"棧幀的內(nèi)部結(jié)構(gòu)
每個棧幀中存儲著:
1 局部變量表(Local variables)
2 操作數(shù)棧(operand stack)(或表達式棧)
3 動態(tài)鏈接(Dynamic Linking)(或指向運行時常量池的方法引用)
4 方法返回地址(Return Address)(或方法正常退出或者異常退出的定義)
5 一些附加信息
三 局部變量表Local Variables
局部變量表也被稱之為局部變量數(shù)組或本地變量表
定義為一個數(shù)字數(shù)組,主要用于存儲方法參數(shù)和定義在方法體內(nèi)的局部變量,這些數(shù)據(jù)類型包括各類基本數(shù)據(jù)類型、對象引用(reference) ,以及returnAddress類型。
由于局部變量表是建立在線程的棧上,是線程的私有數(shù)據(jù),因此不存在數(shù)據(jù)安全問題
局部變量表所需的容量大小是在編譯期確定下來的,并保存在方法的Code屬性的maximum local variables數(shù)據(jù)項中。在方法運行期間是不會改變局部變量表的大小的。
方法嵌套調(diào)用的次數(shù)由棧的大小決定。一般來說==,棧越大,方法嵌套調(diào)用次數(shù)越多==。對一個函數(shù)而言,它的參數(shù)和局部變量越多,使得局部變量表膨脹,它的棧幀就越大,以滿足方法調(diào)用所需傳遞的信息增大的需求。進而函數(shù)調(diào)用就會占用更多的棧空間,導(dǎo)致其嵌套調(diào)用次數(shù)就會減少。
局部變量表中的變量只在當前方法調(diào)用中有效。在方法執(zhí)行時,虛擬機通過使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程。當方法調(diào)用結(jié)束后,隨著方法棧幀的銷毀,局部變量表也會隨之銷毀。
字節(jié)碼中方法內(nèi)部結(jié)構(gòu)的剖析
方法
代碼
行號
局部變量
變量槽slot的理解
參數(shù)值的存放總是在局部變量數(shù)組的index0開始,到數(shù)組長度-1的索引結(jié)束。
局部變量表,最基本的存儲單元是slot(變量槽)
局部變量表中存放編譯期可知的各種基本數(shù)據(jù)類型(8種),引用類型(reference),returnAddress類型的變量。
在局部變量表里,32位以內(nèi)的類型只占用一個slot(包括returnAddress類型),64位的類型(long和double)占用兩個slot。
byte 、 short . char在存儲前被轉(zhuǎn)換為int,boolean也被轉(zhuǎn)換為int,0表示false ,非0表示true。long和double 則占據(jù)兩個slot。JVM會為局部變量表中的每一個slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變量表中指定的局部變量值
當一個實例方法被調(diào)用的時候,它的方法參數(shù)和方法體內(nèi)部定義的局部變量將會按照順序被復(fù)制到局部變量表中的每一個slot上
如果需要訪問局部變量表中一個64bit的局部變量值時,只需要使用前一個索引即可。(比如:訪問long或double類型變量)
如果當前幀是由構(gòu)造方法或者實例方法創(chuàng)建的,那么該對象引用this將會存放在index為0的slot處,其余的參數(shù)按照參數(shù)表順序繼續(xù)排列。
由上面概念得到
由上面:如果當前幀是由構(gòu)造方法或者實例方法創(chuàng)建的,那么該對象引用this將會存放在index為0的slot處,其余的參數(shù)按照參數(shù)表順序繼續(xù)排列。得到靜態(tài)方法是不會將this放在局部變量表中的0索引處,所以在靜態(tài)方法中就用不到this
所以因為構(gòu)造方法與實例方法局部變量表中0索引處有this,就能引用
關(guān)于slot的使用與理解(案列)
Slot的重復(fù)利用
棧幀中的局部變量表中的槽位是可以重用的,如果一個局部變量過了其作用域,那么在其作用域之后申明的新的局部變量就很有可能會復(fù)用過期局部變量的槽位,從而達到節(jié)省資源的目的。
靜態(tài)變量與局部變量的對比
參數(shù)表分配完畢之后,再根據(jù)方法體內(nèi)定義的變量的順序和作用域分配。
我們知道類變量表有兩次初始化的機會,第一次是在“準備階段”,執(zhí)行系統(tǒng)初始化,對類變量設(shè)置零值,另一次則是在“初始化”階段,賦予程序員在代碼中定義的初始值。
和類變量初始化不同的是,局部變量表不存在系統(tǒng)初始化的過程,這意味著一旦定義了局部變量則必須人為的初始化,否則無法使用。
這樣的代碼是錯誤的,沒有賦值不能夠使用。所以局部變量必須顯示賦值,否者編譯不通過
補充說明
在棧幀中,與性能調(diào)優(yōu)關(guān)系最為密切的部分就是前面提到的局部變量表。在方法執(zhí)行時,虛擬機使用局部變量表完成方法的傳遞。
局部變量表中的變量也是重要的垃圾回收根節(jié)點,只要被局部變量表中直接或間接引用的對象都不會被回收。
四 操作數(shù)棧 Operand Stack
概念
棧:可以使用數(shù)組或者鏈表實現(xiàn)
每一個獨立的棧幀中除了包含局部變量表以外,還包含一個后進先出(Last-In-First-out)的操作數(shù)棧,也可以稱之為表達式棧(Expression stack) 。
操作數(shù)棧,在方法執(zhí)行過程中,根據(jù)字節(jié)碼指令,往棧中寫入數(shù)據(jù)或提取數(shù)據(jù),即入棧(push)/出棧(pop).
某些字節(jié)碼指令將值壓入操作數(shù)棧,其余的字節(jié)碼指令將操作數(shù)取出棧。使用它們后再把結(jié)果壓入棧。
比如:執(zhí)行復(fù)制、交換、求和等操作
操作數(shù)棧,主要用于保存計算過程的中間結(jié)果,同時作為計算過程中變量臨時的存儲空間。
操作數(shù)棧就是JVM執(zhí)行引擎的一個工作區(qū),當一個方法剛開始執(zhí)行的時候,一個新的棧幀也會隨之被創(chuàng)建出來,這個方法的操作數(shù)棧是空的。
每一個操作數(shù)棧都會擁有一個明確的棧深度用于存儲數(shù)值,其所需的最大深度在編譯期就定義好了,保存在方法的code屬性中,為max_stack的值。
棧中的任何一個元素都是可以任意的Java數(shù)據(jù)類型。
32bit的類型占用一個棧單位深度64bit的類型占用兩個棧單位深度操作數(shù)棧并非采用訪問索引的方式來進行數(shù)據(jù)訪問的,而是只能通過標準的入棧(push)和出棧(pop)操作來完成一次數(shù)據(jù)訪問。
如果被調(diào)用的方法帶有返回值的話,其返回值將會被壓入當前棧幀的操作數(shù)棧中,并更新Pc寄存器中下一條需要執(zhí)行的字節(jié)碼指令。
操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴格匹配,這由編譯器在編譯器期間進行驗證,同時在類加載過程中的類檢驗階段的數(shù)據(jù)流分析階段要再次驗證。
另外,我們說Java虛擬機的解釋引擎是基于棧的執(zhí)行引擎,其中的棧指的就是操作數(shù)棧。
五 代碼追蹤—操作數(shù)棧
圖解字節(jié)碼指令(上面的代碼案列)
最后return結(jié)束
局部變量表值
面試題: i++ ++i的區(qū)別—>放到字節(jié)碼知識點的時候說明
//面試題: i++ ++i的區(qū)別public void add(){//第一類問題int i1 = 10;i1++;int i2 = 10;++i2;//第二類問題int i3 = 10;int i4 = i3++;int i5 = 10;int i6 = ++i5;//第三類問題int i7 = 10;i7 = i7++;int i8 = 10;i8 = ++i8;//第四類問題int i9 = 10;int i10 = i9++ + ++i9;}六 棧項緩存技術(shù)
前面提過,基于棧式架構(gòu)的虛擬機所使用的零地址指令更加緊湊,但完成一項操作的時候必然需要使用更多的入棧和出棧指令,這同時也就意味著將需要更多的指令分派(instruction dispatch)次數(shù)和內(nèi)存讀/寫次數(shù)。
由于操作數(shù)是存儲在內(nèi)存中的,因此頻繁地執(zhí)行內(nèi)存讀/寫操作必然會影響執(zhí)行速度。為了解決這個問題,HotSpot JVM的設(shè)計者們提出了棧頂緩存(Tos,Top-of-stack Cashing)技術(shù),將棧頂元素全部緩存在物理cPu的寄存器中,以此降低對內(nèi)存的讀/寫次數(shù),提升執(zhí)行引擎的執(zhí)行效率。
七 動態(tài)鏈接(或指向運行時常量池的方法引用)
每一個棧幀內(nèi)部都包含一個指向運行時常量池中該棧幀所屬方法的引用。包含這個引用的目的就是為了支持當前方法的代碼能夠?qū)崿F(xiàn)動態(tài)鏈接( Dynamic Linking)。比如: invokedynamic指令
在Java源文件被編譯到字節(jié)碼文件中時,所有的變量和方法引用都作為符號引用( symbolic Reference)保存在class文件的常量池里。比如:描述一個方法調(diào)用了另外的其他方法時,就是通過常量池中指向方法的符號引用來表示的,那么動態(tài)鏈接的作用就是為了將這些符號引用轉(zhuǎn)換為調(diào)用方法的直接引用。
為什么需要常量池呢?
常量池的作用,就是為了提供一些符號和常量,便于指令的識別。
八 方法的調(diào)用:解析與分派
靜態(tài)鏈接 動態(tài)鏈接
在JVM中,將符號引用轉(zhuǎn)換為調(diào)用方法的直接引用與方法的綁定機制相關(guān)。
靜態(tài)鏈接:
當一個字節(jié)碼文件被裝載進JVM內(nèi)部時,如果被調(diào)用的目標方法在編譯期可知,且運行期保持不變時。這種情況下將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用的過程稱之為靜態(tài)鏈接。
動態(tài)鏈接:
如果被調(diào)用的方法在編譯期無法被確定下來,也就是說,只能夠在程序運行期將調(diào)用方法的符號引用轉(zhuǎn)換為直接引用,由于這種引用轉(zhuǎn)換過程具備動態(tài)性,因此也就被稱之為動態(tài)鏈接。
對應(yīng)的方法的綁定機制為:早期綁定(Early Binding)和晚期綁定(Late Binding)。綁定是一個字段、方法或者類在符號引用被替換為直接引用的過程,這僅僅發(fā)生一次。
早期綁定 晚期綁定
早期綁定:
早期綁定就是指被調(diào)用的目標方法如果在編譯期可知,且運行期保持不變時,即可將這個方法與所屬的類型進行綁定,這樣一來,由于明確了被調(diào)用的目標方法究竟是哪一個,因此也就可以使用靜態(tài)鏈接的方式將符號引用轉(zhuǎn)換為直接引用。
晚期綁定:
如果被調(diào)用的方法在編譯期無法被確定下來,只能夠在程序運行期根據(jù)實際的類型綁定相關(guān)的方法,這種綁定方式也就被稱之為晚期綁定。
隨著高級語言的橫空出世,類似于Java一樣的基于面向?qū)ο蟮木幊陶Z言如今越來越多,盡管這類編程語言在語法風格上存在一定的差別,但是它們彼此之間始終保持著一個共性,那就是都支持封裝、繼承和多態(tài)等面向?qū)ο筇匦?#xff0c;既然這一類的編程語言具備多態(tài)特性,那么自然也就具備早期綁定和晚期綁定兩種綁定方式。
Java中任何一個普通的方法其實都具備虛函數(shù)的特征,它們相當于c++語言中的虛函數(shù)(C++中則需要使用關(guān)鍵字virtual來顯式定義)。如果在Java程序中不希望某個方法擁有虛函數(shù)的特征時,則可以使用關(guān)鍵字final來標記這個方法。
虛方法 非虛方法:
非虛方法:
如果方法在編譯期就確定了具體的調(diào)用版木,這個版本在運行時是不可變的這樣的方法稱為非虛方法。
靜態(tài)方法、私有方法、final方法、實例構(gòu)造器、父類方法都是非虛方法。
虛方法
其他方法稱為虛方法。
虛擬機中提供了以下幾條方法調(diào)用指令:
·普通調(diào)用指令:
·動態(tài)調(diào)用指令:
前四條指令固化在虛擬機內(nèi)部,方法的調(diào)用執(zhí)行不可人為干預(yù),而invokedynamic指令則支持由用戶確定方法版本。其中invokestatic指令和invokespecial指令調(diào)用的方法稱為非虛方法,其余的(final修飾的除外)稱為虛方法。
代碼
package com.fs.demo;public class Son extends Father{public Son(){super();}public Son(int age){this();}//不是重寫的父類方法,因為靜態(tài)方法不能被重寫public static void showStatic(String s){System.out.println("Son "+s);}private void showPrivate(String s){System.out.println("son private "+s);}public void show(){showStatic("小付");super.showStatic("也要");showPrivate("加油鴨");super.showCommon();showFinal();//因為此方法申明final ,不能被子類重寫,所以雖然是invokevirtual ,也認為此方法是非虛方法super.showFinal();//但是被super指定后,就明確調(diào)用父類,就是invokespecial ,所以也是非虛方法showCommon();MethodInterface methodInterface = null;methodInterface.methodA();//invokeinterface }//重寫父類的public void showCommon(){System.out.println("Son 普通方法");}}class Father{public Father(){System.out.println("Father的構(gòu)造方法");}public static void showStatic(String s){System.out.println("father "+s);}public final void showFinal(){System.out.println("final show final");}public void showCommon(){System.out.println("father 普通方法");} }interface MethodInterface{void methodA(); }javav -p Son.class 就可以看到方法是虛方法還是非虛方法
E:\IdeaProjects\JavaEE_WEB\jvmStudy\target\classes\com\fs\demo>javap -v Son.class Classfile /E:/IdeaProjects/JavaEE_WEB/jvmStudy/target/classes/com/fs/demo/Son.classLast modified 2021-5-24; size 1349 bytesMD5 checksum 7d67be0d875a9f75a2f1496c80db9f13Compiled from "Son.java" public class com.fs.demo.Son extends com.fs.demo.Fatherminor version: 0major version: 49flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #23.#45 // com/fs/demo/Father."<init>":()V#2 = Methodref #22.#45 // com/fs/demo/Son."<init>":()V#3 = Fieldref #46.#47 // java/lang/System.out:Ljava/io/PrintStream;#4 = Class #48 // java/lang/StringBuilder#5 = Methodref #4.#45 // java/lang/StringBuilder."<init>":()V#6 = String #49 // Son#7 = Methodref #4.#50 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#8 = Methodref #4.#51 // java/lang/StringBuilder.toString:()Ljava/lang/String;#9 = Methodref #52.#53 // java/io/PrintStream.println:(Ljava/lang/String;)V#10 = String #54 // son private#11 = String #55 // 小付#12 = Methodref #22.#56 // com/fs/demo/Son.showStatic:(Ljava/lang/String;)V#13 = String #57 // 也要#14 = Methodref #23.#56 // com/fs/demo/Father.showStatic:(Ljava/lang/String;)V#15 = String #58 // 加油鴨#16 = Methodref #22.#59 // com/fs/demo/Son.showPrivate:(Ljava/lang/String;)V#17 = Methodref #23.#60 // com/fs/demo/Father.showCommon:()V#18 = Methodref #22.#61 // com/fs/demo/Son.showFinal:()V#19 = Methodref #22.#60 // com/fs/demo/Son.showCommon:()V#20 = InterfaceMethodref #62.#63 // com/fs/demo/MethodInterface.methodA:()V#21 = String #64 // Son 普通方法#22 = Class #65 // com/fs/demo/Son#23 = Class #66 // com/fs/demo/Father#24 = Utf8 <init>#25 = Utf8 ()V#26 = Utf8 Code#27 = Utf8 LineNumberTable#28 = Utf8 LocalVariableTable#29 = Utf8 this#30 = Utf8 Lcom/fs/demo/Son;#31 = Utf8 (I)V#32 = Utf8 age#33 = Utf8 I#34 = Utf8 showStatic#35 = Utf8 (Ljava/lang/String;)V#36 = Utf8 s#37 = Utf8 Ljava/lang/String;#38 = Utf8 showPrivate#39 = Utf8 show#40 = Utf8 methodInterface#41 = Utf8 Lcom/fs/demo/MethodInterface;#42 = Utf8 showCommon#43 = Utf8 SourceFile#44 = Utf8 Son.java#45 = NameAndType #24:#25 // "<init>":()V#46 = Class #67 // java/lang/System#47 = NameAndType #68:#69 // out:Ljava/io/PrintStream;#48 = Utf8 java/lang/StringBuilder#49 = Utf8 Son#50 = NameAndType #70:#71 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#51 = NameAndType #72:#73 // toString:()Ljava/lang/String;#52 = Class #74 // java/io/PrintStream#53 = NameAndType #75:#35 // println:(Ljava/lang/String;)V#54 = Utf8 son private#55 = Utf8 小付#56 = NameAndType #34:#35 // showStatic:(Ljava/lang/String;)V#57 = Utf8 也要#58 = Utf8 加油鴨#59 = NameAndType #38:#35 // showPrivate:(Ljava/lang/String;)V#60 = NameAndType #42:#25 // showCommon:()V#61 = NameAndType #76:#25 // showFinal:()V#62 = Class #77 // com/fs/demo/MethodInterface#63 = NameAndType #78:#25 // methodA:()V#64 = Utf8 Son 普通方法#65 = Utf8 com/fs/demo/Son#66 = Utf8 com/fs/demo/Father#67 = Utf8 java/lang/System#68 = Utf8 out#69 = Utf8 Ljava/io/PrintStream;#70 = Utf8 append#71 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;#72 = Utf8 toString#73 = Utf8 ()Ljava/lang/String;#74 = Utf8 java/io/PrintStream#75 = Utf8 println#76 = Utf8 showFinal#77 = Utf8 com/fs/demo/MethodInterface#78 = Utf8 methodA {public com.fs.demo.Son();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method com/fs/demo/Father."<init>":()V4: returnLineNumberTable:line 6: 0line 7: 4LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/fs/demo/Son;public com.fs.demo.Son(int);descriptor: (I)Vflags: ACC_PUBLICCode:stack=1, locals=2, args_size=20: aload_01: invokespecial #2 // Method "<init>":()V4: returnLineNumberTable:line 10: 0line 11: 4LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/fs/demo/Son;0 5 1 age Ipublic static void showStatic(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=1, args_size=10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;3: new #4 // class java/lang/StringBuilder6: dup7: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V10: ldc #6 // String Son12: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;15: aload_016: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;19: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;22: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V25: returnLineNumberTable:line 15: 0line 16: 25LocalVariableTable:Start Length Slot Name Signature0 26 0 s Ljava/lang/String;public void show();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=10: ldc #11 // String 小付2: invokestatic #12 // Method showStatic:(Ljava/lang/String;)V5: ldc #13 // String 也要7: invokestatic #14 // Method com/fs/demo/Father.showStatic:(Ljava/lang/String;)V10: aload_011: ldc #15 // String 加油鴨13: invokespecial #16 // Method showPrivate:(Ljava/lang/String;)V16: aload_017: invokespecial #17 // Method com/fs/demo/Father.showCommon:()V20: aload_021: invokevirtual #18 // Method showFinal:()V24: aload_025: invokevirtual #19 // Method showCommon:()V28: aconst_null29: astore_130: aload_131: invokeinterface #20, 1 // InterfaceMethod com/fs/demo/MethodInterface.methodA:()V36: returnLineNumberTable:line 24: 0line 25: 5line 26: 10line 29: 16line 30: 20line 31: 24line 33: 28line 34: 30line 36: 36LocalVariableTable:Start Length Slot Name Signature0 37 0 this Lcom/fs/demo/Son;30 7 1 methodInterface Lcom/fs/demo/MethodInterface;public void showCommon();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #21 // String Son 普通方法5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 40: 0line 41: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 this Lcom/fs/demo/Son; } SourceFile: "Son.java"invokedynamic指令
JVM字節(jié)碼指令集一直比較穩(wěn)定,一直到Java7中才增加了一個invokedynamic指令,這是Java為了實現(xiàn)「動態(tài)類型語言』支持而做的一種改進。
但是在Java7中并沒有提供直接生成invokedynamic指令的方法,需要借助ASM這種底層字節(jié)碼工具來產(chǎn)生invokedynamic指令。直到Java8的Lambda表達式的出現(xiàn),invokedynamic指令的生成,在Java中才有了直接的生成方式。
Java7中增加的動態(tài)語言類型支持的本質(zhì)是對Java虛擬機規(guī)范的修改,而不是對Java語言規(guī)則的修改,這一塊相對來講比較復(fù)雜,增加了虛擬機中的方法調(diào)用,最直接的受益者就是運行在Java平臺的動態(tài)語言的編譯器。
動態(tài)類型語言和靜態(tài)類型語言(java是靜態(tài)類型語音)
動態(tài)類型語言和靜態(tài)類型語言兩者的區(qū)別就在于對類型的檢查是在編譯期還是在運行期,滿足前者就是靜態(tài)類型語言,反之是動態(tài)類型語言。
說的再直白一點就是,靜態(tài)類型語言是判斷變量自身的類型信息;動態(tài)類型語言是判斷變量值的類型信息,變量沒有類型信息,變量值才有類型信息,這是動態(tài)語言的一個重要特征。
方法重寫的本質(zhì)
Java語言中方法重寫的本質(zhì):
1.找到操作數(shù)棧頂?shù)牡谝粋€元素所執(zhí)行的對象的實際類型,記作 C。
2.如果在類型c中找到與常量中的描述符合簡單名稱都相符的方法,則進行訪問權(quán)限校驗,如果通過則返回這個方法的直接引用,查找過程結(jié)束;如果不通過,則返回java.lang.IllegalAccessError異常。
3.否則,按照繼承關(guān)系從下往上依次對c的各個父類進行第⒉步的搜索和驗證過程。
4.如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。
illegalAccessError介紹:
程序試圖訪問或修改一個屬性或調(diào)用一個方法,這個屬性或方法,你沒有權(quán)限訪問。一般的,這個會引起編譯器異常。這個錯誤如果發(fā)生在運行時,就說明一個類發(fā)生了不兼容的改變。
虛方法表
在面向?qū)ο蟮木幊讨?#xff0c;會很頻繁的使用到動態(tài)分派,如果在每次動態(tài)分派的過程中都要重新在類的方法元數(shù)據(jù)中搜索合適的目標的話就可能影響到執(zhí)行效率。因此,為了提高性能,JVM采用在類的方法區(qū)建立一個虛方法表(virtual method table)(非虛方法不會出現(xiàn)在表中)來實現(xiàn)。使用索引表來代替查找。
每個類中都有一個虛方法表,表中存放著各個方法的實際入口。
那么虛方法表什么時候被創(chuàng)建?
虛方法表會在類加載的鏈接階段被創(chuàng)建并開始初始化,類的變量初始值準備完成之后,JVM會把該類的方法表也初始化完畢。
九 方法返回地址
存放調(diào)用該方法的pc寄存器的值。
一個方法的結(jié)束,有兩種方式:
正常執(zhí)行完成出現(xiàn)未處理的異常,非正常退出無論通過哪種方式退出,在方法退出后都返回到該方法被調(diào)用的位置。方法正常退出時,調(diào)用者的pc計數(shù)器的值作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。而通過異常退出的,返回地址是要通過異常表來確定,棧幀中一般不會保存這部分信息。
當一個方法開始執(zhí)行后,只有兩種方式可以退出這個方法:
1、執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令(return),會有返回值傳遞給上層的方法調(diào)用者,簡稱正常完成出口;
一個方法在正常調(diào)用完成之后究竟需要使用哪一個返回指令還需要根據(jù)方法返回值的實際數(shù)據(jù)類型而定。
在字節(jié)碼指令中,返回指令包含ireturn(當返回值是boolean.byte、char.short和int類型時使用)、lreturn、freturn、dreturn以及areturn,另外還有一個return指令供聲明為void的方法、實例初始化方法、類和接口的初始化方法使用。
2、在方法執(zhí)行的過程中遇到了異常(Exception),并且這個異常沒有在方法內(nèi)進行處理,也就是只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導(dǎo)致方法退出。簡稱異常完成出口。
方法執(zhí)行過程中拋出異常時的異常處理,存儲在一個異常處理表,方便在發(fā)生異常的時候找到處理異常的代碼。
本質(zhì)上,方法的退出就是當前棧幀出棧的過程。此時,需要恢復(fù)上層方法的局部變量表、操作數(shù)棧、將返回值壓入調(diào)用者棧幀的操作數(shù)棧、設(shè)置PC寄存器值等,讓調(diào)用者方法繼續(xù)執(zhí)行下去。
正常完成出口和異常完成出口的區(qū)別在于:通過異常完成出口退出的不會給他的上層調(diào)用者產(chǎn)生任何的返回值。
十 一些附加信息
棧幀中還允許攜帶與Java虛擬機實現(xiàn)相關(guān)的一些附加信息。例如,對程序調(diào)試提供支持的信息。
十一 棧的相關(guān)面試題
本地方法(Native Method)
什么是本地方法?
簡單地講,一個Native Method就是一個Java調(diào)用非Java代碼的接口。一個Native Method是這樣一個Java方法:該方法的實現(xiàn)由非Java語言實現(xiàn),比如c。這個特征并非Java所特有,很多其它的編程語言都有這一機制,比如在c++中,你可以用extern "c"告知C++編譯器去調(diào)用一個c的函數(shù)。
"A native method is a Java method whose implementation isprovided by non-java code. "
在定義一個native method時,并不提供實現(xiàn)體(有些像定義一個Javainterface),因為其實現(xiàn)體是由非java語言在外面實現(xiàn)的。
本地接口的作用是融合不同的編程語言為Java所用,它的初衷是融合C/C++程序。
標識符native可以與所有其它的java標識符連用,但是abstract除外。
為什么要使用Native Method ?
Java使用起來非常方便,然而有些層次的任務(wù)用Java實現(xiàn)起來不容易,或者我們對程序的效率很在意時,問題就來了。
與Java環(huán)境外交互:
有時Java應(yīng)用需要與Java外面的環(huán)境交互,這是本地方法存在的主要原因。你可以想想Java需要與一些底層系統(tǒng),如操作系統(tǒng)或某些硬件交換信息時的情況。本地方法正是這樣一種交流機制:它為我們提供了一個非常簡潔的接口,而且我們無需去了解Java應(yīng)用之外的繁瑣的細節(jié)。
與操作系統(tǒng)交互:
JVM支持著Java語言本身和運行時庫,它是Java程序賴以生存的平臺,它由一個解釋器(解釋字節(jié)碼)和一些連接到本地代碼的庫組成。然而不管怎樣,它畢竟不是一個完整的系統(tǒng),它經(jīng)常依賴于一些底層系統(tǒng)的支持。這些底層系統(tǒng)常常是強大的操作系統(tǒng)。通過使用本地方法,我們得以用Java實現(xiàn)了jre的與底層系統(tǒng)的交互,甚至JVM的一些部分就是用c寫的。還有,如果我們要使用一些Java語言本身沒有提供封裝的操作系統(tǒng)的特性時,我們也要使用本地方法。
sun’s Java
sun的解釋器是用c實現(xiàn)的,這使得它能像一些普通的c一樣與外部交互。jre大部分是用Java實現(xiàn)的,它也通過一些本地方法與外界交互。例如:類java.lang. Threa的 setPriority()方法是用Java實現(xiàn)的,但是它實現(xiàn)調(diào)用的是該類里的本地方法setPriority0()。這個本地方法是用c實現(xiàn)的,并被植入JVM內(nèi)部,在windows 95的平臺上,這個本地方法最終將調(diào)用win32 setPriority() API。這是一個本地方法的具體實現(xiàn)由JVM直接提供,更多的情況是本地方法由外部的動態(tài)鏈接庫(external dynamic link library)提供,然后被JVM調(diào)用。
現(xiàn)狀
目前該方法使用的越來越少了,除非是與硬件有關(guān)的應(yīng)用,比如通過Java程序驅(qū)動打印機或者Java系統(tǒng)管理生產(chǎn)設(shè)備,在企業(yè)級應(yīng)用中已經(jīng)比較少見。因為現(xiàn)在的異構(gòu)領(lǐng)域間的通信很發(fā)達,比如可以使用socket通信,也可以使用web service等等,不多做介紹。
本地方法棧(Native Method stack)
概念
Java虛擬機棧用于管理Java方法的調(diào)用,而本地方法棧用于管理本地方法的調(diào)用。
本地方法棧,也是線程私有的。
允許被實現(xiàn)成固定或者是可動態(tài)擴展的內(nèi)存大小。(在內(nèi)存溢出方面是相同的)
如果線程請求分配的棧容量超過本地方法棧允許的最大容量,Java虛擬機將會拋出一個stackoverflowError異常。
如果本地方法棧可以動態(tài)擴展,并且在嘗試擴展的時候無法申請到足夠的內(nèi)存,或者在創(chuàng)建新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的本地方法棧,那么Java虛擬機將會拋出一個outofMemoryError異常。
本地方法是使用c語言實現(xiàn)的。
它的具體做法是Native Method stack中登記native方法,在Execution Engine執(zhí)行時加載本地方法庫。
當某個線程調(diào)用一個本地方法時,它就進入了一個全新的并且不再受虛擬機限制的世界。它和虛擬機擁有同樣的權(quán)限。
本地方法可以通過本地方法接口來訪問虛擬機內(nèi)部的運行時數(shù)據(jù)區(qū)。
它甚至可以直接使用本地處理器中的寄存器
直接從本地內(nèi)存的堆中分配任意數(shù)量的內(nèi)存。
并不是所有的JVM都支持本地方法。因為Java虛擬機規(guī)范并沒有明確要求本地方法棧的使用語言、具體實現(xiàn)方式、數(shù)據(jù)結(jié)構(gòu)等。如果JVM產(chǎn)品不打算支持native方法,也可以無需實現(xiàn)本地方法棧。
在Hotspot JVM中,直接將本地方法棧和虛擬機棧合二為一。
舉列子
總結(jié)
以上是生活随笔為你收集整理的JVM学习笔记之-运行时数据区概述及线程概述,程序计数器(PC寄存器),虚拟机栈(栈,局部变量表,操作数栈,动态连接,方法调用,方法返回地址等),本地方法接口,本地方法栈的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM学习笔记之-类加载子系统,类的加载
- 下一篇: JVM学习笔记之-堆,年轻代与老年代,对