java线程栈溢出 排查 java线程栈的结构_常量池

1.程序计数器

  • 定义:记住下一条JVM指令执行的地址
  • 特点
  • 线程是私有的,每个线程有一个单独的程序计数器
  • 不会存在内存溢出

 

2.虚拟机栈

  • 1 定义(Java Virtual Machine Stacks)
  • 每个线程运行时所需要的内存,每个线程有一个单独的栈,称为虚拟机栈
  • 每个栈里面包含多个栈帧,栈帧里装着调用单个方法时方法内的信息(变量等) 递归时每递归一层就会产生一个栈帧,存放相应信息
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  • 2 问题思考
  • 垃圾回收不涉及栈内存,因为栈帧用完后会自动出栈销毁,不需要进行回收
  • 栈的内存空间分配不是越大越好,内存空间固定,单个栈的内存空间越大,所能划分的栈的个数就越少
  • 方法内的局部变量是线程安全的
  • 什么是线程安全
    在方法内定义一个局部变量,对该局部变量进行操作,且没有返回值,是线程安全的
public static void m1() {
        StringBuilder sb = new StringBuilder();
        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;

对一个传入的参数进行操作,

public static void m2(StringBuilder sb) {
        sb.append(1);
        sb.append(2);
        sb.append(3);
        System.out.println(sb.toString());
    }

定义一个静态量,两个方法都对这个量进行操作,是线程不安全的

  • 总结:如果方法内局部变量没有逃离方法的作用访问,他是线程安全的;如果是局部变量引用了对象,并且逃离了方法的作用范围,那么就是线程不安全的
  • 3 栈的内存溢出 StackOverflowError
  • 栈帧过多导致内存溢出:递归层数过多
  • 栈帧过大导致栈内存溢出:一个方法内包含了过多的数据
  • 4 线程的运行诊断
public static void main(String[] args) throws JsonProcessingException {
    //新建一个部门对象
        Dept d = new Dept();
    //设置部门的名字
        d.setName("Market");
    
    //新建一个员工对象
        Emp e1 = new Emp();
    //设置员工1名字
        e1.setName("zhang");
    //设置员工部门为d
        e1.setDept(d);
    
        Emp e2 = new Emp();
    //设置员工2名字
        e2.setName("li");
     //设置员工部门为d
        e2.setDept(d);
    
    //设置部门里有e1,e2两个员工 
        d.setEmps(Arrays.asList(e1, e2));
    //转换java
        ObjectMapper mapper = new ObjectMapper();
       System.out.println(mapper.writeValueAsString(d));
    }

在转换对象过程中,{name:"Market",emps:[{name:"zhangsan",dept:{name:{} ,dept{name:{},…}部门添加员工,而员工中又有一个部门对象,部门对象中又有员工,无限循环,造成内存溢出。

java线程栈溢出 排查 java线程栈的结构_内存溢出_02

3.本地方法栈

和操作系统交互的方法

4.堆 Heap

  • 通过new创建关键字,创建的对象都会使用堆内存
  • 是线程共享的,堆中的对象都需要考虑线程安全问题(堆是线程不安全的)
  • 有垃圾回收机制

5.方法区

  • 定义:存的是和类相关的信息,逻辑上是堆的一部分 实际不一定
  • 方法区内存溢出(元空间内存溢出:原空间使用的是系统内存)MetaSpace
  • 运行时常量池
  • 常量池:一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数信息等等
  • 运行时常量池:常量池是.class文件中的,当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号变成实际内存地址