一起学习JVM-内存结构-程序计数器/虚拟机栈
学习JVM的第一天,首先先了解下JVM。
什么是JVM?
定义: JVM全称Java Virtual Machine(Java程序的运行环境(Java二进制字节码的运行环境))
好处:
1.一次编译,到处运行(跨平台)
2.自动内存管理,垃圾回收功能
3.数组下标越界检查
4.多态
比较: JVM,JRE,JDK
JVM<JRE(JVM+基础类库)<JDK(JVM+基础类库+编译工具)
程序计数器
定义: Program Counter Register 程序计数器(寄存器)
程序计数器是Java里虚拟抽象的名词,实际是用cpu的寄存器来当做程序计数器。
程序执行流程:java源代码---->Jvm指令---->解释器---->机器码---->CPU
作用: 记住下一条jvm指令的执行地址
特点:
1.是线程私有的,每条线程都有自己的程序计数器
2.根本不会存在内存溢出的情况
虚拟机栈
定义: Java Virtual Machine Stacks (Java 虚拟机栈)
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
栈: 线程运行需要的内存空间(一个栈里可以有多个栈帧)
栈帧: 每个方法运行时需要的内存(包含参数,局部变量,返回地址)
代码演示:
/**
* 演示栈帧
*/
public class Demo1_1 {
public static void main(String[] args) throws InterruptedException {
method1();
}
private static void method1() {
method2(1, 2);
}
private static int method2(int a, int b) {
int c = a + b;
return c;
}
}
程序debug运行见:
如图:进栈顺序:main方法先压栈,之后method1方法进栈,最后method2进栈并携带两个参数a=1和b=2。
出栈顺序:method2出栈–>method1出栈–>最后main方法出栈–>程序结束
问题
1.垃圾回收是否涉及栈内存?
答:不涉及。因为栈内存无非就是一次次的方法调用所产生的栈帧内存,而栈帧在每一次方法调用结束后都会被弹出栈,根本不需要垃圾回收来管理。(垃圾回收只回收堆内存中无用的对象)
2.栈内存分配的越大越好吗?
答:肯定不是,栈内存分配越大,反而会让线程数变少。因为物理内存大小是一定的,比如说,一个线程使用的是栈内存吧,假设使用1M的内存,物理内存500M,理论上就可以有500个线程同时运行。如果每个线程设置2M,那么只能同时有250个线程运行。所以栈内存分配越大并不是越好,它分配大了通常只是能够进行多次的方法递归调用,而不会增快程序的运行效率,反而会影响线程数目的变少。一般采用默认的就可以,不必在程序启动的时候手动修改。
3.方法内的局部变量是否线程安全?
答:分两种情况:
1.如果方法内局部变量没有逃离方法的作用访问,它是线程安全的。
2.如果是局部变量引用了对象,或逃离方法的作用范围,需要考虑线程安全。
*逃离方法:*在方法结束的时候把这个变量return出去
演示代码:
/**
* 局部变量的线程安全问题
*/
public class Demo1_17 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append(4);
sb.append(5);
sb.append(6);
new Thread(()->{
//m1();
m2(sb);
//m3();
}).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是不安全的。
栈内存溢出
导致栈内存溢出有两种情况:
1.栈帧过多导致栈内存溢出(递归操作,大量方法进栈)
如下代码:
/**
* 演示栈内存溢出 java.lang.*Error
* -Xss256k
*/
public class Demo1_2 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();
}
}
运行结果:java.lang.*Error 并打印了2736。可见方法递归了2736次,也就是栈里有2736个方法时最终导致栈内存溢出。
2.栈帧过大导致栈内存溢出(一般不会出现,一个int变量才4个字节,而默认栈大小都1M左右)
一般不会出现,这里就不做演示了。
最后说一句:下次再见,铁子