2. JVM基本結(jié)構(gòu)

JVM啟動流程

JVM基本結(jié)構(gòu)

1. PC寄存器 or 程序計數(shù)器(Program Counter Register)

  • 是一塊較小的內(nèi)存空間,指向下一條指令的地址:分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基本功能都需要依賴它來完成
  • 執(zhí)行本地方法時,PC的值為空(undefined)
  • 每個線程擁有一個PC寄存器:在任何一個確定的時刻,一個處理器(內(nèi)核)都只會執(zhí)行一條線程中的指令
  • 在線程創(chuàng)建時創(chuàng)建

2. Java虛擬機棧(Java Virtual Machine Stacks)

  • 線程私有的(和PC寄存器一樣)
  • 每個Java方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame:方法運行時的基本數(shù)據(jù)結(jié)構(gòu)):存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法返回地址等信息
  • 每一個方法從調(diào)用直至執(zhí)行完成:對應(yīng)著一個棧幀在虛擬機中入棧到出棧的過程
  • 局部變量表(Local Variable Table):存放了編譯期可知的各種基本數(shù)據(jù)類型、對象引用和returnAddress類型(指向了一條字節(jié)碼指令的地址),即方法參數(shù)和方法內(nèi)部定義的局部變量
    • 64位長度的long和double數(shù)據(jù)會占用2個局部變量空間(slot),其余的數(shù)據(jù)類型只占用1個
    • 局部變量表所需的內(nèi)存空間在編譯期間完成分配:一個方法需要在幀中分配多大的局部變量空間是完全確定的,運行期間不會改變局部變量表的大小
    • 實例方法(非static的方法)的局部變量表中第0位索引的Slot默認(rèn)是對此方法所屬對象實例的引用,即,"this",余下的Slot則與static方法相同
  • 操作數(shù)棧(Operand Stack):Java沒有寄存器,所有參數(shù)傳遞使用操作數(shù)棧
    • 后入先出
    • 32位數(shù)據(jù)類型所占的棧容量為1,64位數(shù)據(jù)類型所占的棧容量為2
  • 棧上分配
    • 小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
    • 直接分配在棧上,可以自動回收,減輕GC壓力
    • 大對象或者逃逸對象無法棧上分配

3. Java堆(Java Heap)

  • 被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建:唯一目的就是存放對象實例
  • 是GC管理的主要區(qū)域
  • GC基本都采用分代收集算法Java堆可以細(xì)分為新生代和老年代;再細(xì)致一點的有Eden空間、From Survivor空間、To Survivor空間等
  • 可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可:主流的虛擬機都是按照大小可擴展來實現(xiàn)的

4. 方法區(qū)(Method Area)

  • 各個線程共享的內(nèi)存區(qū)域(與Java堆一樣):存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
  • 在HotSpot虛擬機上,很多人把方法區(qū)稱為“永久代”:兩者并不等價,只是因為使用了永久代來實現(xiàn)方法區(qū)--這樣更容易遇到內(nèi)存溢出問題,放棄中,JDK 1.7已經(jīng)把原本放在永久代的字符串常量池移出到堆中

5. 棧、堆、方法區(qū)交互

public class AppMain
//運行時, jvm把appmain的信息都放入方法區(qū)
{
    public static void main(String[] args) 
    //main 方法本身放入方法區(qū)。
    {
        Sample test1 = new Sample("測試1");
        //test1是引用,所以放到棧區(qū)里,Sample是自定義對象應(yīng)該放到堆里面
        Sample test2 = new Sample("測試2");
        test1.printName();
        test2.printName();
    }
    
    public class Sample
    //運行時, jvm把Sample的信息都放入方法區(qū)
    {
        private name;
        //new Sample實例后,name引用放入棧區(qū)里,name 對象放入堆里
        public Sample(String name)
        {
            this.name =name;
        }
        //print方法本身放入方法區(qū)里。
        public void printName()
        {
            System.out.println(name);
        }
    }
}

內(nèi)存模型

Java內(nèi)存模型的主要目標(biāo):定義程序中各個變量的訪問規(guī)則,即,在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)

  • 主內(nèi)存(Main Memory):存儲所有的變量
  • 每條線程還有自己的工作內(nèi)存(Working Memory):保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作<讀取、賦值等>都必須在工作內(nèi)存中進(jìn)行
  • 數(shù)據(jù)從主內(nèi)存復(fù)制到工作內(nèi)存:必須按順序執(zhí)行read和load
    1. 由主內(nèi)存執(zhí)行的讀(read)操作;
    2. 由工作內(nèi)存執(zhí)行的相應(yīng)的加載(load)操作
  • 數(shù)據(jù)從工作內(nèi)存拷貝到主內(nèi)存
    1. 由工作內(nèi)存執(zhí)行的存儲(store)操作;
    2. 由主內(nèi)存執(zhí)行的相應(yīng)的寫(write)操作
  • 每種操作都是原子的、不可再分的:lock unlock read load use assign store write
  • 執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:
    • 不允許一個變量從主內(nèi)存讀取了但工作內(nèi)存不接受;或者從工作內(nèi)存發(fā)起回寫了但主內(nèi)存不接受
    • 變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存
    • 沒有發(fā)生過assign操作時不允許把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存
    • 對一個變量實施use、store操作之前,必須先執(zhí)行過了assign和load操作
    • 一個變量在同一個時刻只允許一條線程對其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行湘通次數(shù)的unlock操作,變量才會被解鎖
    • 如果對一個變量執(zhí)行l(wèi)ock操作,那將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個變量前,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
    • 如果一個變量事先沒有被lock鎖定,那就不允許對它執(zhí)行unlock,也不允許去unlock一個被其它線程鎖定的變量
    • 對一個變量執(zhí)行unlock之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store、write操作)
  • volatile關(guān)鍵字:Java虛擬機提供的最輕量級的同步機制
    • 非正式但通俗易懂的作用介紹:當(dāng)一個變量定義為volatile后,它將具備兩種特性
      1. 保證此變量對所有線程的可見性(當(dāng)一條線程修改了這個變量的值,新值對于其它線程來說是可以立即得知的):volatile變量在各個線程的工作內(nèi)存中不存在一致性問題,但是Java里面的運算并非原子操作,導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的,只有在以下運算場景中才適合使用volatile變量,否則仍然要通過鎖(synchronized或java.util.concurrent中的原子類)來保證原子性
        • 運算結(jié)果并不依賴變量的當(dāng)前值
        • 變量不需要與其他的狀態(tài)變量共同參與不變約束
      2. 禁止指令重排
    • 選用volatile的意義--它能讓我們的代碼比使用其他的同步工具更快嗎?
      • 大多數(shù)場景下volatile的總開銷比鎖要低
      • 在volatile與鎖之中選擇的唯一依據(jù)是volatile的語義能否滿足使用場景的要求
  • 原子性、可見性與有序性:Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這3個特征來建立的
    • 原子性(Atomicity)
      • 基本數(shù)據(jù)類型的訪問讀寫是具備原子性的
      • 在synchronized塊之間的操作也具備原子性
    • 可見性(Visibility):當(dāng)一個線程修改了共享變量的值,其他線程能夠立即得知這個修改
      • volatile
      • synchronized:對一個變量執(zhí)行unlock前,必須先把此變量同步回主內(nèi)存中
      • final:一旦初始化完成,其他線程就可見
    • 有序性(Ordering)在本線程內(nèi),操作都是有序的;在線程外觀察,操作都是無序的:“指令重排”或“工作內(nèi)存與主內(nèi)存同步延遲”)
      • volatile
      • synchronized:一個變量在同一個時刻只允許一條線程對其進(jìn)行l(wèi)ock--持有同一個鎖的兩個同步塊只能串行的進(jìn)入
  • 先行發(fā)生原則(happens-before):判斷數(shù)據(jù)是否存在競爭、線程是否安全的主要依據(jù)
    • 程序順序規(guī)則(Program Order Rule):一個線程內(nèi)保證語義的串行性
    • 鎖規(guī)則(Monitor Lock Rule):解鎖(unlock)必然發(fā)生在隨后的對同一個鎖的加鎖(lock)前
    • volatile規(guī)則(Volatile Variable Rule):對一個volatile變量的寫,先發(fā)生于讀
    • 線程啟動規(guī)則(Thread Start Rule):線程的start方法先于它的每一個動作
    • 線程終止規(guī)則(Thread Termination Rule):線程的所有操作先于線程的終結(jié)(Thread.join())
    • 線程中斷規(guī)則(Thread Interruption Rule):線程的中斷(interrupt())先于被中斷線程的代碼檢測到中斷事件的發(fā)生
    • 對象終結(jié)規(guī)則(Finalizer Rule):對象的構(gòu)造函數(shù)執(zhí)行結(jié)束先于finalize()方法
    • 傳遞性(Transitivity):A先于B,B先于C 那么A必然先于C
    • 結(jié)論:一個操作“時間上的先發(fā)生”不代表這個操作會是“先行發(fā)生”;一個操作“先行發(fā)生”不能推導(dǎo)出這個操作必定是“時間上的先發(fā)生”

編譯和解釋運行的概念

  • 解釋運行
    • 解釋執(zhí)行以解釋方式運行字節(jié)碼
    • 解釋執(zhí)行的意思是:讀一句執(zhí)行一句
  • 編譯運行(JIT)
    • 將字節(jié)碼編譯成機器碼
    • 直接執(zhí)行機器碼
    • 運行時編譯
    • 編譯后性能有數(shù)量級的提升
  • 解釋器與編譯器兩者各有優(yōu)勢:
    • 當(dāng)程序需要迅速啟動和執(zhí)行的時候,解釋器可以首先發(fā)揮作用,省去編譯的時間,立即執(zhí)行
    • 當(dāng)程序運行后,隨著時間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,372評論 11 349
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,731評論 0 11
  • 1. 我今天脖子好疼呀,好像是落枕了。前幾天的時候可能是我在自習(xí)室門口的地方看書來著,著涼了。現(xiàn)在左邊脖子一動就會...
    小石頭333閱讀 124評論 0 0
  • 編碼問題需要設(shè)置成統(tǒng)一的編碼格式才行,命令行進(jìn)去MySQLmysql -u root -p輸入命令查看當(dāng)前數(shù)據(jù)庫的...
    itachi閱讀 217評論 0 0
  • 為什么淘寶客會那么火爆?我總結(jié)了以下3點: 1、淘客這個項目做起來比較簡單、上手快且收益可觀; 2、淘寶網(wǎng)目前正在...
    kimi小晨哥閱讀 1,097評論 0 0