jvm

JVM 系列 内存管理之内存区域

开启JVM探索新篇章

Posted by lichao modified on November 7, 2019

存储概览

程序计数器

特点:线程私有

记录当前线程所执行的字节码指令的地址。字节码解释器的工作就是通过改变这个计数器来选取下一条需要执行的字节码指令。

注意: 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 native 方法,这个计数器值则为空。

虚拟机栈

特点:线程私有

描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,不等同于对象本身)和 returnAdress类型(指向一条字节码指令的地址)。局部变量表所需内存空间在编译期间确定,在方法运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变。

注意: Java内存模型 要求 lock、unlock、read、load、assign、use、store、write 这8个操作都具有原子性,但是对于64位的数据类型(long和double),在模型中特别定义了一条相对宽松的规定:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为2次32位的操作来执行,即允许虚拟机实现选择可以不保证64位数据类型的read、store、load和write这4个操作的原子性。这就是所谓的long和double的非原子性协定(Nonatomic Treatment of double and long Variables)

本地方法栈

为虚拟机使用到的 native 方法服务。

Java堆

特点:线程共享 用来保存对象的实例(保存对象实例的属性值、属性类型和对象类型标记等)。是垃圾收集器管理的主要区域。

注意:根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

方法区

特点:线程共享 存储概览

JDK1.2 ~ JDK6

在JDK1.7及以前,HotSpot虚拟机将 Java 类信息、常量池、静态变量、即时编译器编译后的代码等数据,存储在Perm(永久代)里(对于其他虚拟机如BEA JRockit、IBM J9等是不存在永久代概念),类的元数据和静态变量在类加载的时候被分配到Perm里,当常量池回收或者类被卸载的时候,垃圾收集器会回收这一部分内存,但效果不太理想。

存储概览

《Java虚拟机规范》规定了方法区的概念及作用,并没有规定如何去实现它。在不同的 JVM 中方法区的实现可能不同。 大多数用的 JVM 都是 HotSpot。在 HotSpot 上把 GC 分代收集扩展至方法区,或者说使用永久代来实现方法区。因此,永久代是 HotSpot 的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现。其他的虚拟机实现并没有永久带这一说法。在1.7之前在(JDK1.2 ~ JDK6)的实现中,HotSpot 使用永久代实现方法区,HotSpot 使用 GC 分代来实现方法区内存回收。

Java7

JDK1.7 中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

Java8

jdk 1.8 中则把永久代给完全删除了,取而代之的是 MetaSpace。

JDK 1.8 时,HotSpot 虚拟机对 JVM 模型进行了改造,将类元数据放到了元空间 MetaSpace(本地内存 native memory)中,将常量池和静态变量放到了 Java 堆里,HotSpot VM 将会为类的元数据明确的分配与释放本地内存,在这种架构下,类元数据就突破了 -XX:MaxPermSize 的限制,所以此配置已经失效,现在可以使用更多的本地内存。这样一定程度上解决了原来在运行时生成大量的类,从而经常 Full GC 的问题——如运行时使用反射、代理等。 存储概览

论点:JDK 1.8 为什么要把方法区从JVM里(永久代)移到直接内存(元空间)
  • 原因一:因为直接内存,JVM将会在IO操作上具有更高的性能,因为它直接作用于本地系统的IO操作。而非直接内存,也就是堆内存中的数据,如果要作IO操作,会先复制到直接内存,再利用本地IO处理。
    • 从数据流的角度,非直接内存是下面这样的作用链:本地IO –> 直接内存 –> 非直接内存 –> 直接内存 –> 本地IO
    • 而直接内存是:本地IO –> 直接内存 –> 本地IO
  • 原因二:整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError
    • 可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
    • -XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。

直接内存

本机直接内存的分配不会受到 Java 堆大小的限制。 在 JDK 1.4 中新加入了 NIO 类,引入了一种基于 channel 和缓冲区 buffer 的 IO 方式,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 directByteBuffer 对象作为这块内存的引用的操作。