二、Hotspot虚拟机对象揭秘

1、对象的创建

在Java中,对象的创建主要有两个步骤:分配内存和初始化。

第一步,分配内存。在Java中,使用new关键字来创建对象时,会首先在堆内存中分配一块足够大小的空间,用来存储对象的成员变量。分配内存的大小由对象的成员变量所占的空间决定。

第二步,初始化。在分配内存后,Java会根据类的构造函数初始化对象。构造函数是一个特殊的方法,用来初始化对象的成员变量。在对象创建时,会自动调用构造函数进行初始化。如果没有定义构造函数,Java会自动生成一个默认构造函数,其函数体为空。

在对象初始化时,可以使用初始化块、静态初始化块等语法来对成员变量进行初始化。初始化块是一种在构造函数调用前执行的代码块,静态初始化块是一种在类加载时执行的代码块,用于对静态成员变量进行初始化。

除了使用new关键字来创建对象,还可以使用反射、序列化、克隆等方式来创建对象。反射是一种在运行时动态获取类信息、创建对象、调用方法等功能的机制,序列化是一种将对象转换为字节流的机制,克隆是一种复制对象的机制,它们都可以创建对象并进行初始化。但是,使用这些方式创建对象可能会影响程序的性能和安全性,需要谨慎使用。

2、为对象分配内存

在Java中,为对象分配内存的过程通常由Java虚拟机来完成,开发人员不需要手动分配内存。当使用new关键字创建一个对象时,Java虚拟机会自动为该对象分配内存。

Java中的内存分配是通过堆来实现的,堆是Java虚拟机中最大的一块内存空间。堆中存储的是所有的对象实例和数组,其大小由-Xmx和-Xms两个JVM参数控制。

在为对象分配内存时,Java虚拟机需要考虑两个因素:对象的大小和内存的分配策略。对象的大小包括对象头、实例变量和对齐填充三部分,不同的对象类型大小不同。内存的分配策略主要包括指针碰撞和空闲列表两种方式。

指针碰撞是一种通过指针移动来分配内存的方式,它假设堆内存中的空间是连续的,可以用一个指针来表示堆内存中的空闲空间,每次分配内存时将指针向前移动。空闲列表是一种通过记录空闲内存块来分配内存的方式,它维护了一个空闲内存块的列表,每次分配内存时从列表中找到一个合适的空闲块进行分配。

在分配内存时,Java虚拟机还会考虑线程安全问题。由于Java中的对象可以被多个线程访问,因此在为对象分配内存时需要考虑线程安全问题,避免出现多个线程同时访问同一块内存的情况。为了解决这个问题,Java虚拟机会使用同步锁等机制来保证内存分配的线程安全性。

3、处理并发安全问题

在Java中,为对象分配内存时需要考虑并发安全问题,避免出现多个线程同时访问同一块内存的情况。为了解决这个问题,Java虚拟机采用了两种不同的内存分配方式:指针碰撞和空闲列表,以及两种不同的内存分配方式:线程本地分配缓冲区(Thread Local Allocation Buffer,TLAB)和分代堆内存分配。

指针碰撞和空闲列表是两种不同的内存分配方式,它们分别采用了不同的内存分配算法。指针碰撞算法假设堆内存是连续的,可以用一个指针来表示堆内存中的空闲空间,每次分配内存时将指针向前移动。空闲列表算法维护了一个空闲内存块的列表,每次分配内存时从列表中找到一个合适的空闲块进行分配。这两种算法都是线程安全的,因为它们不会有多个线程同时访问同一块内存的情况。

在分配内存时,Java虚拟机还使用了线程本地分配缓冲区(TLAB)和分代堆内存分配来提高并发性能。TLAB是一种专门为线程分配内存的机制,每个线程都有自己的TLAB,避免了多个线程同时访问同一块内存的情况。分代堆内存分配则是一种将堆内存分为多个不同代的机制,不同代的内存分配方式和回收策略不同,可以根据对象的生命周期和访问模式来优化内存的使用。

总之,在为对象分配内存时,Java虚拟机需要考虑对象的大小、内存的分配策略、线程安全等因素,采用不同的算法和机制来保证内存分配的并发安全性和性能。

4、对象的访问定位

对象的访问定位是指 JVM 如何找到对象的内存地址。Java 对象的内存地址是在程序运行时动态分配的,而且每个对象的地址可能不同。因此,JVM 需要一种机制来确定对象的实际内存地址,以便能够访问该对象。

Java 对象的访问定位涉及到对象引用的处理,对象引用是指程序中用来访问对象的变量或表达式。当程序需要访问一个对象时,需要通过对象引用来定位对象的内存地址。JVM 将对象引用转换成实际的内存地址的过程称为对象的访问定位。

在 Java 中,对象引用分为两种类型:强引用和软引用。强引用是指程序中直接使用的对象引用,它可以直接访问对象的内存地址。软引用是指程序中间接使用的对象引用,它需要通过一些特殊的机制来访问对象的内存地址,例如使用软引用队列来管理软引用对象。

当程序中使用一个对象引用时,JVM 会根据引用类型的不同来执行不同的操作。对于强引用,JVM 直接将对象引用转换成实际的内存地址;对于软引用,JVM 需要根据软引用队列中的对象来确定对象的实际内存地址。无论是强引用还是软引用,JVM 都会进行必要的安全检查来确保程序不会越界或访问不存在的对象。

Last updated