zhenlanghuo's Blog

深入理解java虚拟机(第二章 Java内存区域)

2017/03/06

《深入理解java虚拟机》的重点记录,方便复习


Java内存区域

201208212311249241.png-62.4kB

程序计数器(Program Counter Register)

  • 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器
  • 线程私有,每一个JVM线程都有独立的程序计数器,各线程间的计数器互不影响,独立存储,确保线程切换后能够恢复到正确的执行位置

Java虚拟机栈(Java Virtual Machine Stack)

  • 线程私有,与线程的生命周期相同
  • 描述java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
  • 局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(referencel类型)、和returnAddress类型(指向一条字节码指令的地址)。
  • 一般说的java栈内存就是指虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
  • 两种异常状况:
    • 线程请求栈深度大于虚拟机允许深度,抛出StackOverflowError异常
    • 虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,抛出OutOfMemory异常

本地方法栈(Native Method Stack)

  • 与虚拟机栈非常类似,区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,二本地方法栈则为虚拟机使用到的Native方法服务

Java堆(Java Heap)

  • Java虚拟机管理内存中最大的一块,所有线程共享的内存区域,随虚拟机的启动而创建
  • 该区域唯一目的是存放对象实例,几乎所有对象的实例都在堆里面分配。
  • Java堆是垃圾收集器管理的主要区域,被称作“GC堆”
  • 可以处于物理上不连续的内存空间中,只要逻辑上连续即可,如同磁盘空间一样,既可以实现成固定大小,也可以是扩展的,当前主流虚拟机都是按照扩展来实现的(通过-Xmx和-Xms控制)
  • 如果堆中没有内存完成实例分配,并且堆无法再扩展,抛出OutOfMemoryError异常

方法区(Method Area)

  • 各个线程共享的区域,用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据
  • 当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常

运行时常量池(Runtime Constant Pool)

  • 方法区的一部分
  • Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面常量和符号引用,这部分内容在类加载后存放到方法区的常量池中。
  • 当常量池无法申请到内存时抛出OutOfMemoryError异常。

直接内存(Direct Memory)

  • 直接内存并不是虚拟机运行时数据区域的一部分,也非Java虚拟规范中定义的内存区域,但这部分内存也被频繁使用,并且可能导致OutOfMemoryError异常出现

引用:一句代码说明Java栈、Java堆和方法区三个最重要的内存区域之间的关联关系

Object obj = new Object();

假设这句代码出现在方法体中:
1)“Object obj”这部分的语义反映到Java栈的本地变量表中,作为一个reference类型数据出现。
2)“new Object()”这个部分的语义反映到Java堆中,形成一块存储了Object类型所有实例数据值的结构化内存,以及查找到此对象类型的地址信息。
3)Object类的类型数据(如对象类型、父类、接口的实现、方法等)存储在方法区中。


HotSpot虚拟机对象探秘

对象的创建

检查

  • 虚拟机用到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

分配内存

  • 类加载检查通过后,虚拟机将为新生对象分配内存,对象所需内存大小在类加载完成后便可以完全确定。
  • 两种分配内存的方式:
    • 假如Java堆中内存是绝对规整的(用过和空闲的内存分开,中间放着一个指针作为分界点的指示器),采用指针碰撞(Bump the Pointer)的分配方式
    • 假如Java堆中内存不是规整的(用过和空闲的内存相互交错),采用空闲列表(Free List)的分配方式
    • Java堆时候规整又由采用的垃圾收集器是否带有压缩整理功能决定

对象的内存布局

  • 对象在内存中存储的布局分为3个区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)
  • 对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
  • 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。
  • 对齐填充并不是必然存在的,HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的整数倍,因此,对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

对象的定位访问

  • 两种对象访问方式:
    • 句柄式,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址
    • 直接指针式,reference中存储的直接就是对象地址
      1348659242_7055.jpg-95.2kB
      1348658605_5211.jpg-93.9kB
CATALOG
  1. 1. Java内存区域
    1. 1.1. 程序计数器(Program Counter Register)
    2. 1.2. Java虚拟机栈(Java Virtual Machine Stack)
    3. 1.3. 本地方法栈(Native Method Stack)
    4. 1.4. Java堆(Java Heap)
    5. 1.5. 方法区(Method Area)
    6. 1.6. 运行时常量池(Runtime Constant Pool)
    7. 1.7. 直接内存(Direct Memory)
    8. 1.8. 引用:一句代码说明Java栈、Java堆和方法区三个最重要的内存区域之间的关联关系
  2. 2. HotSpot虚拟机对象探秘
    1. 2.1. 对象的创建
      1. 2.1.1. 检查
      2. 2.1.2. 分配内存
    2. 2.2. 对象的内存布局
    3. 2.3. 对象的定位访问