深入理解JAVA虚拟机学习笔记——第二章Java内存区域与内存溢出异常

运行时数据区域

Java虚拟机在执行Java程序的过程中将其管理的内存划分为若干个不同的区域。各个区域有不同的用途,创建时间和销毁时间。
这里写图片描述

程序计数器
  • 程序计数器(Program Counter Register)是一块比较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器;
  • PCR为线程私有内存

  • 是唯一一个在Java虚拟机规范中没有规定任何OOM(OutOfMemoryError)情况的区域;

Java虚拟机栈

这里写图片描述

  • Java虚拟机栈也是线程私有,它的生命周期与线程相同。

  • Java虚拟机栈(Java Virtual Machine Stacks)描述的是Java方法执行的内存模型:每个方法在在执行的同时都会创建一个栈帧(Stack Frame)用于存储 局部变量表、操作数栈、动态链接、方法接口 等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈出栈的过程。

  • Java内存区常分为 堆内存(Heap)和栈内存(Stack);

  • OOM情况:(1)线程请求的栈深度>虚拟机所运行的最大深度;(2)虚拟机动态扩展时无法申请到足够的内存

本地方法栈

这里写图片描述

本地方法栈(Native Method Stack)与虚拟机所发挥的作用非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机所使用的Native方法服务。

  • HotSpot虚拟机把本地方法栈和虚拟机栈合二为一;

  • 此区域会抛StackOverflowError 和 OutofMemoryError异常

Java堆

这里写图片描述

Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块,Java Heap是所有线程共享的一块内存区域,在VM启动时创建。此区域的唯一母的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  • 所有的对象实例以及数组都要在堆上分配(不绝对:栈上分配、标量替换优化技术);

  • Java堆是垃圾收集器管理的主要区域,也可称做GC堆(Garbage Collected Heap)

  • 从内存回收的角度,现代收集器基本都采用分代收集算法,Java Heap可细分为新生代老年代,再细致可氛围Eden空间、From Survivor空间、To Survivor空间等–>更好回收内存。

  • 从内存分配的角度,线程共享的Java堆中可能分出多个线程私有的分配缓存区(TLAB:Thread Local Allocation Buffer)目的是为了更快分配内存。

  • Java堆出于逻辑连续的内存空间中,物理上可不连续,如磁盘空间一样;

  • Java堆在实现上可时,可以实现成固定大小的,也可以按照可扩展实现(-Xmx和-Xms控制);

  • OOM情况:堆中没有内存完成实例分配,堆也无法再扩展时。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的区域,它用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

方法区可以看做是描述堆的一个逻辑部分,它还有个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。

  • 永久代:JavaGC的分代收集机制分为3个代:年青代,老年代,永久代,将方法区定义为“永久代”,这是因为,对于之前的HotSpot Java虚拟机的实现方式中,将分代收集的思想扩展到了方法区,并将方法区设计成了永久代。不过,除HotSpot之外的多数虚拟机,并不将方法区当做永久代,随着Java8的到来,已放弃永久代改为采用Native Memory来实现方法区的规划。

  • 方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。

  • 一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,此区域回收目标主要是针对常量池的回收和对类型的卸载。

运行时常量池

这里写图片描述

运行时常量池(Runtime Constants Pool)是方法区的一部分。

  • Class文件中除了有类的版本、字段、方法、接口等描述的信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  • 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,运行期间也可以将新的常量放入池中。

  • OOM在常量池无法申请到内存时会抛出。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。

  • NIO(New Input/Output)类,引入一种基于通道与缓冲区的IO方式,能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

  • 直接内存的分配不会受到Java堆大小的限制,但会受到本机总内存(RAM以及SWAP/分页文件)大小以及处理器寻址空间的限制。

  • 设置Xmx等参数信息时注意不能忽略直接内存,不然会引起OOM。

HotSpot虚拟机对象探秘

对象的创建

这里写图片描述

虚拟机遇到一个new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。接下来JVM将为新生对象分配内存。

如果Java堆是规整的(所有用过的内存在一边,未使用的在另一边,维护麻烦),那么将使用“指针碰撞”的分配方式,否则使用“空闲列表”的分配方式。Java堆是否规整由采用的垃圾收集器是否带有压缩整理功能决定。

但是内存的分配是同步的,如果一个线程刚分配一个对象内存,但是还没有修改指针所指向的位置,那么另一个线程分配对象的时候可能就出错了。解决方法有两个,**一是对分配内存空间的动作进行同步处理(CAS方式)。另一种是把内存分配的动作按照线程划分在不同的空间进行,每个线程在java堆中预分配一小块内存,称为本地线程分配缓冲(TLAB)。只有TLAB用完并分配新的TLAB时,才需要同步。**JVM是否开启TLAB功能,可通过-XX:+/-UseTLAB参数来设定。

内存分配完之后,初始化零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。

接下来,JVM对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中,根据JVM当前运行状态不同,如是否启用偏向锁等,对象头会有不同的设置方式。

执行完new指令后接着执行init方法,把对象按照程序员的意愿进行初始化,这样一个对象就初始化完成了。

对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头、实例数据和对齐填充。

HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的存储官方称为Mark Word)。
另一部分是类型指针(即对象指向它的类元数据的指针,JVM通过这个指针来确定这个对象是哪个类的实例)。

如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。

接下来的实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容,在父类中定义的变量会出现在子类之前,如果CompactFields参数值为true,那么子类中较窄的变量也可能会插入到父类变量的空隙之中。

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。不满8个字节的时候占位。

对象的访问定位

我们的Java程序需要通过栈上的Reference数据来操作堆上的具体对象。Reference访问对象的方式目前主流的有两种:句柄和直接指针。

  • 如果直接使用句柄访问,java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象数据与类型数据各自的具体地址信息,如下图所示。

这里写图片描述

  • 如果使用直接指针访问,那么java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址,如下图所示。

这里写图片描述

这两种对象访问方式各有优势,使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。

使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。HotSpot虚拟机使用的是直接指针访问的方式。句柄来访问的情况也十分常见。

参考链接:
只吃全麦面包的人
深入理解JAVA虚拟机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值