【Java】从JVM角度看对象的创建

【Java】从JVM角度看对象的创建

以前才刚开始学习Java基础的时候,便闻听“请简述Java中对象的创建过程”是阿里的一个高频率经典面试题。
我们都知道,Java程序是运行在JVM,即JAVA虚拟机上的。因此今天试图从虚拟机的角度、从操作系统底层来探寻这一问题。

1.引入

1.1Java的内存机制简述

相比起C/C++,Java在使用过程中一大便利之处便是具有垃圾回收机制。所谓垃圾回收机制,说白了便是一个动态分配内存的机制。
C++中,若创建了一个对象,在对象被使用结束之后,便需要对对象执行delete();来删除对象,释放内存。而Java由于虚拟机的存在,这一原本需要手动得操作被交给了虚拟自行完成。即虚拟机根据用户的需求,为对象分配内存,并自动在生命期之外回收内存。
Java虚拟机在运行时,内存空间会被开辟为若干个数据区,分别用于存放运行时程序的方法、虚拟机栈、方法栈、堆、程序计数器。其中,程序计数器的作用在于:记录虚拟机的字节码指令地址。

1.2Java中的栈和堆

栈和堆是我们比较熟悉的两种数据结构,前者的特点在于后进先出,后者的特点在于按优先级大小进行操作。两者具体到JVM中作用大致如下:
栈主要用于描述虚拟机中的Java方法的内存模型。其作用是:每个方法在执行时,都为其创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。即方法的生命期伴随着一个栈在虚拟机中入栈和出栈的过程;
堆在JVM内存中占比最大,且内存共享,是垃圾收集的主要区域。Java堆可以是物理上不连续的内存空间,即逻辑上连续即可。

1.3方法区

别名“非堆(Non-Heap)”,之所以这么叫是为了和Java堆区分开来。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据;

1.4运行常量池

本质上来说是方法区的一部分。用于存放编译器生成的各种字面量和符号引用,在类加载后进入方法区的运行时常量池中存放

2.正题——对象在虚拟机中的创建

在介绍完几个基础概念后,现在来描述一下对象创建在虚拟机找中的具体步骤:

new的输入之后

当程序员在键入一个new之后,虚拟机会首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,且这个符号引用代表的类是否已经被加载、解析和初始化过;若没有,则先执行类的加载的过程;

Step1 定位

Java通过栈上的refrence数据来操作栈上的具体对象,这便需要对对象进行一个定位。目前有两种定位的方式:
方式一:句柄:
Java堆中划分出一块内存作为句柄池,refrence中存储的对象就是对象的句柄地址,而句柄中包含了对象实例数据与类型对象各自的具体地址信息。
好处:句柄地址稳定,refrence本身不需要修改
方式二:直接指针:
Java堆对象布局中必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是将对象地址。
好处:速速较快,只用一次指针定位

Step2 分配内存大小

接下来是最关键的分配内存环节,其中仍有两种方式来确定分配内存大小:
方式一:指针碰撞
内存中一块内存用于分配,一块用于空闲,其中中间放了一根指针作为分界点,根据挪动的距离来确定分配的内存大小。
方式二:空闲列表
上述的情况是内存空间是连续的。若不连续的话,就要采用空闲列表的方式。虚拟机会尽量空出一块足够大的内存,然后根据程序的需要来分配内存的大小。

一个需要注意的特殊情况

在给对象分配内存时,可能会出现内存分配与指针发生冲突的情况,即:由于要给多给对象分配内存,可能存在指针来不及修改的情况。对此,有两种解决办法:
方法一:
对分配内存动作进行同步处理;
方式二:
把分配内存的动作划分在不同的空间中,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。

Step3 对象初始化及设置

内存分配完毕后,需要将分配到的内存空间除对象头外初始化为零值。为的是对象的实例字段在Java代码中可以不赋初值就直接使用。
赋零值之后,再对对象进行进行必要的设置,例如对象所属的类、如何查找对象、对象的Hash码等等。

init方法

在虚拟机对对象进行了初始化之后,从虚拟机的角度来说,对象的创建已经结束。但从程序员的角度来说,还需要调用init方法来按程序员自身的意愿对对象进行初始化(即我们在IDE中进行的初始化)。

总结

在编写Java程序时,程序员只是简单地“new”了一下,但虚拟机内部却进行了许多步骤来完成这一过程。在了解虚拟机创建对象的过程之前,首先要对Java的内存处理机制、Java堆和栈、运行常量池以及方法区有一定的了解。之后,Java虚拟机中的创建对象过程可以简单滴概括为:
定位-分配内存-初始化
当然,这三个步骤其中仍有许多细节需要去理解,这也正是精华所在。
(第一次写Java博客,感谢阅读)

发布了15 篇原创文章 · 获赞 16 · 访问量 1514

猜你喜欢

转载自blog.csdn.net/weixin_44522586/article/details/103941251
今日推荐