JVM 内存区域划分如图所示,从图中我们可以看出:
- JVM 堆中的数据是共享的,是占用内存最大的一块区域。
- 可以执行字节码的模块叫作执行引擎。
- 执行引擎在线程切换时怎么恢复?依靠的就是程序计数器。
- JVM 的内存划分与多线程是息息相关的。像我们程序中运行时用到的栈,以及本地方法栈,它们的维度都是线程。
虚拟机栈:
- Java 虚拟机栈是基于线程的。哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。栈里的每条数据,就是栈帧。在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。可以理解为每个方法开辟一个独立的栈帧,这个栈帧里的局部变量不受其他栈帧的影响。这条线程开辟的虚拟机栈内存大小是所有的栈帧开辟的大小总和。
程序计数器
- 程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。这里面存的,就是当前线程执行的进度。在多线程任务中,cup在线程之间进行切换需要记录线程执行到哪了,等到线程重新分配到cpu时才能在上次执行的地方继续执行下去。
堆
- JVM 上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的。我们常说的垃圾回收,操作的对象就是堆。jvm堆空间大小可以指定,在启动时就会从系统内存中分配,分配的堆内存不一定全部会被用到。就好像一个公司包了电影院的三排座位,但是实际公司去了多少人不一定,但是这三排位置是预留给公司的人的,不是公司的人不能够使用。随着对象的频繁创建,堆空间占用的越来越多,就需要不定期的对不再使用的对象进行回收。这个在 Java 中,就叫作 GC(Garbage Collection)。对象在堆中被创建的时候大小是固定的,这里涉及到位置分配的问题,我们再用电影院场景模拟。计算机只能识别0和1,数据在内存中实际上是一连串的0和1,一个对象时一连串的0和1,当对象比较大的时候,这串数字就比较长,保存在内存中占的位置也就比较长,好比我们三个人去电影院看电影。作为电影院分配位置的方案,比较好的就是三个人坐一块,按顺序坐(1,2,3号位置)。新来的人继续往后面,如果来两个就分配(4,5号)位置,如果来三个就分配(4,5,6号)位置,把整个电影院划分为两个区域(已使用区域和未使用区域),不管已使用区域的人是否离开,新来的人安排去未使用区域,这种方案好处时管理方便,安排入座快,坏处就是如果有人中途离开,那对应的位置就空了下来不能利用,浪费了空间,对应内存中的内存碎片。
元空间
- 在 Java 8 之前,类的信息是放在一个叫 Perm (永久代)区的内存里面的。更早版本(java7之前),甚至 String.intern 相关的运行时常量池也放在这里。这个区域有大小限制,很容易造成 JVM 内存溢出,从而造成 JVM 崩溃。Perm 区在 Java 8 中已经被彻底废除,取而代之的是 Metaspace。原来的 Perm 区是在堆上的,现在的元空间是在非堆上的,这是背景。
元空间的好处也是它的坏处。使用非堆可以使用操作系统的内存,JVM 不会再出现方法区的内存溢出;但是,无限制的使用会造成操作系统的死亡。所以,一般也会使用参数 -XX:MaxMetaspaceSize 来控制大小。
方法区,作为一个概念,依然存在。它的物理存储的容器,就是 Metaspace。我们将在后面的课时中,再次遇到它。现在,你只需要了解到,这个区域存储的内容,包括:类的信息、常量池、方法数据、方法代码就可以了。
本地方法栈
- 本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
-Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。
参考资料:1.《深入浅出 Java 虚拟机》
2.https://www.jianshu.com/p/8a775d747c47