文章目录
一、内存结构图
二、数据结构-栈
数据结构中,栈的特点是什么?
简而言之:先进后出。
类比手枪的子弹夹
三、JVM栈
定义
1、每个线程运行时,所需要的内存。
2、每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存空间。
3、每个线程只能有一个活动栈帧,对应着程序当前执行的方法。
IDEA 演示
四、本地方法栈
那些不是由Java
编写的接口方法,比如用C语言或者C++语言
开发的本地方法
,让Java
可以通过调用本地方法,来间接与操作系统更底层的相关API
交互。
此时,运行本地方法所用的内存,就是本地方法栈。
比如Thread
类里面,Object
类里面的native
方法,都是本地方法。
五、问题辨析
1、垃圾回收是否涉及栈内存?
答:不会,因为栈是给方法运行时的内存空间,所以,当方法执行完毕时,会自动释放内存。故而不需要GC来清理空间。
2、栈内存越大越好吗?
答:不是的。
栈内存和线程并发数有着相关关系。
当JVM总内存一定时,栈内存越大,那么,对应的线程数就越少。
比如,500MB的JVM内存,那么,栈内存设置为1MB,并发线程数理论上是500个,如果栈内存设置为10MB,那么,并发线程数就是50个。
设置栈内存
-Xss1m
3、方法内的局部变量是否线程安全?
答:判断变量是否安全的原则是,这个变量是否被多线程共享。
所以,方法内的局部变量是线程安全的。它不会被多个线程共享。
但是,要注意
方法内的局部变量,作用域不能逃出方法外
,否则,依然是线程不安全的。
例如,入参和返参都是线程不安全的。
像下面的method1
是安全的,method2,method3的sb变量
是不安全的。
java
private static void method1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(1);
sb.append(1);
System.out.println(sb.toString());
}
private static void method2(StringBuilder sb) {
sb.append(1);
sb.append(1);
sb.append(1);
System.out.println(sb.toString());
}
private static StringBuilder method3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(1);
sb.append(1);
return sb;
}
4、栈内存溢出问题
1、栈内的栈帧过多,导致的内存溢出。
这种情况一般发生在递归调用
的时候。
错误复现
java
public class Demo2 {
private static int count;
public static void main(String[] args) {
try {
method();
} catch (Throwable e) {
e.printStackTrace();
System.out.println("运行次数:"+count);
}
}
private static void method() {
count++;
method();
}
}
还有可能是在对象数据格式化的时候出现。
比如,对象转json字符串。
如果出现,bean
互相套用,也会出现无限循环的情况,导致StackOverflowError
。
2、栈帧过大导致的溢出。
这种情况是,方法内的局部变量太大了,直接超过了栈的内存,导致的。
比如,一个方法内,String 变量的值特别大,就可以导致这个错误发生。