《深入理解java虚拟机》读书笔记(一):Java内存区域

适用对象

java入门阶段的小白、看过原书后需要快速复习的同学。

运行时数据区域

在这里插入图片描述

  • 程序计数器:“线程私有”,如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
  • Java虚拟机栈:Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时 都会创建一个栈帧(Stack Frame[1])用于存储局部变量表(基本数据类型,对象引用,returnAddress类型)、操作数栈、动态链接、方法出口 等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 可抛出StackOverflowError异常和OutOfMemoryError异常。
  • 本地方法栈:与虚拟机栈所发挥的作用是非常相似,而本地方法栈则为虚 拟机使用到的Native方法服务。本地方法 栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
  • Java堆:是Java虚拟机所管理的内存中最大的一块。 Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。所有的对象实例以及数组都要在堆上分配。(现在已经不那么绝对了)从内存回收的角度来看,所以Java堆中还可以细分为:新生代和老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上 是连续的即可。会抛出OutOfMemoryError异 常。
  • 方法区:它用于存储已被虚 拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以 选择固定大小或者可扩展外,还可以选择不实现垃圾收集。当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError异常。
  • 运行时常量池:用于存放编译期生成的各种字面量和符号引用。Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。当常量池无法再申 请到内存时会抛出OutOfMemoryError异常。
  • 直接内存:它可以使用Native函数库直接分配堆外内存,然后通过一个存储 在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。既然是内存,肯定还是 会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制)将导致动态扩展时出现OutOfMemoryError异常。

对象创建

仅限制于普通java对象,不包括数组和class对象。
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一 个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。为对象分配空间的任务等同于把 一块确定大小的内存从Java堆中划分出来。分为:指针碰撞空闲列表两种方式。选择哪种分配方式由 Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决 定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针 碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。
如何确保线程安全? 解决这个问题有两种方案,一种是对分配内存空间的动作进行同步处理 ——实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;另一种是把内存分 配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内 存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内 存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找 到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对 象头(Object Header)之中。
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从Java程 序的视角来看,对象创建才刚刚开始——<init>方法还没有执行,所有的字段都还为零。 所以,一般来说(由字节码中是否跟随invokespecial指令所决定),执行new指令之后会接着 执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完 全产生出来。

对象的内存布局

对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。

  • 对象头:包括两部分信息,第一部分用于存储对象自身的运行时数据。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指 针来确定这个对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型 指针,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。
  • 实例数据:对象真正存储的有效信息,也是在程序代码中所定义的各种类 型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
  • 对齐填充: 并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。当对象实例数据部分没有对齐时(对象起始地址必须是8的字节的整数倍),就需要通过对齐填充来补全。

对象访问定位

我们的Java程序需要通过栈上的reference数据来操作堆上的 具体对象。对象访问方式也是 取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种。
句柄访问
指针访问
使用句柄来访问的最大好处就是reference中存储的是稳 定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中 的实例数据指针,而reference本身不需要修改。 使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销, 由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成 本。就本书讨论的主要虚拟机Sun HotSpot而言,它是使用第二种方式进行对象访问的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值