亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入理解以DEBUG方式線程的底層運行原理

 更新時間:2021年06月21日 16:21:10   作者:飛天小牛肉  
說到線程的底層運行原理,想必各位也應該知道我們今天不可避免的要講到JVM 了。其實大家明白了Java的運行時數據區(qū)域,也就明白了線程的底層原理,今天帶著大家一步一步DEBUG,來看看線程到底是怎么運行的,順便把IDEA的DEBUG方法簡單講一下

一、Java 運行時數據區(qū)域

友情提示:這部分內容可能大部分同學都有一定的了解了,可以跳過直接進入下一小節(jié)哈。

Java 虛擬機在執(zhí)行 Java 程序的過程中會把它所管理的內存劃分為若干個不同的數據區(qū)域,這些區(qū)域都有各自的用途,以及創(chuàng)建和銷毀的時間。

全文我們都將以 JDK 7 的運行時數據區(qū)域為例:

先簡單解釋下線程共享和線程私有是啥意思。

所謂線程私有,通俗來說就是每個線程都會創(chuàng)建一個屬于自己的東西,每個線程之間的這塊私有區(qū)域互不影響,獨立存儲。比如程序計數器就是線程私有的,每個線程都會擁有一個屬于自己的程序計數器,互不干涉。

線程共享就沒啥好說的,簡單理解為公共場所,誰都能去,存儲的數據所有線程都能訪問。

OK,然后我們來逐個分析下每個區(qū)域都是用來存儲什么的。當然了,這里不會做太多詳細的說明,不然會使文章顯得非常臃腫,在理解本文的基礎上能夠讓大家對各個區(qū)域有基本的認知就好了。

首先來看一下線程共享的兩個區(qū)域:

1)Java 堆(Java Heap)是 Java 虛擬機所管理的內存中最大的一塊,在虛擬機啟動時創(chuàng)建。此內存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。這一點在 Java 虛擬機規(guī)范中的描述是:所有的對象實例以及數組都要在堆上分配。

2)方法區(qū)(Method Area)與 Java 堆一樣,是各個線程共享的內存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數據。

很多人習慣的把方法區(qū)稱為永久代(Permanent Generation),但實際上這兩者并不等價。通俗來說,方法區(qū)是一種規(guī)范,而永久代是 HotSpot 虛擬機實現這個規(guī)范的一種手段,對于其他虛擬機(比如 BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。

另外,對于 HotSpot 虛擬機來說,它在 JDK 8 中完全廢棄了永久代的概念,改用與 JRockit、J9 一樣在本地內存中實現的元空間(Meta-space)來代替,把 JDK 7 中永久代還剩余的內容(主要是類型信息)全部移到元空間中。

再來看看線程私有的三個區(qū)域:

1)虛擬機棧(Java Virtual Machine Stacks)其實是由一個一個的棧幀(Stack Frame)組成的,一個棧幀描述的就是一個 Java 方法執(zhí)行的內存模型。也就是說每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數棧、動態(tài)鏈接、方法的返回地址等信息。

每一個方法從調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程,當然,出棧的順序自然是遵守棧的后進先出原則的。

棧幀的概念在接下來的原理解析部分非常重要,各位務必搞懂哈。

2)本地方法棧(Native Method Stack)和上面我們所說的虛擬機棧作用基本一樣,區(qū)別只不過是本地方法棧為虛擬機使用到的 Native 方法服務,而虛擬機棧為虛擬機執(zhí)行 Java 方法(也就是字節(jié)碼)服務。

這里解釋一下 Native 方法的概念,其實不僅 Java,很多語言中都有這個概念。

"A native method is a Java method whose implementation is provided by non-java code."

就是說一個 Native 方法其實就是一個接口,但是它的具體實現是在外部由非 Java 語言寫的。所以同一個 Native 方法,如果用不同的虛擬機去調用它,那么得到的結果和運行效率可能是不一樣的,因為不同的虛擬機對于某個 Native 方法都有自己的實現,比如 Object 類的 hashCode 方法。

這使得 Java 程序能夠超越 Java 運行時的界限,有效地擴充了 JVM。

3)程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。字節(jié)碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

由于 Java 虛擬機的多線程是通過輪流分配 CPU 時間片的方式來實現的,因此,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數器。

那么程序計數器里存的到底是什么東西呢?

《深入理解 Java 虛擬機:JVM 高級實踐與最佳實戰(zhàn) - 第 2 版》給出了答案:如果線程正在執(zhí)行的是一個 Java 方法,程序計數器中記錄的就是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果正在執(zhí)行的是 Native 方法,這個計數器值則為空(Undefined)。

二、用 DEBUG 的方式看線程運行原理

接下來,我們就通過 DEBUG 這段代碼來看下線程的運行原理:

上述代碼的邏輯非常簡單,main 方法調用了 method1 方法,而 method1 方法又調用了 method2 方法。

看下圖,我們打了一個斷點:

OK,以 DEBUG 的方式運行 Test.main(),雖然這里我們沒有顯示的創(chuàng)建線程,但是 main 函數的調用本身就是一個線程,也被稱為主線程(main 線程),所以我們一啟動這個程序,就會給這個主線程分配一個虛擬機棧內存。

上文我們也說了,虛擬機棧內存其實就是個殼兒,里面真正存儲數據的,其實是一個一個的棧幀,每個方法都對應著一個棧幀。

所以當主線程調用 main 方法的時候,就會為 main 方法生成一個棧幀,其中存儲了局部變量表、操作數棧、動態(tài)鏈接、方法的返回地址等信息。

各位現在可以看看 DEBUG 窗口顯示的界面:

左邊的 Frames 就是棧幀的意思,可以看見現在主線程中只有一個 main 棧幀;

右邊的 Variables 就是該棧幀存儲的局部變量表,可以看到現在 main 棧幀中只有一個局部變量,也就是方法參數 args。

接下來 DEBUG 進入下一步,我們先來看看 DEBUG 界面上的每個按鈕都是啥意思,總共五個按鈕(已經了解的各位可以跳過這里):

1)Step Over:F8

程序向下執(zhí)行一行,如果當前行有方法調用,這個方法將被執(zhí)行完畢并返回,然后到下一行

2)Step Into:F7

程序向下執(zhí)行一行,如果該行有自定義方法,則運行進入自定義方法(不會進入官方類庫的方法)

3)Force Step Into:Alt + Shift + F7

程序向下執(zhí)行一行,如果該行有自定義方法或者官方類庫方法,則運行進入該方法(也就是可以進入任何方法)

4)Step Out:Shift + F8

如果在調試的時候你進入了一個方法,并覺得該方法沒有問題,你就可以使用 Step Out 直接執(zhí)行完該方法并跳出,返回到該方法被調用處的下一行語句。

5)Drop frame

點擊該按鈕后,你將返回到當前方法的調用處重新執(zhí)行,并且所有上下文變量的值也回到那個時候。只要調用鏈中還有上級方法,可以跳到其中的任何一個方法。

OK,我們點擊 Step Into 進入 method1 方法,可以看到,虛擬機棧內存中又多出了一個 method1 棧幀:

再點擊 Step Into 直到進入 method2 方法,于是虛擬機棧內存中又多出了一個 method2 棧幀:

當我們 Step Into 走到 method2 方法中的 return n 語句后,n 指向的堆中的地址就會被返回給 method1 中的 m,并且,滿足棧后進先出的原則,method2 棧幀會從虛擬機棧內存中被銷毀。

然后點擊 Step Over 執(zhí)行完輸出語句(Step Into 會進入 println 方法,Force Step Into 會進入 Object.toString 方法)

至此,method1 的使命全部完成,method1 棧幀會從虛擬機棧內存中被銷毀。

最后再往下走一步,main 棧幀也會被銷毀,這里就不再貼圖了。

三、線程運行原理詳細圖解

上面寫了這么多,其實也就是教會了大家棧幀這個東西,接下來我們通過圖解的方式,來帶大家詳細看看線程運行時,Java 運行時數據區(qū)域的各種變化。

首先第一步,類加載。

《深入理解 Java 虛擬機:JVM 高級實踐與最佳實戰(zhàn) - 第 2 版》中是這樣解釋類加載的:虛擬機把描述類的數據從 Class 文件(字節(jié)碼文件)加載到內存,并對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。

而加載進來的這些字節(jié)碼信息,就存儲在方法區(qū)中。看下圖,這里為了各位理解方便,我就不寫字節(jié)碼了,直接按照代碼來,大家知道這里存的其實是字節(jié)碼就行

主線程調用 main 方法,于是為該方法生成一個 main 棧幀:

那么這個參數 args 的值從哪里來呢?沒錯,就是從堆中 new 出來的:

而 main 方法的返回地址就是程序的退出地址。

再來看程序計數器,如果線程正在執(zhí)行的是一個 Java 方法,程序計數器中記錄的就是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,也就是說此時 method1(10) 對應的字節(jié)碼指令的地址會被放入程序計數器,圖片中我們仍然以具體的代碼代替哈,大家知道就好

OK,CPU 根據程序計數器的指示,進入 method1 方法,自然,method1 棧幀就被創(chuàng)建出來了:

局部變量表和方法返回地址安頓好后,就可以開始具體的方法調用了,首先 10 會被傳給 x,然后走到 y 被賦值成 x + 1 這步,也就是程序計數器會被修改成這步代碼對應的字節(jié)碼指令的地址:

走到 Object m = method2(); 這一步的時候,又會創(chuàng)建一個 method2 棧幀:

可以看到,method2 方法的第一行代碼會在堆中創(chuàng)建一個 Object 對象:

隨后,走到 method2 方法中的 return n; 語句,n 指向的堆中的地址就會被返回給 method1 中的 m,并且,滿足棧后進先出的原則,method2 棧幀會從虛擬機棧內存中被銷毀:

根據 method2 棧幀指向的方法返回地址,我們接著執(zhí)行 System.out.println(m.toString()) 這條輸出語句,執(zhí)行完后,method1 棧幀也被銷毀了:

再根據 method1 棧幀指向的方法返回地址,發(fā)現我們的程序已走到了生命的盡頭,main 棧幀于是也被銷毀了,就不再貼圖了。

四、用 DEBUG 的方式看多線程運行原理

上面說的是只有一個線程的情況,其實多線程的原理也差不多,因為虛擬機棧是每個線程私有的,大家互不干涉,這里我就簡單的提一嘴。

分別在如下兩個位置打上 Thread 類型的斷點:

然后以 DEBUG 方式運行,你就會發(fā)現存在兩個互不干涉的虛擬機棧空間:

當然,使用多線程就不可避免的會遇到一個問題,那就是線程的上下文切換(Thread Context Switch),就是說因為某些原因導致 CPU 不再執(zhí)行當前的線程,轉而執(zhí)行另一個線程。

導致線程上下文切換的原因大概有以下幾種:

1)線程的 CPU 時間片用完

2)發(fā)生了垃圾回收

3)有更高優(yōu)先級的線程需要運行

4)線程自己調用了 sleep、yield、wait、join、park、synchronized、lock 等方法

當線程的上下文切換發(fā)生時,也就是從一個線程 A 轉而執(zhí)行另一個線程 B 時,需要由操作系統(tǒng)保存當前線程 A 的狀態(tài)(為了以后還能順利回來接著執(zhí)行),并恢復另一個線程 B 的狀態(tài)。

這個狀態(tài)就包括每個線程私有的程序計數器和虛擬機棧中每個棧幀的信息等,顯然,每次操作系統(tǒng)都需要存儲這么多的信息,頻繁的線程上下文切換勢必會影響程序的性能。

以上就是深入理解以DEBUG方式線程的底層運行原理的詳細內容,更多關于DEBUG方式線程運行原理的資料請關注腳本之家其它相關文章!

相關文章

  • 基于eclipse.ini內存設置的問題詳解

    基于eclipse.ini內存設置的問題詳解

    本篇文章是對eclipse.ini內存設置的問題進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • Spring?Security實現HTTP認證

    Spring?Security實現HTTP認證

    本文主要介紹了Spring?Security實現HTTP認證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧<BR>
    2022-06-06
  • JVM內存溢出和內存泄漏的區(qū)別及說明

    JVM內存溢出和內存泄漏的區(qū)別及說明

    這篇文章主要介紹了JVM內存溢出和內存泄漏的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • mybatis的使用-Mapper文件各種語法介紹

    mybatis的使用-Mapper文件各種語法介紹

    這篇文章主要介紹了mybatis的使用-Mapper文件各種語法介紹,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • MybatisX中xml映射文件中命名空間爆紅的解決

    MybatisX中xml映射文件中命名空間爆紅的解決

    本文主要介紹了MybatisX中xml映射文件中命名空間爆紅的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-06-06
  • Spring Web MVC和Hibernate的集成配置詳解

    Spring Web MVC和Hibernate的集成配置詳解

    這篇文章主要介紹了Spring Web MVC和Hibernate的集成配置詳解,具有一定借鑒價值,需要的朋友可以參考下
    2017-12-12
  • 關于@Value注解失效的原因分析

    關于@Value注解失效的原因分析

    這篇文章主要介紹了關于@Value注解失效的原因分析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Springboot項目全局異常統(tǒng)一處理案例代碼

    Springboot項目全局異常統(tǒng)一處理案例代碼

    最近在做項目時需要對異常進行全局統(tǒng)一處理,主要是一些分類入庫以及記錄日志等,因為項目是基于Springboot的,所以去網絡上找了一些博客文檔,然后再結合項目本身的一些特殊需求做了些許改造,現在記錄下來便于以后查看
    2023-01-01
  • MyBatis Plus構建一個簡單的項目的實現

    MyBatis Plus構建一個簡單的項目的實現

    這篇文章主要介紹了MyBatis Plus構建一個簡單的項目的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • 深度剖析Java成員變量、局部變量和靜態(tài)變量的創(chuàng)建和回收時機

    深度剖析Java成員變量、局部變量和靜態(tài)變量的創(chuàng)建和回收時機

    這篇文章主要介紹了深度剖析Java成員變量、局部變量和靜態(tài)變量的創(chuàng)建和回收時機,成員變量是定義在類中的變量,每個類的實例都會擁有自己的成員變量。它們的生命周期與對象的創(chuàng)建和銷毀相對應,下面我將詳細介紹它們的特點和生命周期,需要的朋友可以參考下
    2023-07-07

最新評論