1、線程運(yùn)行原理
1.1 棧與棧幀??
Java Virtual Machine Stacks (Java 虛擬機(jī)棧 JVM)
我們都知道 JVM 中由堆、棧、方法區(qū)所組成,其中棧內(nèi)存是給誰用的呢?其實(shí)就是線程,每個(gè)線程啟動(dòng)后,虛擬機(jī)就會(huì)為其分配一塊棧內(nèi)存。
-
每個(gè)棧由多個(gè)棧幀(Frame)組成,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占用的內(nèi)存
-
每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法
單線程示例代碼
public class TestFrames {
? ?public static void main(String[] args) {
? ? ? ?method1(10); // 斷點(diǎn)處
? }
?
? ?private static void method1(int x) {
? ? ? ?int y = x + 1;
? ? ? ?Object m = method2();
? ? ? ?System.out.println(m);
? }
?
? ?private static Object method2() {
? ? ? ?Object n = new Object();
? ? ? ?return n;
? }
}
在打斷點(diǎn)處,可以看到一個(gè)棧幀
執(zhí)行到method1,可以看到新起了一個(gè)棧幀
當(dāng)執(zhí)行到method2時(shí),可以看到又新起了一個(gè)棧幀
由于是棧,隨著的程序的運(yùn)行,后面開啟的棧幀會(huì)先被銷毀,直至main棧幀被銷毀,此刻程序運(yùn)行完成。
對(duì)應(yīng)圖解:
內(nèi)存釋放后
具體就是:
1.將編譯好的字節(jié)碼加載到j(luò)vm的方法區(qū)內(nèi)存中 2.jvm啟動(dòng)一個(gè)main的主線程,cpu核心就準(zhǔn)備運(yùn)行主線程的代碼了,給主線程分配自己的棧內(nèi)存【args、局部變量、返回地址、所記錄】,每個(gè)線程的棧里面還有個(gè)程序計(jì)數(shù)器 程序計(jì)數(shù)器的作用:當(dāng)cpu要執(zhí)行哪行代碼了,就去這個(gè)里面去要 3.把主方法的里面代碼行放到程序計(jì)數(shù)器 4.主方法調(diào)用的是method1的方法,為method1分配棧內(nèi)存,里面存儲(chǔ)這個(gè)方法里面局部變量,返回地址,這些變量是分配內(nèi)存時(shí),會(huì)把空間預(yù)留好 5.將method1的第一行讀到程序計(jì)數(shù)器讓cpu執(zhí)行 6.methode1下一行調(diào)用method2()方法,創(chuàng)建他的棧內(nèi)存 7.把Object n = new Object()這行代碼讀取到計(jì)數(shù)器,在隊(duì)中創(chuàng)建對(duì)象 8.method2()將返回地址給m,方法執(zhí)行完就可以釋放掉method2()的棧內(nèi)存 9.一層層方法結(jié)束后,依次釋放掉每個(gè)方法線程
現(xiàn)在來看看多線程下的棧與棧幀
public class TestFrames {
? ?public static void main(String[] args) {
? ? ? ?Thread t1 = new Thread(){
? ? ? ? ? ?@Override
? ? ? ? ? ?public void run() {
? ? ? ? ? ? ? ?method1(20);// 斷點(diǎn)處
? ? ? ? ? }
? ? ? };
? ? ? ?t1.setName("t1");
? ? ? ?t1.start();
? ? ? ?method1(10);// 斷點(diǎn)處
? }
?
? ?private static void method1(int x) {
? ? ? ?int y = x + 1;
? ? ? ?Object m = method2();
? ? ? ?System.out.println(m);
? }
?
? ?private static Object method2() {
? ? ? ?Object n = new Object();
? ? ? ?return n;
? }
}
?
在第一個(gè)斷點(diǎn)處
可以看到多個(gè)線程同時(shí)運(yùn)行中,我們可以選擇具體的線程來查看運(yùn)行狀況并且往下運(yùn)行,具體的讀者可以自行實(shí)踐。
1.2 線程上下文切換(Thread Context Switch)
因?yàn)橐韵乱恍┰驅(qū)е?cpu 不再執(zhí)行當(dāng)前的線程,轉(zhuǎn)而執(zhí)行另一個(gè)線程的代碼(簡(jiǎn)單來說就是從使用cpu到不使用cpu)
-
線程的 cpu 時(shí)間片用完
-
垃圾回收
-
有更高優(yōu)先級(jí)的線程需要運(yùn)行
-
線程自己調(diào)用了 sleep、yield、wait、join、park、synchronized、lock 等方法
當(dāng) Context Switch(上下文切換) 發(fā)生時(shí),需要由操作系統(tǒng)保存當(dāng)前線程的狀態(tài),并恢復(fù)另一個(gè)線程的狀態(tài),Java 中對(duì)應(yīng)的概念就是程序計(jì)數(shù)器(Program Counter Register),它的作用是記住下一條 jvm 指令的執(zhí)行地址,是線程私有的
-
狀態(tài)包括程序計(jì)數(shù)器、虛擬機(jī)棧中每個(gè)棧幀的信息,如局部變量、操作數(shù)棧、返回地址等
-