学习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.StackOverflowError
 * -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.StackOverflowError 并打印了2736。可见方法递归了2736次,也就是栈里有2736个方法时最终导致栈内存溢出。
2.栈帧过大导致栈内存溢出(一般不会出现,一个int变量才4个字节,而默认栈大小都1M左右)
一般不会出现,这里就不做演示了。

本地方法栈

在这里插入图片描述
定义: Java虚拟机调用本地方法时分配的内存空间
本地方法: 不是由Java代码编写的方法的方法。因为Java代码是有限制的,不能直接跟操作系统底层打交道,所以就需要用C,C++等语言编写的本地方法与底层打交道,Java代码可以间接的通过本地方法来调用到底层的功能。
如图:带native的方法就是本地方法

在这里插入图片描述

本篇为JVM开篇

>>下一篇:内存结构-堆(Heap)

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐