定义
Java Virtual Machine Sracks(Java 虚拟机栈)
- 每个线程运行时所需要的内存,称为虚拟机栈
- 每个栈由多个栈帧(Frame)组成,对应每次方法调用时所占的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
注:
- 垃圾回收机制不涉及栈内存,因为栈内存在方法每次调用后被自动回收
- 栈内存分配不是越大越好
- 方法内的局部变量是否线程安全?
- 如果方法内局部变量没有逃离方法的作用访问,它的线程就是安全的
- 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
栈内存溢出
栈帧过多导致栈内存溢出
示例代码:递归方法没有增加结束条件
public class demo {
private static int count;
public static void main(String[] args) {
try {
run1();
}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println(count);
}
}
public static void run1(){
count++;
run1();
}
}
栈帧过大导致栈内存溢出
示例代码:Json数据转换双向引用
import org.codehaus.jackson.map.ObjectMapper;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class demo2 {
public static void main(String[] args) throws IOException {
//创建一个部门对象
Dept dept = new Dept();
dept.setDname("Market");
//创建两个员工对象
Emp emp1 = new Emp();
Emp emp2 = new Emp();
emp1.setEname("张三");
emp1.setDname(dept);
emp2.setEname("李四");
emp2.setDname(dept);
dept.setEmpList(Arrays.asList(emp1,emp2));
System.out.println(new ObjectMapper().writeValueAsString(dept));
}
}
class Emp {
private String ename;
private Dept dname;
public Emp() {
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Dept getDname() {
return dname;
}
public void setDname(Dept dname) {
this.dname = dname;
}
public Emp(String ename, Dept dname) {
this.ename = ename;
this.dname = dname;
}
}
class Dept{
private String dname;
private List<Emp> empList;
public Dept() {
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public List<Emp> getEmpList() {
return empList;
}
public void setEmpList(List<Emp> empList) {
this.empList = empList;
}
public Dept(String dname, List<Emp> empList) {
this.dname = dname;
this.empList = empList;
}
}
线程运行诊断
案例1:cpu占用过多(在linux操作系统中进行)
/**
* 演示 cpu 占用过高
*/
public class Demo1_16 {
public static void main(String[] args) {
new Thread(null, () -> {
System.out.println("1...");
while(true) {
}
}, "thread1").start();
new Thread(null, () -> {
System.out.println("2...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread2").start();
new Thread(null, () -> {
System.out.println("3...");
try {
Thread.sleep(1000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "thread3").start();
}
}
- 用top定位哪个进程对cpu的占用过高
- 再用ps命令进一步定位是哪个线程引起的cpu占用过高
ps H -eo pid,tid,%cpu | grep 进程id - 根据进程id列出该进程的所有线程:jstack 进程id
- 将找到线程的线程id换算成16进制,即可在列表中找到具体问题
案例2:程序运行很长时间没有结果
package cn.itcast.jvm.t1.stack;
/**
* 演示线程死锁
*/
class A{};
class B{};
public class Demo1_3 {
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();
}
}