看到这篇文章觉得比以前看过的所有文章说的比透彻。 从来不写博客的我, 想写一篇翻译来传播原作者的精神哈哈。

前言

如你所知, Java时一门面向对象语言, 开发时常常会创建一摞子对象。

User user = new User();

这样一行代码, JVM会做些什么事情呢?

对【对象】的理解

内存布局

在Hotspot虚拟机内部, “对象”的内存布局被划分了三部分: 对象头, 实例数据, 和填充对齐。

对象头包括两部分信息, 第一部分用来存储对象本身运行时相关数据(HashCode,GC分代年龄,lock锁状态标志,等等)。

第二部分是类型指针, 指向Class元数据区。虚拟机用这个指针来确定这个对象对应的Class对象(如果有句柄池,就不会有这个东西)。如果new出来的对象是数组,会被存储数组长度,如下所示

Java new对象后是null吗 java如何new对象_句柄

Mard Word是一个【非固定】的把很多信息存储在一个尽可能小空间内的内存区域,同时也会根据对象状态来审时度势。

Java new对象后是null吗 java如何new对象_Java_02

实例数据部分是实际存储的有效信息,即代码中定义的各种类型的字段内容,是由父类继承还是在子类中继承(这句是借助翻译工具翻译的, 刚开始没读懂什么意思)。

“对象填充“并非是实际分配来存储信息的,它只是在虚拟机中扮演一个花瓶的角色,因为HotSpot虚拟机对象对象的起始地址必须是8个字节的整数倍,

2.【对象】访问

在Java程序中, 我们通过指针(指向对象的引用)来操作对象。众所周知,对象是存储在堆中,然而其指针是存储在“虚拟机栈“ 那么指针是什么定位到堆内存的对象呢?

直接指针方法(HotSpot实现):直接存储在引用中的地址是堆中对象的地址。优点是定位速度快,缺点是对象移动(GC移动时的对象移动)本身需要修改

句柄方法:Java堆的一部分用作句柄池。引用存储对象的句柄地址,句柄包含对象实例和类型的特定位置信息。优点是对象的移动只改变了句柄中的实例数据指针,缺点是两次定位。

创建对象的操作

当虚拟机有遇到一个新的指令, 虚拟机会根据指令参数能否在常量池定位到Class的符号引号, 其后检查这个类是否已经被类加载器加载过。如果没有加载, 先加载类。

类型检查通过后,虚拟机将会为新的对象分配内存,所需内存大小在类加载后决定。

内存分配完成后,虚拟机将会把对象初始化为0, 是为了那些在程序代码里未设置初始值的字段能够直接被使用。对于静态变量, 会在类的准备阶段被初始化为0。

设置对象头的基本信息, 类元数据, HashCode, gc年龄,等等

以上操作完成后,一个新的对象诞生了。但是成员方法还未被访问,所有字段都是0。与此同时,要调用构造函数来根据程序员意愿实例化对象。静态变量的初始化操作会在类加载的初始化阶段完成。

有两种方法来分配内存

规则的Java堆内存(垃圾回收算法用标记压缩), 用一个指针指向空闲内存,内存分配多少,指针移动多少。

不规则的Java堆内存(垃圾回收算法用标记清除),虚拟机维护了一个内存空闲列表, 当内存被分配能从空闲列表找到哪块空闲时足够用的, 同时更新这个列表

当内存不够用时, 将会发生一次GC(垃圾回收)

分配内存的并发解决方案

同步分配内存的骚操作---使用“CAS失败重试”来确保更新操作的原子性。每个线程在堆中预先分配一小部分内存,称为线程本地分配缓冲区(TLAB),只有在TLAB耗尽并分配一个新的TLAB时,线程才在其TLAB上分配内存。需要同步锁。由-XX:+/-UseTLAB参数设置

创建对象重排序问题

A a = new A();

它被简单的分解为三个步骤

为对象分配内存

初始化对象

设置指向内存

在2 , 3步骤, 者2个指令有可能会被重排, 从而引起 在多线程环境下 “在访问对象的时候, 对象还未初始化完成“。 单例模式的双检锁会存在这个问题。你可以用“volatile“来禁止重排序, 问题解决。

自己写了个代码, 就当是复习了

Java new对象后是null吗 java如何new对象_Java_03

如图所示, antlr=new AntlrDemo(); 在这一步的三个“jvm“指令当时, 如果对象的赋值先与初始化, 那么就空指针了。在volatile修饰下, 三个指令不会被重排。

本文由博客一文多发平台 OpenWrite 发布!