欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一起学习JVM-内存结构-程序计数器/虚拟机栈

程序员文章站 2022-06-07 08:48:13
...

学习JVM的第一天,首先先了解下JVM。

什么是JVM?

定义: JVM全称Java Virtual Machine(Java程序的运行环境(Java二进制字节码的运行环境))
好处:
1.一次编译,到处运行(跨平台)
2.自动内存管理,垃圾回收功能
3.数组下标越界检查
4.多态
比较: JVM,JRE,JDK
JVM<JRE(JVM+基础类库)<JDK(JVM+基础类库+编译工具)

程序计数器

一起学习JVM-内存结构-程序计数器/虚拟机栈
定义: Program Counter Register 程序计数器(寄存器)
程序计数器是Java里虚拟抽象的名词,实际是用cpu的寄存器来当做程序计数器。
程序执行流程:java源代码---->Jvm指令---->解释器---->机器码---->CPU
作用: 记住下一条jvm指令的执行地址
特点:
1.是线程私有的,每条线程都有自己的程序计数器
2.根本不会存在内存溢出的情况

一起学习JVM-内存结构-程序计数器/虚拟机栈

虚拟机栈

一起学习JVM-内存结构-程序计数器/虚拟机栈
定义: Java Virtual Machine Stacks (Java 虚拟机栈)
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
栈: 线程运行需要的内存空间(一个栈里可以有多个栈帧)
栈帧: 每个方法运行时需要的内存(包含参数,局部变量,返回地址)
一起学习JVM-内存结构-程序计数器/虚拟机栈
代码演示:

/**
 * 演示栈帧
 */
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运行见:
一起学习JVM-内存结构-程序计数器/虚拟机栈
如图:进栈顺序: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左右)
一般不会出现,这里就不做演示了。
最后说一句:下次再见,铁子

相关标签: JVM