JVM 栈深入理解(三)

一、栈的相关理解

1、什么是栈

当线程运行时就会开辟一个内存空间, 这个内存空间就是栈,也称为栈内存。它是由多个栈针(Frame)组成,生命周期和线程相同

2、什么是栈针?

一个方法就是一个栈针,也是每个方法运行所需要的内存。栈针是由方法中的参数、临时变量、返回地址等组成,当方法调用完成就会释放内存

3、入栈规则

栈针入栈的规则采用后进先出的原则,先入栈的是主方法,往后依次是被调用的子方法,所以最先出来的栈针是最后被进去的子栈针

4、什么是活动栈针

活动栈针就是当前线程正在运行的方法,每个线程只能允许有一个活动栈针

在这里插入图片描述

二、和栈相关问题的辨析

1、垃圾回收是否涉及栈内存?

不会涉及?因为栈就是方法的调用,当一个方法调用完成以后他会自动释放内存.

2、是否是栈内存越来越大就好?

答案是否定的。因为每个机器的内存是固定的。一个线程占用的栈内存是固定的,如果设置过大反而会使可访问的线程数变少。

虚拟机默认的栈的内存大小是1M,可通过-Xss 1M 设置内存的大小
3、方法内的局部变量是否线程安全?

线程安全与不安全是看,变量是否是私有的,如果是私有的则安全,否则不安全,以下面一段代码看下
/**
 * 局部变量的线程安全问题
 */
public class Demo {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append(4);
        sb.append(5);
        sb.append(6);
        new Thread(()->{
            m2(sb);
        }).start();
    }
    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;
    }
}

其中m1 是安全的,m2 和 m3 是线程不安全的
m2不安的原因是,从以下代码中看到

  public void  main (string[] args){
     StringBuilder sb = new StringBuilder();
     sb.append(4);
     sb.append(5);
     new Thread(()->{
      m2(sb);
     }     
  }

当主线程创建了个sb对象,将m2方法做了修改,不是属于私有的
m3 不安的原因是,将这个对象返回。可能被别的线程访问到。
结论:
(1)如果变量没有逃离了方法的范围,他是线程安全的
(2)如果是局部变量 引用了对象,并逃离了方法范围,则需要考虑线程安全的问题

三、栈内存溢出

1、栈针过多导致内存溢出
(1)方法的递归调用,没有合理的退出
(2)调用第三方的jar 包也能导致内存溢出,

  /**
 * json 数据转换
 */
public class Demo1_19 {

    public static void main(String[] args) throws JsonProcessingException {
        Dept d = new Dept();
        d.setName("Market");

        Emp e1 = new Emp();
        e1.setName("zhang");
        e1.setDept(d);

        Emp e2 = new Emp();
        e2.setName("li");
        e2.setDept(d);

        d.setEmps(Arrays.asList(e1, e2));

        // { name: 'Market', emps: [{ name:'zhang', dept:{ name:'', emps: [ {}]} },] }
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(d));
    }
}

class Emp {
    private String name;
    @JsonIgnore
    private 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 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;
    }
}

上述代码调用了jackson 的 ObjectMapper类,如不加 @JsonIgnore就会造成内存溢出

2、栈针过大导致的内存溢出
由于方法中参数、变量和返回值占用的内存很有限,一般不会出现这种情况

四、线程运行诊断

案例1 : cpu 占用过高
1、top 命令 查看进程号、cup 使用情况
在这里插入图片描述
通过上图可以看到,进程号为32655 的进程占用内存达99.5%
2、使用 ps H -eo pid(进程号) tip(线程号) %cpu |32655进程号 命令
在这里插入图片描述
可以看到线程号为:32665 的线程占用过高
3、 使用 jstack 32655 , 打印一些线程问题
在这里插入图片描述通过线程号找到对应的线程,由于通过ps 命令 找到的线程二进制的,而jstack 打印出来的是16进制,所以需要转换。32665 转换成16进制的7f99
在这里插入图片描述
通过上图看到第8行代代码有问题,打开代码看到一直空循环造成的
在这里插入图片描述

案例2: 程序长时间不响应
1、启动一段java 程序,
2、jps 找到进程号 32752,
3、使用 jstack 32752(如下图), 在最后看到,有死锁的问题

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值