JAVA内存管理机制

142 阅读4分钟

JAVA虚拟机在执行JAVA程序的过程中会把其所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途和生命周期。JAVA虚拟机所管理的内存包括以下几个运行数据区域。

image.png

程序计数器

程序计数器是一块内存区域,可以看做当前线程所执行的字节码的行号指示器。为了线程切之后能够恢复到正确的执行位置,因此每条线程都需要一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。程序计数器是线程私有的。

程序计数器作用是记住下一条 jvm指令的执行地址。程序计数器是线程私有,不会存在内存溢出(OutOfMemeryError)。

虚拟机栈

与程序计数器相同,JAVA虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈描述的是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;
    }
}

v2-1d755e1460f6d68c1eeb65b8ae2c2391_1440w.png 虚拟机栈中局部变量表存放了编译期可知的各种数据类型,对象引用(是引用类型,不等同于对象本身,可能是一个指向对象起始地址的引用指针,或者是指向一个代表对象的句柄或其他与此对象相关的位置)。局部变量表所需的内存空间是在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量是完全确定的,在方法运行期间不会改变局部变量表的大小。

JAVA虚拟机规定了两种异常:线程请求的栈深度大于虚拟机所允许的深度造成的StackOverflowError异常;可以通过设置VM参数 -Xss设置虚拟机栈的大小。

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();
    }
}

image.png 虚拟机动态扩增无法申请到足够内存时造成的OutOfMemoryError异常。

每个线程只有一个活动栈帧,对应当前执行方法。

其次JAVA垃圾回收不涉及栈内存垃圾回收,垃圾回收只会回收堆内存中的内存占用,栈内存不需要,栈帧运行完会自动出栈。

栈内存大小需要合理设置,因为系统内存有限,栈内存过大,会导致分配线程数变少,因为一个线程对应一个虚拟机栈。栈内存过小会容易导致StackOverflowError错误。

方法内的局部变量是否线程安全?如果方法内局部变量没有逃离方法的作用访问,它是线程安全的。如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。

public class Demo1_18 {
	static int x = 0;
	public static void main(String[] args) throws Exception{
		 new Thread(()->{
	            m1();
	     }).start();
		 
		 new Thread(()->{
				m1();
	     }).start();
	}
    // 多个线程同时执行此方法
    static void m1() {
    
        for (int i = 0; i < 500000; i++) {
            x++;
            
        }
        System.out.println(x);
    }

这里x不是线程安全的。

本地方法栈

本地方法栈与虚拟机栈所发挥的作用基本相似,也是线程私有,虚拟机栈为虚拟机执行JAVA方法服务,二本地方法栈使用Native方法服务。对本地方法栈使用的语言和方法没有强制规定,不同虚拟机有不同的实现方式,本地方法栈也会抛出StackOverflowError和OutOfMeMemoryError错误。

JAVA堆

JAVA堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,是虚拟机管理的内存中最大的一块。堆用于存放对象实例,几乎所有的对象实例都在这里分配内存。虚拟机的垃圾回收主要是对JAVA堆进行回收,JAVA堆在可以处于物理上不连续的内存空间,只要逻辑上联系即可。可以通过设置VM参数-Xmx设置堆的大小。

方法区

方法区与JAVA堆一样是所有线程共享的,用于存储已经被虚拟机加载的类信息,常量,静态变量,编译器编译后的代码等数据。