目录
程序计数器
Program Counter Register
内存中位置
作用
一条指令的执行流程
- 二进制(文件)字节码指令需要经过解释器转成机器码,交给CPU执行
- 地址为0的指令交给cpu执行后,会将下一条指令的地址(3)放入程序计数器,当第一条指令(0)执行完以后,解释器会到程序计数器取到3,继续执行。
- 程序计数器物理上是通过寄存器实现的
- 程序计数器作用:记住下一条指令的执行地址
特点
- 线程私有
- CPU为每个线程分配时间片,如果当前线程(1)的时间片用完,则CPU会执行其他线程(线程2)的指令,为了线程切换后(线程1->线程2)能恢复(线程2->线程1)到该线程之前的位置,需要程序计数器记录之前线程(线程1)的执行位置
- 唯一一个不会存在OutOfMemoryError的区域
虚拟机栈
Java Virtual Machine Stack
定义
每个线程运行时需要的内存空间,称为虚拟机栈
每个方法被执行时,虚拟机都会创建一个栈桢,用于存储局部变量表(内部变量及入参)、操作数栈、动态链接、方法出口(出参)等。
每个线程只能有一个活动栈桢,对应正在执行的方法,方法被执行时压入栈,执行完时弹出栈。
图示
栈桢演示
1.执行main,main方法被压栈,当前活动栈桢为main
2.执行method01,method01被压栈,当前活动栈桢为method01
3.执行method02,method02被压栈,当前活动栈桢为method02
4.执行完method02,method02被弹出栈
面试常见问题
- 垃圾回收是否涉及栈内存
- 不需要回收栈内存,栈中存的是一个个的栈桢,待方法执行完后会自动弹出(栈帧出栈后,即被销毁),所以无需通过垃圾回收的方式去回收。
- 栈内存分配越大越好吗
- 不是,物理内存一定的情况下,栈内存越大则可分配的线程越少,假如一个线程分配2m的空间,物理内存为500m,则只能分配出250个线程
- 栈的配置可见如下参数设置
- 方法内的局部变量是否线程安全
实例
public static void m1() {
// sb是方法内局部变量,是线程私有的,因为是线程安全的
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
public static void m2(StringBuilder sb) {
// sb是外面传入的参数,非线程私有的,因为不是线程安全的
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
public static StringBuilder m3() {
// sb 作为方法m3()内部的局部变量,是线程私有的,但是作为返回值,逃离了方法的作用范围,因
为非线程安全
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
总结:
- 如果方法内局部变量没有逃离方法的作用范围,则是线程安全的
- 如果局部变量引用了对象,且逃离了方法的作用范围,则需要考虑线程安全问题
本地方法栈
在Java调用本地方法时,为本地方法(native)提供的内存空间,
参考资料:《深入Java虚拟机》