转载

重走JAVA之路(七):你要的JAVA内存结构

虽然作为Java程序员,不用太多关心Java内存管理这一块,是由JVM自动管理,这一点确定比C++深得我心(最近在学C++,很痛苦,不说了....),不了解JVM的内存结构和各个内存区域的工作职责,将对解决问题带来很大的麻烦,先来张图表示下,《深入理解Java虚拟机(第2版)》中的描述是下面这个样子的:

重走JAVA之路(七):你要的JAVA内存结构

1.程序计数器

程序计数器属于内存中比较小的一块空间,属于线程私有的,其作用可以大概理解为记录当前线程所执行的字节码位置,或者通俗来说可以理解为代码执行到第几行了,为什么需要这么一小块空间做这种事情呢,因为JVM的多线程操作实际上并不是真正意义上的并行,是通过线程轮流切换并分配CPU执行时间片的方式来实现的,也就是一个CPU在某一个时间点只会执行一个线程中的指令,那么在线程切换后,不知道自己之前执行的位置,岂不是很懵逼。。所以每个线程都需要有一个独立的程序计数器存储,而且注意,此区域是唯一不会OutOfMemoryError情况的区域,因为程序计数器是由虚拟机内部维护的。

2.虚拟机栈

栈也是属于线程私有的一块区域,既然是栈,那里面肯定是要放东西的,虚拟机栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表、操作数栈、动态链接、方法出口等信息。常常所说的栈内存也就是局部变量表这一部分内存空间,随着方法的调用到执行的完成,也就对应着栈帧的进栈出栈

这里需要注意一点,每个栈帧的大小是在编译期就已经分配好了的,运行时并不会改变其内存的大小。

栈的内存在固定大小的情况下,如果调用深度大于JVM的范围,就会抛出StackOverflowError异常,我们来模拟一下看看

public class TestClass {
    public static void main(String []agrs){
        new TestClass().testStackOverFlowError();
    }
    public void testStackOverFlowError(){
        System.out.println("run");
        testStackOverFlowError();
    }
}

Exception in thread "main" java.lang.StackOverflowError
atcom.example.hik.lib.TestClass.testStackOverFlowError(TestClass.java:9)
复制代码

可以看到直接就抛出异常了

3.本地方法栈

和虚拟机栈其实类似,只不过一个是执行Java方法服务,而本地方法栈是执行native方法的,其他就不过多介绍了,这块区域涉及的比较少,但是与虚拟机栈一样,本地方法栈也会抛StackOverflowError和OutOfMemoryError异常

4.堆

堆算是整个JVM管理的最大的一块区域,是属于线程共享区的,也称GC堆,在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)

从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen),具体回收算法就不多介绍了

5.方法区

方法区也是线程共享区,用于存储【虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态变量,即时编译器编译后的代码等数据】,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

二、实例

public class TestClass {
    public static void main(String []agrs){
        A a =new A();
		a.test();
    }
}

复制代码

有这么一个类A,里面有个test方法,那么在执行这两段代码时,具体流程是怎么样的呢?

  • 首先,创建一个A对象,运行时JVN先会去方法区找A的类型信息,如果没有找到,那说明类A之前没有被加载过,则使用Classloader将A.class字节码文件加载到内存的方法区,将A类的类型消息存放至方法区,方便下次查找

  • 第二步,JVM会在堆中给A的实例对象分配内存空间,一般是会被分配在新生代区域,这个实例是有着指向方法区中的A类型信息的引用,也就是第一步中的信息,指向方法区的内存地址

  • 第三步,因为当前方法肯定是执行在一个线程中的,那么这个线程创建的同时,也创建一个虚拟机栈,在调用一个方法的时候就会创建一个栈帧,上面的a是A对象的引用,并持有着堆中A对象的内存地址

  • JVM通过a引用找到堆中A对象的内存地址,找到A对象实例,再通过指向方法区中地址的引用找到A类型信息,拿到test()方法的字节码信息,然后再执行test()方法

原文  https://juejin.im/post/5cb03138f265da0378758680
正文到此结束
Loading...