第二章 Java内存区域与内存溢出异常

2.2 运行时数据区域

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

2.2.1 程序计数器

是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。这块内存区域是线程私有的

2.2.2 Java 虚拟机栈

  • 线程私有
  • 描述的是Java方法执行的内存模型
  • 用于存储局部变量表、操作数栈、动态链接、方法出口等信息
  • 局部变量表所需的内存空间在编译期完成分配
  • 对于这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
  • 如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常

2.2.3 本地方法栈

  • 描述的是虚拟机执行Native方法服务

2.2.4 Java堆

  • 是Java虚拟机管理的内存中最大的一块
  • 是被所有线程共享的内存区域,在虚拟机启动时创建
  • Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆

2.2.5 方法区

  • 是各个线程共享的内存区域
  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码

2.2.6 运行时常量池

  • 是方法区的一部分
  • 用于存放编译期生成的各种字面量和符号引用
  • 具备动态性,Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中

2.3 HotSpot虚拟机对象探秘

  • 对象的创建
  • 对象的内存布局
  • 对象的访问定位

2.3.1 对象的创建

  • 虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且查看这个引用是否已被加载、解析和初始化,如果没有,那必须先执行相应的类加载过程
  • 为新生对象分配内存,大小在类加载完成后便可完全确认。目前有两种做法,使用哪种方式是由 GC 回收器是否带有压缩整理功能决定的:
    • 指针碰撞(Bump the Pointer):没用过的内存和用过的内存用一个指针划分(所以需要保证 java 堆中的内存是整理过的,一般情况是使用的 GC 回收器有 compact 过程),假如需要分配8个字节,指针就往后挪8个字节
    • 空闲列表(Free List):虚拟机维护一个列表,记录哪些内存是可用的,分配的时候从列表中遍历,找到合适的内存分配,然后更新列表
  • 同时需要考虑对象创建在虚拟机中是否频繁, 引入一个新的问题并发,就刚才的一个修改指针操作,就会带来隐患:对象 A 正分配内存呢,突然!!对象 B 又同时使用了原来的指针来分配 B 的内存。解决方案也有两种:
    • 同步处理——实际上虚拟机采用 CAS 配上失败重试来保证更新操作的原子性
    • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,成为本地线程分配缓存(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配,用完并分配新的TLAB时,才需要同步锁定(虚拟机是否使用 TLAB,可以通过-XX:+/-UseTLAB 参数来设置)
  • 内存分配完成后,需要将分配的内存空间都初始化为0

2.3.2 对象的内存布局

在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instantce Data)、对齐补充(Padding)。

  • 对象头(Header):包含两部分信息。第一部分用于存储对象自身的运行时数据,如 hashcode 值、GC 分代的年龄、锁状态标志、线程持有的锁等,官方称为“Mark Word”。第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
  • 实例数据(Instance Data):就是程序代码中所定义的各种类型的字段内容
  • 内存对齐,这个在前面博文中已经说过好多次了,不懂的可以去看看即可

2.3.3 对象的访问定位

  • 假如代码出现在方法体中,那么Object obj就会存在在Java虚拟机栈的本地变量表中,作为一个引用类型数据。
  • new Object()则存在在Java堆上。另外,在Java堆上还必须包含能查找到该对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
  • 由于引用类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式可能不同,主流的有:
    • 使用句柄:Java堆中划分一块区域作为句柄池,引用存储的是对象的句柄地址,而句柄中含有对象实例数据和类型数据各自的数据信息
    • 直接指针:引用中直接存储的就是对象的地址,同时还必须包括方法区类型信息的指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值