一、栈的相关理解
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(如下图), 在最后看到,有死锁的问题