JVM系列之Java内存结构详解

7,251 阅读5分钟

Java内存结构详解

相信大多数Javaer对Java的内存结构都有一定的了解,但如果对于Java的内存结构只停留的"堆","栈"中显然是不够的。今天来给大家详细谈一谈Java的内存区域结构,本文基于 JDK7 的内存结构做讲解,JDK8的内存结构加上了metaspace,有些许变动,想详细了解的同学请自行翻阅相关资料。

文章结构

  1. 内存结构图
  2. 根据内存结构图各个区域做详细讲解

1 . 内存结构图

图片说明

  • 方法区,堆区(标绿)为所有线程共享的内存区域,虚拟机栈,本地方法栈,程序计数器(标蓝)为线程似有的内存区域,即线程隔离的。

2 . 各个区域详解

程序计数器

代码的运行是有顺序的,但当CPU在多线程间切换时,当从A线程切换到B线程,再切回到A线程时,CPU如何知道该从A线程的哪里继续执行呢?CPU工作时就是根据每个线程的程序计数器的值来选取下一条需要执行的字节码指令,即"找到它离开时的位置来继续执行"。需要提示的是,当CPU执行的是一个Java方法时,程序计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行的是Native方法,这个计数器值为Undefined,即不发挥作用。

虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
局部变量表存放了编译期间可以知道大小的各种类型变量,它所需要的内存空间大小在编译期间就已经分配,当一个方法被调用时,栈帧进入虚拟机栈,在运行期间,局部变量表大小是不会变化的。

本地方法栈

本地方法栈与虚拟机栈锁发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机执行Native方法服务。需要注意的是,由于虚拟机规范对于本地方法栈的具体实现没有做强制要求,所以Sun HotSpot直接把本地方法栈和虚拟机栈合二为一。

Java堆

Java堆是Javaer需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等),这里不做太多详细的介绍内存的分配与回收,后期有时间专门出博客讲解。在Java虚拟机规范中的描述是:所有对象实例以及数组都要在堆上分配。需要特别注意的是,线程共享的Java堆中可能分出多个线程私有的分配缓冲区(TLAB,这是为了并发分配内存时的脏分配问题,需要使用相关参数来开启。虚拟机默认使用CAS加上失败重试机制解决脏分配问题)。此外,Java堆在HotSpot中的实现是可扩展的。参数-Xmx/-Xms来控制。

方法区(永久代)

方法区用于存储已经被虚拟机加载的类信息,常量("zdy","123"等),静态变量(static变量)等数据。方法区有一个别名叫永久代(Permanent Generation),这是因为HotSpot设计团队把GC分代收集扩展至方法区,这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省钱专门为方法区编写内存管理代码的工作。对于其他虚拟机(J9)等,是没有永久代这个概念的。
永久代的配置参数: -XX:MaxPermSize

运行时常量池

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量("zdy","123"等)和符号引用。运行时常量池具有动态性,并非只有Class文件中的内容才能进入运行时常量池,运行期间也能将新的常量放入池中。如String.intern()方法。

附加--直接内存

直接内存不是Java虚拟机规范的内存区域。但是这部分也被Javaer频繁使用,而且也会导致OutOfMember异常。所以这里顺带提一提。
在JDK4中加入的NIO,使用了Native函数库直接分配堆外内存,然后通过一个缓存在Java堆中的Buffer来指向这块地址进行操作。这样能够避免Java堆和Native堆来回复制数据,在某些场景可以显著提高性能。
直接内存不受任何虚拟机参数控制,但很明显,你不能大于物理内存大小。

结语

JDK7的内存模型就大致介绍完了,JVM系列博客后期将带给大家更加深入的内容,下期预告:JVM系列之实战内存溢出异常