一. 定义
JVM Stacks 虚拟机栈先进后出,后进先出
二. 概述
-
每个方法在运行时需要的内存都会创建一个
栈帧(Stack Frame)
用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程 -
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理
动态链接(Dynamic Linking)
、方法返回值
和异常分派( Dispatch Exception)
。栈帧随着方法调用而创建,随着方法结束而销毁— —无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束
注意:
线程私有,每个线程都会有Java虚拟机栈
每个线程运行时所需要的内存空间,称为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
三. 栈的演示
四. 问题辨析
-
垃圾回收是否涉及栈内存?
- 不需要,因为栈内存就是一次次方法调用所产生的栈帧内存,而栈帧内存在每一次方法调用结束后,都会弹出栈,自动的被回收掉,根本不需要垃圾回收来管理栈内存。
-
栈内存分配越大越好吗?
- 栈内存划分越大,会导致线程数变少,例如一个线程需要1MB栈内存,100MB内存就能划分100个线程,但是如果一个线程需要2MB栈内存,那么100MB内存只能划分50个线程
-
方法内的局部变量是否是线程安全?
- 如果方法内局部变量没有逃离方法的作用范围,他就是线程安全的
- 如果局部变量引用了对象,并逃离方法的作用范围
五. 栈内存溢出
栈帧过多会导致栈内存溢出
栈帧过大导致栈内存溢出
代码演示
/*
演示栈内存溢出 StackOverflowError
-Xss256k
*/
public class Demo2 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();
}
}
json数据转换也会出现
注意:@JsonIgnore注解,假如把员工json化时,遇到部门属性直接略过不转该属性
import java.util.Arrays;
import java.util.List;
/*
json 数据转换
*/
public class Demo2 {
public static void main(String[] args) throws JsonprocessingException {
Dept d=new Dept();
d.setName("鸡你太美");
Emp e1=new Emp();
e1.setName("菜徐琨");
e1.setDept(d);
Emp e2=new Emp();
e2.setName("小黑子");
e2.setDept(d);
d.setEmps(Arrays.asList(e1, e2));
ObjectMapper mapper=new ObjectMapper();
System.out.println(mapper.writeValueAsString(d));
}
}
class Emp{
private String name;
private Dept dept;
public Emp() {
}
public Emp(String name, Dept dept) {
this.name = name;
this.dept = dept;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
class Dept{
private String name;
private List<Emp> emps;
public Dept() {
}
public Dept(String name, List<Emp> emps) {
this.name = name;
this.emps = emps;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Emp> getEmps() {
return emps;
}
public void setEmps(List<Emp> emps) {
this.emps = emps;
}
}
六. 线程运行诊断
- 案例1:CPU占用过多
这里需要安装:
- VMware Workstation Pro
- Xshell 7(免费)
- SecureCRT.exe
-
定位
-
用top定位哪个进程对cpu的占用过高
-
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
-
jstack 进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
- 所以java:8,在源程序第
8
行出现问题
- 所以java:8,在源程序第
public class Demo2 {
public static void main(String[] args) {
// Thread(ThreadGroup group, Runnable target, String name) 分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。
new Thread(null,()->{
System.out.println("1...");
while(true) {
}},"菜徐琨01").start();
new Thread(null,()->{
System.out.println("2...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"菜徐琨02").start();
new Thread(null,()->{
System.out.println("3...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"菜徐琨03").start();
}
}
- 案例2:程序运行很长时间没有结果
- 输入
nohup
路径&
进行连接
jstack
进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
- 原因:
- 原因:
class A extends Thread {
}
class B extends Thread {
}
public class Demo3 {
static A a = new A();
static B b = new B();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (a) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("我获得了a和b");
}
}
}).start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (b) {
synchronized (a) {
System.out.println("我获得了a和b");
}
}
}).start();
}
}