欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解以DEBUG方式線程的底層運(yùn)行原理

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

一、Java 運(yùn)行時(shí)數(shù)據(jù)區(qū)域

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

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

全文我們都將以 JDK 7 的運(yùn)行時(shí)數(shù)據(jù)區(qū)域?yàn)槔?/p>

先簡(jiǎn)單解釋下線程共享和線程私有是啥意思。

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

線程共享就沒啥好說的,簡(jiǎn)單理解為公共場(chǎng)所,誰都能去,存儲(chǔ)的數(shù)據(jù)所有線程都能訪問。

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

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

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

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

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

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

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

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

每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程,當(dāng)然,出棧的順序自然是遵守棧的后進(jìn)先出原則的。

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

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

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

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

就是說一個(gè) Native 方法其實(shí)就是一個(gè)接口,但是它的具體實(shí)現(xiàn)是在外部由非 Java 語言寫的。所以同一個(gè) Native 方法,如果用不同的虛擬機(jī)去調(diào)用它,那么得到的結(jié)果和運(yùn)行效率可能是不一樣的,因?yàn)椴煌奶摂M機(jī)對(duì)于某個(gè) Native 方法都有自己的實(shí)現(xiàn),比如 Object 類的 hashCode 方法。

這使得 Java 程序能夠超越 Java 運(yùn)行時(shí)的界限,有效地?cái)U(kuò)充了 JVM。

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

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

那么程序計(jì)數(shù)器里存的到底是什么東西呢?

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

二、用 DEBUG 的方式看線程運(yùn)行原理

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

上述代碼的邏輯非常簡(jiǎn)單,main 方法調(diào)用了 method1 方法,而 method1 方法又調(diào)用了 method2 方法。

看下圖,我們打了一個(gè)斷點(diǎn):

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

上文我們也說了,虛擬機(jī)棧內(nèi)存其實(shí)就是個(gè)殼兒,里面真正存儲(chǔ)數(shù)據(jù)的,其實(shí)是一個(gè)一個(gè)的棧幀,每個(gè)方法都對(duì)應(yīng)著一個(gè)棧幀。

所以當(dāng)主線程調(diào)用 main 方法的時(shí)候,就會(huì)為 main 方法生成一個(gè)棧幀,其中存儲(chǔ)了局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法的返回地址等信息。

各位現(xiàn)在可以看看 DEBUG 窗口顯示的界面:

左邊的 Frames 就是棧幀的意思,可以看見現(xiàn)在主線程中只有一個(gè) main 棧幀;

右邊的 Variables 就是該棧幀存儲(chǔ)的局部變量表,可以看到現(xiàn)在 main 棧幀中只有一個(gè)局部變量,也就是方法參數(shù) args。

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

1)Step Over:F8

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

2)Step Into:F7

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

3)Force Step Into:Alt + Shift + F7

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

4)Step Out:Shift + F8

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

5)Drop frame

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

OK,我們點(diǎn)擊 Step Into 進(jìn)入 method1 方法,可以看到,虛擬機(jī)棧內(nèi)存中又多出了一個(gè) method1 棧幀:

再點(diǎn)擊 Step Into 直到進(jìn)入 method2 方法,于是虛擬機(jī)棧內(nèi)存中又多出了一個(gè) method2 棧幀:

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

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

至此,method1 的使命全部完成,method1 棧幀會(huì)從虛擬機(jī)棧內(nèi)存中被銷毀。

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

三、線程運(yùn)行原理詳細(xì)圖解

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

首先第一步,類加載。

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

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

主線程調(diào)用 main 方法,于是為該方法生成一個(gè) main 棧幀:

那么這個(gè)參數(shù) args 的值從哪里來呢?沒錯(cuò),就是從堆中 new 出來的:

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

再來看程序計(jì)數(shù)器,如果線程正在執(zhí)行的是一個(gè) Java 方法,程序計(jì)數(shù)器中記錄的就是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址,也就是說此時(shí) method1(10) 對(duì)應(yīng)的字節(jié)碼指令的地址會(huì)被放入程序計(jì)數(shù)器,圖片中我們?nèi)匀灰跃唧w的代碼代替哈,大家知道就好

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

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

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

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

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

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

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

四、用 DEBUG 的方式看多線程運(yùn)行原理

上面說的是只有一個(gè)線程的情況,其實(shí)多線程的原理也差不多,因?yàn)樘摂M機(jī)棧是每個(gè)線程私有的,大家互不干涉,這里我就簡(jiǎn)單的提一嘴。

分別在如下兩個(gè)位置打上 Thread 類型的斷點(diǎn):

然后以 DEBUG 方式運(yùn)行,你就會(huì)發(fā)現(xiàn)存在兩個(gè)互不干涉的虛擬機(jī)??臻g:

當(dāng)然,使用多線程就不可避免的會(huì)遇到一個(gè)問題,那就是線程的上下文切換(Thread Context Switch),就是說因?yàn)槟承┰驅(qū)е?CPU 不再執(zhí)行當(dāng)前的線程,轉(zhuǎn)而執(zhí)行另一個(gè)線程。

導(dǎo)致線程上下文切換的原因大概有以下幾種:

1)線程的 CPU 時(shí)間片用完

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

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

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

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

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

以上就是深入理解以DEBUG方式線程的底層運(yùn)行原理的詳細(xì)內(nèi)容,更多關(guān)于DEBUG方式線程運(yùn)行原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于eclipse.ini內(nèi)存設(shè)置的問題詳解

    基于eclipse.ini內(nèi)存設(shè)置的問題詳解

    本篇文章是對(duì)eclipse.ini內(nèi)存設(shè)置的問題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Spring?Security實(shí)現(xiàn)HTTP認(rèn)證

    Spring?Security實(shí)現(xiàn)HTTP認(rèn)證

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

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

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

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

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

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

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

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

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

    關(guān)于@Value注解失效的原因分析

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

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

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

    MyBatis Plus構(gòu)建一個(gè)簡(jiǎn)單的項(xiàng)目的實(shí)現(xiàn)

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

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

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

最新評(píng)論