0%

深入理解JVM之Java内存区域

JVM 内存区域模型

JVM 内存区域基本上由4个区域组成:

  • 类加载器:在JVM启动时或者类运行时,将需要的class加载到JVM中。
  • 运行时数据区:将内存划分成若干个区以模拟实际机器上的存储、记录和调度功能模块。
  • 执行引擎:负责执行class文件中包含的字节码指令,相当于实际机器上的CPU。
  • 本地方法调用:执行C/C++实现的本地方法的代码,并返回结果。

Java 运行时数据区域

程序计数器

Java 线程私有,类似于操作系统里的 PC 计数器,它可以看做是当前线程所执行的字节码的行号指示器。

  • 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。
  • 此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

Java虚拟机栈

Java线程私有,虚拟机栈描述的是 Java 方法执行的内存模型:

  • 每个方法在执行的时候,都会创建一个栈帧用于存储局部变量、操作数、动态链接、方法出口等信息。
  • 每个方法调用都意味着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈

和 Java 虚拟机栈的作用类似,区别是该区域为 JVM 提供使用 Native 方法的服务。

Java堆

所有线程共享的一块区域,垃圾收集器管理的主要区域。

  • 目前主要的垃圾回收算法都是分代收集算法,所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等,默认情况下新生代按照 8:1:1 的比例来分配。
  • 根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘一样。

元数据(方法区)

各个线程共享的一个区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  • 虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

运行时常量池

是元数据的一部分,用于存放编译器生成的各种字面量和符号引用。

直接内存

并不是虚拟机运行时数据的一部分。JDK1.4引入了NIO,它可以使用Native函数库直接分配堆外内存。直接内存的分配不会受到Java堆大小的限制,但是会受到本机内存的限制。也可能导致OutOfMemoryError出现。

Java对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:

  • 对象头(Header)
  • 实例数据(Instance Data)
  • 对齐填充(Padding)

对象头

HotSpot虚拟机对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID、偏向时间戳等,官方称为“Mark Word”。

对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据

是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录下来。

对齐填充

并不是必然存在,仅仅起着占位符的作用。

对象的访问

Java对象需要通过栈上的reference数据来操作堆上的具体对象。对象的具体访问方式取决于虚拟机的实现。主流的访问方式有使用句柄和直接指针两种。

使用句柄访问对象

使用直接指针访问对象

栈帧

栈帧是用来存储数据和部分过程结果的数据结构,同时也用来处理动态链接(dynamic linking)、方法返回值和异常分派(dispatch exception)。

栈帧随着方法调用而创建,随着方法结束而销毁。栈帧的存储空间
由创建它的线程分配在Java虚拟机栈之中。每一个栈帧都有一个本地变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用。

局部变量表

每个栈帧内部都包含一组称为局部变量表的变量列表。

一个局部变量表可以保存一个类型为boolean、byte、char、short、int、float、reference或returnAddress的数据,两个局部变量可以保存一个类型为long或double的数据。

局部变量使用索引来进行定位访问,首个局部变量的索引值为0。

Java虚拟机使用局部变量表来完成方法调用时的参数传递。当调用类方法时,它的参数将会依次传递到局部变量表中从0开始的连续位置上。当调用实例对象方法时,第0个局部变量一定会用来存储该实例方法所在对象的引用(即this关键字)。后续的其他参数将会传递至局部变量表中从1开始的连续位置上。

操作数栈

每个栈帧内部包含一个称为操作数栈的后进先出栈。

随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。

动态链接

在class文件里,一个方法若想调用其他方法,或者访问成员变量,则需要通过符号引用(symbolic reference)来表示,动态链接的作用就是将这些以符号引用所表示的方法转换为对实际方法的直接引用。

iisheng wechat
微信扫码关注 Coder阿胜