1、线程运行原理
1.1 栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈 JVM)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
-
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
-
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
单线程示例代码
public class TestFrames {
public static void main(String[] args) {
method1(10); // 断点处
}
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;
}
}
在打断点处,可以看到一个栈帧
执行到method1,可以看到新起了一个栈帧
当执行到method2时,可以看到又新起了一个栈帧
由于是栈,随着的程序的运行,后面开启的栈帧会先被销毁,直至main栈帧被销毁,此刻程序运行完成。
对应图解:
内存释放后
具体就是:
1.将编译好的字节码加载到jvm的方法区内存中 2.jvm启动一个main的主线程,cpu核心就准备运行主线程的代码了,给主线程分配自己的栈内存【args、局部变量、返回地址、所记录】,每个线程的栈里面还有个程序计数器 程序计数器的作用:当cpu要执行哪行代码了,就去这个里面去要 3.把主方法的里面代码行放到程序计数器 4.主方法调用的是method1的方法,为method1分配栈内存,里面存储这个方法里面局部变量,返回地址,这些变量是分配内存时,会把空间预留好 5.将method1的第一行读到程序计数器让cpu执行 6.methode1下一行调用method2()方法,创建他的栈内存 7.把Object n = new Object()这行代码读取到计数器,在队中创建对象 8.method2()将返回地址给m,方法执行完就可以释放掉method2()的栈内存 9.一层层方法结束后,依次释放掉每个方法线程
现在来看看多线程下的栈与栈帧
public class TestFrames {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(20);// 断点处
}
};
t1.setName("t1");
t1.start();
method1(10);// 断点处
}
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;
}
}
在第一个断点处
可以看到多个线程同时运行中,我们可以选择具体的线程来查看运行状况并且往下运行,具体的读者可以自行实践。
1.2 线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码(简单来说就是从使用cpu到不使用cpu)
-
线程的 cpu 时间片用完
-
垃圾回收
-
有更高优先级的线程需要运行
-
线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当 Context Switch(上下文切换) 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
-
状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
-
Context Switch 频繁发生会影响性能,因为线程数不是越多越好。