JVM
注意:括号中为八股在每次面试中出现的概率
对象创建的过程了解吗?(392/1759=22.3%)
对象的创建过程是 Java 虚拟机(JVM)中一个非常重要的环节,它主要分为五步。
第一步是进行类加载检查,当程序执行到 new 指令时,JVM 会先检查对应的类是否已经被加载、解析和初始化过。如果类尚未加载,JVM 会按照类加载机制(加载、验证、准备、解析、初始化)完成类的加载过程。这一步确保了类的元信息(如字段、方法等)已经准备好,为后续的对象创建奠定基础。
第二步是进行内存的分配,JVM 会为新对象分配内存空间。对象所需的内存大小在类加载完成后就可以确定,因此分配内存的过程就是从堆中划分一块连续的空间,主要有两种方式:
一种是通过指针碰撞,如果堆中的内存是规整的(已使用和空闲区域之间有明确分界),JVM 可以通过移动指针来分配内存。另一种是通过空闲列表,如果堆中的内存是碎片化的,JVM 会维护一个空闲列表,记录可用的内存块,并从中分配合适的区域。
此外,为了保证多线程环境下的安全性,JVM 还会采用两种策略避免内存分配冲突,一种是通过 CAS 操作尝试更新分配指针,如果失败则重试;另一种是每个线程在堆中预先分配一小块专属区域,避免线程间的竞争。
第三步是将零值初始化,JVM 会对分配的内存空间进行初始化,将其所有字段设置为零值(如 int 为 0,boolean 为 false,引用类型为 null)。这一步确保了对象的实例字段在未显式赋值前有一个默认值,从而避免未初始化的变量被访问。
第四步是设置对象头,其中包含Mark Word、Klass Pointer和数组长度。Mark Word 用于存储对象的哈希码、GC 分代年龄、锁状态标志等信息。Klass Pointer 指向对象所属类的元数据(即 Person.class 的地址)。
第五步是执行构造方法,用<init> 方法完成对象的初始化。构造方法会根据代码逻辑对对象的字段进行赋值,并调用父类的构造方法完成继承链的初始化。这一步完成后,对象才真正可用。
如何记忆:
1.口诀记忆
口诀:
类加载检查先开路,内存分配两方式,零值初始化保安全,对象头设信息全,构造方法来收尾。
解释:
第一句“类加载检查先开路”对应第一步:类加载检查。
第二句“内存分配两方式”对应第二步:指针碰撞和空闲列表。
第三句“零值初始化保安全”对应第三步:字段设置为默认值。
第四句“对象头设信息全”对应第四步:设置 Mark Word 和 Klass Pointer。
第五句“构造方法来收尾”对应第五步:<init> 方法完成初始化。
2.联想记忆
假设你正在建一座房子(对象):
类加载检查 :先检查设计图纸(类)是否齐全,如果没有图纸,就去档案馆(JVM)找一份。
内存分配 :根据图纸规划土地(堆内存),如果土地整齐(规整堆),直接划线分地;如果土地杂乱(碎片化堆),则查看空地清单(空闲列表)。
零值初始化 :给新房子里的所有房间(字段)装上默认家具(如灯关着、水龙头关闭)。
对象头设置 :在门口挂上牌子(对象头),标明房子的编号(哈希码)、使用年限(GC 分代年龄)等信息。
构造方法 :最后装修房子(初始化字段),并邀请家人入住(调用父类构造方法)。
拓展:
1.第二步分配内存后,对象的内存结构图
各部分区域的作用如下:
对象头:包含 Mark Word 和 元数据指针,其中 Mark Word 记录对象的哈希值、锁状态、分代年龄等信息,元数据指针 指向方法区的类元数据。此外,如果对象是数组,还会额外存储数组长度。
实例成员数据:用于存放对象的成员变量值。如果变量是基本类型,直接存储数值;如果是引用类型,则存储指向实际对象的地址。
对齐填充:由于 HotSpot 虚拟机 规定对象的起始地址必须是 8 字节对齐,若对象大小未达到 8 字节 的整数倍,则通过对齐填充补足,以确保高效的内存访问。
注意:其实到内存分配这一步完成,这个对象算已经创建好了,但只是个雏形,还不能使用。
2.Java 创建对象的四种常见方式
(1)new 关键字(最常见)
使用 new 关键字可以直接创建对象,并调用 无参或有参构造方法 进行初始化。
示例:
Person person1 = new Person(); Person person2 = new Person("fsx", 18);
适用于大部分场景,代码直观,易于理解。
(2)反射(Class 和 Constructor 两种方式)
反射机制可以在运行时动态创建对象,主要通过 Class.newInstance() 和 Constructor.newInstance() 两种方式实现。
Class.newInstance()
通过 Class 对象的 newInstance() 方法创建实例,只能调用无参构造方法。
Person person = Person.class.newInstance(); System.out.println(person);
限制:必须有无参构造方法,否则会抛出异常。
Constructor.newInstance()
Constructor 类提供更灵活的创建方式,可以调用 任意构造方法(包括私有构造方法)。
Constructor<Person> constructor = Person.class.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); // 允许访问私有构造方法 Person person = constructor.newInstance("fsx", 18); System.out.println(person);
对比:
Class.newInstance() 只能调用无参构造方法,Constructor.newInstance() 可调用任意构造方法。
Constructor.newInstance() 可以通过 setAccessible(true) 访问私有构造方法。
(3)clone() 方法(对象克隆)
clone() 方法用于创建一个相同内容的新对象,不会调用构造方法。需要实现 Cloneable 接口,并重写 clone() 方法。
public class Person implements Cloneable { @Override public Person clone() throws CloneNotSupportedException { return (Person) super.clone(); } }
Person person1 = new Person("fsx", 18); Person person2 = person1.clone(); System.out.println(person1 == person2); // false
注意:clone() 只进行 浅拷贝,对象内部的引用类型变量仍然指向同一块内存。如果要实现 深拷贝,需要让所有引用类型的成员变量也实现 Cloneable 接口,并重写 clone() 方法。
(4)反序列化(Serializable)
反序列化可以将存储或传输的对象数据恢复成 Java 对象,不会调用构造方法。
Person person1 = new Person("fsx", 18); byte[] bytes = SerializationUtils.serialize(person1); Person person2 = (Person) SerializationUtils.deserialize(bytes); System.out.println(person1 == person2); // false
特点:对象必须实现 Serializable 接口,否则无法序列化;反序列化创建的对象是全新的,与原对象无关;性能开销较大,适用于数据存储或网络传输,而不是频繁对象创建。
类载入过程 JVM 会做什么?(364/1759=20.7%)
类的加载过程确保了类在运行时能够被正确地使用,可以分为五个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和 初始化(Initialization)。接下来我会详细讲述每个阶段的具体内容和作用。
第一个是加载阶段,在这个阶段,JVM 会完成三件事情,
首先是通过类的全限定名获取定义此类的二进制字节流,这可以通过多种方式实现,例如从本地文件系统加载 .class 文件、从网络下载、或者通过动态代理生成字节码。
然后是将字节流所代表的静态存储结构转化为方法区的运行时数据结构,即将类的元信息(如字段、方法、父类等)存储到方法区中。
最后是在堆中生成一个代表该类的 java.lang.Class 对象,这个对象作为程序访问该类的入口点,所有的反射操作都通过这个对象进行。
第二个是验证阶段,在这个阶段,JVM 会对加载的字节码进行校验,以确保其符合 Java 虚拟机规范,并且不会危害虚拟机的安全,一般会验证四样东西。
首先是文件格式,检查字节码文件是否符合 Class 文件格式规范。
然后是元数据,检查类的元信息是否符合语法规则,例如父类是否存在、是否继承了 final 类等。
其次是字节码,分析字节码指令,确保其不会执行非法操作(如类型转换错误、越界访问等)。
最后是符号引用,检查符号引用能否正确解析为直接引用,例如检查类、字段、方法是否存在并且可访问。
第三个是准备阶段,JVM 会为类的静态变量分配内存,并设置默认初始值(零值)。这个阶段并不会执行任何 Java 代码,也不会为实例变量分配内存(实例变量是在对象创建时分配的)。例如,如果类中有一个静态变量 static int value = 123;,在这个阶段,value 会被初始化为 0,而不是 123(赋值操作会在初始化阶段完成)。
第四个是解析阶段,JVM 会将类中的符号引用替换为直接引用。符号引用是以一组符号描述所引用的目标,例如类的全限定名、字段的名称和描述符等。直接引用是可以直接定位到目标的指针、句柄或偏移量。解析的对象包括类或接口、字段、方法、方法类型、方法句柄和调用点限定符等。
第五个是初始化阶段,在此阶段,JVM 会执行类的初始化代码,包括静态变量赋值和静态代码块的执行。这是类加载过程的最后一个阶段,也是唯一一个会执行用户代码的阶段。初始化的顺序遵循“父类优先”的原则,即先初始化父类,再初始化子类。
如何记忆:
1.口诀记忆
口诀:
加载三件事,字节流转对象;验证四样全,格式元码符;准备静态值,零值默认记;解析符号换,直接引用齐;初始化代码,父先子后行。
解释:
第一句“加载三件事,字节流转对象”对应加载阶段的三个任务:获取字节流、转换为运行时数据结构、生成 Class 对象。
第二句“验证四样全,格式元码符”对应验证阶段的四项内容:文件格式、元数据、字节码、符号引用。
第三句“准备静态值,零值默认记”对应准备阶段:为静态变量分配内存并设置默认值(零值)。
第四句“解析符号换,直接引用齐”对应解析阶段:将符号引用替换为直接引用。
第五句“初始化代码,父先子后行”对应初始化阶段:执行静态代码块和赋值操作,遵循父类优先原则。
2.联想记忆
假设你正在组织一场大型活动(类加载过程):
加载阶段 :你需要准备好活动的所有资料(字节流),然后整理成活动手册(运行时数据结构),最后指定一个负责人(Class 对象)来管理活动。
验证阶段 :在活动开始前,你需要检查资料是否符合规范(文件格式)、活动规则是否合理(元数据)、流程是否安全(字节码)、以及参与人员是否可信(符号引用)。
准备阶段 :你需要提前安排好场地(静态变量分配内存),但暂时不摆放具体物品(设置默认值)。
解析阶段 :你需要将活动计划中的代号(符号引用)替换为具体的地点或人员(直接引用)。
初始化阶段 :活动正式开始,按照计划布置场地(静态变量赋值)并执行开场仪式(静态代码块),先处理主会场(父类),再处理分会场(子类)。
拓展:
1.类加载流程图
2.Java 类的初始化时机
在 Java 虚拟机(JVM)中,只有在特定情况下才会触发类的初始化。根据规范,只有 6 种情况会导致类的初始化:
(1)执行特定字节码指令
当代码涉及以下操作时,JVM 会触发类的初始化:new 关键字创建实例对象时;访问类的 静态变量(getstatic 指令,常量不会触发初始化);对静态变量赋值(putstatic 指令);调用类的 静态方法(invokestatic 指令)。
(2)反射调用
使用 java.lang.reflect 包中的方法(如 Class.forName("类名") 或 newInstance())动态加载类时,会触发类的初始化。
(3)父类未初始化
当一个类需要初始化时,如果其父类尚未初始化,则会先初始化父类。
(4)虚拟机启动
JVM 启动时,会初始化包含 main 方法的主类,确保程序可以正确运行。
(5)使用 MethodHandle 或 VarHandle
MethodHandle 和 VarHandle 主要用于轻量级反射机制。如果想通过它们执行类的静态方法或访问静态变量,需要先对该类进行初始化。
(6) JDK 8 及以上的默认方法
如果接口中定义了默认方法(default 关键字修饰),且该接口的实现类尚未初始化,那么初始化接口时,会确保所有实现类已被初始化。
什么是双亲委派模型?(425/1759=24.2%)
双亲委派模型是 Java 类加载机制中的核心概念,它定义了类加载器之间的层次关系和加载规则。通过这种模型,Java 能够保证类的唯一性和安全性,同时避免重复加载类的问题。接下来我会详细讲述双亲委派模型的定义、层次结构和工作流程。
首先说一下什么是双亲委派模型,它其实是一种类加载机制,其规定了当一个类加载器收到类加载请求时,不会立即尝试自己去加载这个类,而是先将请求委托给父类加载器完成。只有当父类加载器无法加载该类(例如在父类的搜索范围内找不到对应的类)时,子类加载器才会尝试自己加载。这种机制确保了类的加载过程具有层次性,并且优先使用高层级的类加载器来加载核心类库。
接下来说一下类加载器的层次结构,主要分为四层,
第一层是(Bootstrap ClassLoad
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
神哥引路,稳稳起步!!早鸟特惠,仅剩177名额!晚了就涨到29.9了! 核心亮点: 1.数据驱动,精准高频:基于1759篇面经、24139道八股题,精准提炼真实高频八股。 2.科学记忆,高效掌握:融合科学记忆法和面试表达技巧,记得住,说得出。 3.提升思维,掌握财商:不仅可学习八股,更可教你变现,3个月赚不回购买价,全额退。 适宜人群: 在校生、社招求职者及自学者。