JVM栈

参考资料:https://www.javainterviewpoint.com/java-virtual-machine-architecture-in-java/
程序计数器
Program Counter Register 程序计数器(是通过寄存器实现的),用于保存JVM中下一条所要执行的指令的地址。
PC 寄存器用来存储指向下一条指令的地址,即将要执行的指令代码。由执行引擎读取下一条指令。

左边是二进制字节码,右边是Java编译后的代码,程序计数器就是用于记住下一条jvm指令的执行地址,比如现在是“0”,那他就会记住“3”,因为“3”在“0”的下面,而JVM会将指令 交给 解释器,解释器再将转为 机器码,交于CPU执行。
特点:
- 是线程私有的:
- CPU会为每个线程分配时间片,当当前线程的时间片使用完以后,CPU就会去执行另一个线程中的代码
- 程序计数器是每个线程所私有的,当另一个线程的时间片用完,又返回来执行当前线程的代码时,通过程序计数器可以知道应该执行哪一句指令
- 不会存在内存溢出
虚拟机栈
Java Virtual Machine Stacks (Java 虚拟机栈),栈:先入后出
- 每个线程运行需要的内存空间,称为虚拟机栈
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存空间(参数、局部变量、返回地址)
- 每个线程只能有一个活动栈帧,对应当前正在执行的那个方法
IDEA中的虚拟机栈:

常见问题
- 垃圾回收是否涉及栈内存?
不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
- 栈内存分配越大越好吗?
不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
使用
-Xss可以设置栈内存大小,比如物理内存是100Mb,当栈内存为1Mb时,可以同时有100个线程,而当栈内存为2Mb时,最多同时只有50个线程了。
- 方法内的局部变量是否线程安全?
如果方法内部局部变量没有逃离方法的作用访问,它是线程安全的
如果是局部变量引用了对象,并逃离方法的范围,需要考虑线程安全问题
public class main1 {
public static void main(String[] args) {
}
//下面各个方法会不会造成线程安全问题?
//不会
public static void m1() {
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.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
}
//会,其他线程可能会拿到这个线程的引用
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
}
}栈内存溢出
Java.lang.stackOverflowError:栈内存溢出
导致栈内存溢出的情况:
- 栈帧过多导致栈内存溢出
- 栈帧过大导致栈内存溢出

线程运行诊断
Linux环境下运行某些程序的时候,可能导致CPU的占用过高,使用 top 命令可以查看CPU的使用情况:

ps H -eo pid,tid,%cpu | grep 进程id ,刚才通过top查到的进程号,用ps命令进一步定位是哪个线程引起的cpu占用过高:

jstack 进程id ,通过ps命令看到的tid来对比定位,注意jstack查找出的线程id是16进制的,需要转换
这样就知道那个线程占用过多的CPU了,进一步定位到问题代码的源码行数
还可以展示出死锁的信息

本地方法栈
Native Method Stacks
一些带有native关键字的方法就是需要JAVA去调用本地的C或者C++方法,因为JAVA有时候没法直接和操作系统底层交互,所以需要用到本地方法。