前言

对象是Java中最常被提到的概念,也是Java的灵魂,Java中一切皆是对象。

JVM是Java的根基,理解灵魂与根基是如何融合的,对于理解Java本身至关重要。

对象的创建

对象的创建从Java语言层面上,往往就是一个简单的new即可搞定,而深入JVM的底层,可就复杂的多了。

JVM中对象的创建过程,分为如下5步,如图:

类加载检查

虚拟机遇到一条 new 指令后,会执行如下几个步骤:

  1. 首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用
  2. 检查这个符号引用代表的类是否已被加载过解析过初始化过
  3. 如果没有,那必须先执行相应的类加载过程。
  4. 确定对象所需的内存大小。
  5. 类加载检查通过后,进入下一步骤,为新生对象分配内存

分配内存

为对象分配空间相当于分田地,就是将空余的内存划分一块给对象。

由于不同的JVM虚拟机,Java堆的内存规整情况是不同的,所以分配方式上也会有一定区别。

Java 堆是否规整,由JVM所采用的垃圾收集器是否带有压缩整理功能决定

内存空间分配方式

指针碰撞

Java堆中内存是规整(无内存碎片)时使用,即垃圾收集器有压缩整理功能时。

原理就是将用过的内存放在一块,没用的放一块,中间用指针做标识,分配的时候将指针向空闲内存区域移动

空闲列表

Java堆中内存不规整,即垃圾收集器无压缩整理功能时。

使用内存和空闲内存相互交错,JVM需维护一个列表,记录上哪些内存是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。

两者具像化呈现如图:

并发时的内存分配

创建对象是很频繁的事,并发创建对象的时候有一个很重要的问题,就是线程安全

如:程序中创建对象A和对象B,底层VM给A对象分配内存,指针没来及修改,对象B同时使用原来的指针分配内存。

JVM一般采用一下两种方式来保障线程安全。

同步处理:CAS

CAS 是乐观锁的一种实现方式。

所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。

本地线程分配缓冲:TLAB

  • 每一个线程预先在 Eden 区分配一块儿内存,即为TLAB

  • JVM 在给线程中的对象分配内存时,首先在 TLAB 分配

  • 当对象大于 TLAB 中的剩余内存不足或已用尽时,再采用上述的 CAS 进行内存分配

更多关于CAS的内容可以参考博主的另一篇文章Java并发/多线程-CAS原理分析

初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)

这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

设置对象头

对象头是虚拟机对对象进行必要的设置

对象头存放了关于对象的各种信息如:

  • 对象是哪个类的实例
  • 如何才能找到类的元数据信息
  • 对象的哈希码
  • 对象的 GC 分代年龄
  • ……

根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生

从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。

所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的内存布局

在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域。

对象头

对象头上面已经介绍过了,其实主要就是分为两类:

  • 用于存储对象自身的运行时数据
  • 类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例

实例数据

这部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。

对齐填充

对齐填充不是必然存在的,没有实际意义,就是用来补位的。

因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,对象的大小必须是 8 字节的整数倍。

所以,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

对象的访问定位

建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。

对象的访问方式由虚拟机实现而定,目前主流的访问方式有如下两种方式

使用句柄

Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,如图:

直接指针

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

二者对比

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

使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

参考:《深入理解JVM虚拟机》第三版(吹爆)

JVM虚拟机-了解Java堆中对象分配、布局和访问的全过程的更多相关文章

  1. JVM 堆中对象分配、布局和访问

    本文摘自深入理解 Java 虚拟机第三版 对象的创建 Java 是一门面向对象的语言,Java 程序运行过程中无时无刻都有对象被创建出来.从语言层面看,创建对象只是一个 new 关键字而已,而在虚拟机 ...

  2. JVM高级特性-二、JVM在堆中对象的分配、布局、访问过程

    前面介绍了jvm运行时数据区域后,下面讲解下对内存中数据的其他细节,看他们是如何创建.布局及访问的 一.对象的创建 1.对象的分配 对象的创建分配方式主要有两种:指针碰撞和空闲列表 指针碰撞: 假设堆 ...

  3. 面试题:JVM在Java堆中对对象的创建、内存结构、访问方式

    一.对象创建过程 1.检查类是否已被加载 JVM遇到new指令时,首先会去检查这个指令参数能否在常量池中定位到这个类的符号引用,检查这个符号引用代表的类是否已被加载.解析.初始化,若没有,则进行类加载 ...

  4. jvm详情——1、堆中存什么?栈中存什么?

    数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...

  5. 翻译:JVM虚拟机规范1.7中的运行时常量池部分(二)

    本篇为JVM虚拟机规范1.7中的运行时常量池部分系列的第二篇. 4.4.4. The CONSTANT_Integer_info and CONSTANT_Float_info Structures ...

  6. JVM学习--(八)java堆分析

    上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...

  7. Java虚拟机三 Java堆和栈

    Java堆是和Java应用程序关系最为紧密的内存空间,几乎所有的对象都存放在堆中.并且堆是完全自动化管理的. 根据垃圾回收机制的不同,Java堆有可能有不同的结构.最为常见的一种就是将整个Java堆分 ...

  8. 翻译:JVM虚拟机规范1.7中的运行时常量池部分(一)

    原文链接: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4 Java Virtual Machine i ...

  9. JVM虚拟机21: 1.8中废弃永久代(PermGen)迎来元空间(Metaspace)

    1.JDK8永久代的废弃 JDK8 永久代变化如下图: 1.新生代:Eden+From Survivor+To Survivor 2.老年代:OldGen 3.永久代(方法区的实现) : PermGe ...

随机推荐

  1. linux 安装mysql后修改密码出现问题

    新安装的mysql 执行命令时候出现错误: 一 错误信息: ERROR 1045 (28000): Access denied for user 'mysql'@'localhost' (using ...

  2. Linux操作数据库基本

    连接数据库MySQL中每个命令后都要以分号;结尾1: mysql -h 192.168.10.250 -u root -p2:Enter password //要求你输入密码cug313@com3:s ...

  3. 慢查询日志工具mysqlsla的使用

    安装mysqlsla源码路径:https://github.com/daniel-nichter/hackmysql.com源码存放路径:/usr/local/src1.获取源码如果没有git命令,请 ...

  4. PHP使用Apache中的ab测试网站的压力性能及mpm介绍

    打开Apache安装的bin目录 shift+鼠标右键 复制粘贴以下代码->回车 ab -n 1000 -c 100 http://localhost/test.php 上例表示总共访问http ...

  5. 关于MVC RouteExistingFiles疑问后续

    前两天写了<关于MVC RouteExistingFiles疑问>,本来希望寻求大佬快速解答,奈何无人问津. 只能查看.NET 源代码,可以使用反编译工具(我用IL spy),也可以在线查 ...

  6. leetcode [34] Find First and Last Position of Element in Sorted Array

    Given an array of integers nums sorted in ascending order, find the starting and ending position of ...

  7. Spring是什么 包括SpringBean SpringMVC SpringBoot SpringCloud

    什么是Spring:spring是个开源框架,spring mvc是基于spring的一个mvc框架,spring boot是基于spring4的条件注册的一套快速开发整合包. Spring两大特征: ...

  8. QTablewidget 简单例子

    [1]QTableWidget简介 QTableWidget是QT对话框设计中常用的显示数据表格的控件. 学习QTableWidget就要首先看看QTableView控件(控件也是有”家世“的!就像研 ...

  9. java流类练习前篇

    总结: package com.aini; import java.io.*; public class gf { public static String main(String[] args) t ...

  10. struct和class的不同以及struct的应用场景

    struct在C#中被用来定义结构,它是一种比类小的数据类型.和类一样都是创建对象的模板,可以有自己的数据以及处理和访问数据的方法. struct的用法: struct FurnitureSize { ...