JVM架构及内存模型详解

JVM内存架构

jvm内存模型 java线程模型 jvm 的内存模型_jvm

JVM架构主要分成了三个部分:

  1. JVM内存模型,主要包括了方法区、堆、虚拟机栈、程序计数器、本地方法栈。在下面进行展开。
  2. 执行引擎,包括最核心的解释器和GC垃圾回收器,还包括了JIT编译器。
  3. 本地方法接口和库

JVM内存模型

这里我们将结合着第二张图一起来看。

jvm内存模型 java线程模型 jvm 的内存模型_jvm内存模型 java线程模型_02

方法区

在JVM的规范里是存储一些常量的区域,主要是类常量。因为运行时也可以产生常量,比如我们最常用的String,所还包括了Runtime Constant Pool。最后它还包括了Code Cache一部分,这部分是存储JIT优化编译的内容。

存储在哪? 这个地方来说是存储在堆上,因为非栈即堆嘛。但是因为GC并不经常需要管理这块区域,所以才有了一个逻辑的区分,好让GC能能够识别。这也就是为什么要称之为永久代的原因。

那为什么永久代最后也取消了呢?
这个是在JDK7的时候与JRockit融合时发现,JRockit没有永久代,所以它也取消了,使用了新的元空间来替代,不继续在堆上,而是放入到直接内存里。主要带来的好处是:分配内存更灵活。这里有一个要注意的点是-XX:MetaspaceSize的作用,从Oralce文档我们可以看到这是什么时候触发一次Full GC,而不是最大值。根据平台的不同,默认值是12~20MB。
而真正用来限制元空间大小的参数是-XX:MaxMetaspaceSize。

这个很都熟悉,是用来存储我们对象的地方。也是垃圾回收主要的区域,分成年轻代和老年代。

这里要注意的有两个点:

  • 由于优化编译器的进步,现在对象在堆上分配已经不是那么绝对。
  • TLAB(Thread Local Allocation Buffer)这个概念,指的是在Heap每个线程可以拥有自己独立的线程分配缓存,可以更高效的分配内存。

虚拟机栈、本地方法栈、程序计数器

这两个都是线程私有的。

在第二张图中可以看到,程序计数器比较小,它主要负责的是当前线程所执行的字节码的行号指示器。如果熟悉操作系统的CPU指令暂存对这个理解起来会比较容易,主要还是CPU的分片时间机制,所以每一个并发需要保存自己的指令和数据。程序计数器指向的是下一条指令的内存地址。

虚拟机栈和本地方法栈都是栈,区别是本地方法栈是Native的方法。

虚拟机方法栈描述的是Java方法执行的内存模型,每一个方法被执行的时候,都会创建一个桢(Frame)。桢的信息主要包括了返回值,局部变量表,元信息引用,操作数栈。

局部变量表中的对象会指向堆,也会指向运行时常量池(Runtime Constant Pool)。

运行时常量池

其实目的是很简单,就是对一些常量做缓存,避免重复生成,在JVM的规范我们可以看到是放在方法区的,但是Sting字符串是比较特殊的。我这里对它的理解是String类型的长度不固定,所以在正常GC过程中价值会比较大。

此外,CodeCache 这里主要是JIT编译器的结果缓存,避免重复编译指令,可以提高性能。

一些问题

  1. 基本类型存储在哪? 是线程私有的吗?
    基本类型要分成是函数执行过程的还是对象的字段,前者会存储在栈上,后者会存储在Heap上。
  2. 为什么要有基本类型呢?主要基本类型用的比较多,分配对象动作比较重,所以为了性能考虑。
    我们常说变量会共享是为什么呢? 这里是指栈上可以有多个变量指向同一个基本类型