栈与栈帧
JVM由堆、栈、方法区组成。其中栈就是给线程用的。每个线程启动后,虚拟机就会为其分配一块栈内存。
- 每个栈由多个栈帧组成。栈帧是每次方法调用时所占用的内存。
- 每个线程只能有一个活动栈帧,对应当前正执行的方法
单线程(main线程)例子
package com.zqh.day1;
/**
* 一个线程(main)的栈与栈帧
* 创建了三个栈帧,因为有三个方法
*/
public class Frames {
public static void main(String[] args) {
method1(1);
}
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,method1输出method2。
main是一个主线程,主线程已启动,虚拟机就会给他分配一块栈内存。栈内存由一个个栈帧组成,先调用的是main方法,就会为main方法产生一个栈帧,
,里面是方法的参数
主方法又会调用method1,JVM又会为method1产生一个栈帧
,而method1又会调用method2,就会又产生一个栈帧
栈是后进先出的。method2最后进入的,然而执行完却是第一个走的。
图解栈帧(main线程)
当执行这个类的时候,首先是类加载(把类里的方法放在方法区)
加载完,虚拟机就会启动一个main线程,为他分配内存,接下来线程交给任务调度器进行执行,main线程里又会有一个main方法的栈帧内存局部变量里就是局部变量和方法参数,像main方法,JVM会生成一个String数组,然后args会指向它
main方法的返回地址是程序的退出地址。
然后走到method1,JVM会给method1分配一个栈帧,如图它的返回地址是
走到method2的时候,又分配一个栈帧。
完整执行流程
然后执行完依次返回,结束程序
两个线程的栈与栈帧
主线程已经写好了,添加一个线程
package com.zqh.day1;
public class ThreadsFrames {
// 创建一个主线程
public static void main(String[] args) {
// 创建一个t1线程
Thread t1 = new Thread(){
// t1线程执行method1时参数为20
@Override
public void run() {
method1(20);
}
};
t1.setName("t1");
t1.start();
// 主线程执行method1参数为10
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;
}
}
在主线程和t1线程调用method1时都打个断点,断点模式选Thread
debug
先创建了main线程,和main方法的栈帧
下拉列表还有个t1线程。这俩可以分开走。
先看主线程。
进method1了,再走就是method2。
看t1线程,走到method1
证明主线程和t1线程之间的栈帧是相互独立的
小结
一个线程对应一个栈。栈由栈帧组成,一个方法对应一个栈帧,每个线程的栈帧是相互独立的
线程上下文切换(Thread Context Switch)
一个线程把CPU分配的时间片用完了**(并不是线程执行完了),就要把CPU使用权交给其他线程,从使用CPU到不使用CPU,就是一次上下文切换。
发生上下文切换的原因:
第一个不用解释了,第二个垃圾回收,垃圾回收的时候会暂停所有线程,让垃圾回收的线程进行垃圾回收。就发生上下文切换。前三种都是被动
第四种:线程自己调用了一些暂停的方法。
当上下文切换发生的时候,需要保存当前线程的状态**。
比如一个线程要执行十行代码,但是当执行完八行代码的时候,CPU时间片用完了,就记录一下“现在执行到了第八行代码”,然后再上下文切换,恢复另一个线程的状态。
Java中由程序计数器来干这个事,当要发生上下文切换的时候,程序计数器就把当前线程下条JVM指令的执行地址记住,用于后面恢复。记录的还包括这些信息
上下文切换太频繁,会影响性能。
所以,线程数不是越多越好,(因为上下文切换太频繁)