前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java虚拟机内存管理(二)—堆的使用

Java虚拟机内存管理(二)—堆的使用

作者头像
Wizey
发布2018-09-29 09:44:50
5130
发布2018-09-29 09:44:50
举报
文章被收录于专栏:编程心路

Java 与 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的 “高墙”,墙外面的人想进去,墙里面的人却想出来。——《深入理解Java虚拟机:JVM高级特性与最佳时实践(第二版)》周志明

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以作为一个 Java 程序员要比 C++ 程序员幸福,但是内存方面一旦出现问题,如果对虚拟机怎样使用内存不了解,就很难排查错误。

这段时间看周志明先生的《深入理解Java虚拟机:JVM高级特性与最佳时实践(第二版)》,下面就对 Java 虚拟机对内存的管理做一个系统的整理,本篇文章是该专题的第二篇。

2、堆的使用

对 Java 堆使用,也即是对象创建时使用这一部分的内存,语言层面上,对象的创建只是一个 new 关键字,但是在 Java 虚拟机(这里指的是主流的 HotSpot 虚拟机)中的对象(这里讨论的对象不包括数组和 Class 对象)创建过程却不是这么简单的。

2.1 给对象分配内存

在虚拟机遇到一条 new 指令时,首先会去检查这个指令的参数是否能在方法区的常量池中找到这个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析(符号引用替换成直接引用)和初始化。如果没有,那必须执行相应的类加载过程。在类加载检查后,虚拟机才会为新生的对象在 Java 堆区域分配内存。

对象所需的内存大小在类加载完成后就可以确定下来,为对象分配内存空间相当于把一块确定大小的内存从 Java 堆中分出来。这个划分不一定是规整的,也即不是已经使用过的内存(已经分给对象的)放在一边,空闲的内存放在一边,已经使用过的内存和空闲的内存很可能是相互交错的,这就需要一个列表来维护,记录哪些内存块是可以用的,哪些内存块已经被对象占用了,在这个过程中很可能会产生一些内存碎片。根据 Java 堆中的内存是否规整,有“指针碰撞”和“空闲列表”两种分配方式,而 Java 堆是否规整,又是由所采用的垃圾收集器是否具有内存压缩整理功能决定的。对象内存的划分除了需要考虑内存空间外,还需要考虑引用对象的指针。在给一个对象A分配内存时使用的指针P,绝对不能被另外一个对象B同时使用来分配B对象所需的内存,也即是内存的分配需要是一个原子性操作,这样才能在并发情况下保证线程的安全。当然解决这个问题,也有不同的方法,一种是对分配内存空间的动作加锁进行同步处理,另一种是把内存分配的动作按照线程划分,每个线程在 Java 堆中都预先分配一小块内存区域,称为是“本地线程分配缓冲”(TLAB),哪个线程要给对象分配内存就在该线程的 TLAB 上分配。

2.2 初始化对象

给对象分配完内存后,虚拟机还需要将分配到的内存空间初始化为零(不包括对象头)。这一步操作保证了对象实例字段可以在不赋初值的情况下,就可以访问到这些字段的零值。对象的对象头中存放着这个对象是属于哪个类的实例,怎样找到类的元数据、对象的哈希码,对象在垃圾收集器中分代的年龄等信息。经过虚拟机为我们初始化对象后,一个对象可以说是诞生了,但是对于我们程序员来说,还需要根据我们在程序中编写的构造函数初始化。

2.3 对象在内存中存储

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

对象头在前面已经说了,对象头的一部分用于存储对象自身运行时的数据,另外的部分是类型指针,即对象指向它类元数据的指针,虚拟机通过这个指针来确定这个对象是属于哪个类的。

实例数据区域是对象存储真正有效的信息,即在程序中定义的各种类型的字段数据。这一部分数据有从父类中继承下来的,也有在子类中定义的,总之都要被记录下来。

对齐填充并不一定是必然存在的,因为 HotSpot 虚拟机内存管理的要求是给对象分配内存的大小必须是 8 字节的整数倍,所以不够的部分才需要对齐填充。又因为对象头部分正好是 8 字节的倍数,所以对齐填充是为了补全实例数据区域的。对齐填充并没有特别的含义,仅仅是起到填充占位符的作用。

2.4 访问对象

我们创建对象的目的是为了使用对象,这就要牵扯到如何访问对象了。在前面对内存的划分中说到,Java 虚拟机栈的局部变量表,存放的有对象引用(reference)类型,这个类型在 Java 虚拟机规范中只是规定这是指向一个对象的引用,但并没有规定如何定位对象,访问对象在堆中的具体位置。在不同的虚拟机中,对象的访问方式也是不同的,主流的访问方式有使用句柄直接指针两种。

使用句柄:

通过句柄访问对象.jpg

如果使用句柄访问方式,Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的是对象的句柄地址,而句柄中包含的才是对象实例数据和类型数据各自的具体地址信息,所以说使用句柄是一种间接使用指针访问对象的方式。

直接指针:

通过直接指针访问对象.jpg

如果使用直接指针访问方式,Java 堆中对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference 中直接存储的就是对象地址。

两种访问方式各有优势,使用句柄访问方式的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改。而使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的的时间开销。在 HotSpot 虚拟机中采用的是第二中访问方式,但使用句柄方式来访问的情况在软件开发中也很常见。

小结:

Java 堆就像是游戏玩家出生的新手村,承载着一个对象来到 Java 世界的梦想,资源的分配,初始的装备,以及对象的区分和查找,每一步都和对象的命运息息相关。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.08.30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2、堆的使用
    • 2.1 给对象分配内存
      • 2.2 初始化对象
        • 2.3 对象在内存中存储
          • 2.4 访问对象
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档