2024.11.19 周二
许久不见,甚是想念。时隔周六、周日、周一,又准备开始打卡,许多课的结课大作业把我的计划打乱许多,因此许久没有打卡。
今日学习内容也不算多,课业和项目都比较花时间,时间花在项目上八股的学习时间就偏少。
八股
类初始化和类加载
创建对象的过程
- 类加载检查:虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池(回顾:常量池存储类和接口中的常量,包括字面值常量、符号引用、以及运行时常量池,同时常量池属于方法区的存储内容,而元空间是方法区的具体实现)中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程。
- 分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆(回顾:根据 JVM8 规范,JVM 运行时内存共分为虚拟机栈、堆、元空间、程序计数器、本地方法栈五个部分。堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建)中划分出来。
- 初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,因为程序能访问到这些字段的数据类型所对应的零值。
- 进行必要设置,比如对象头:初始化零值完成后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
什么是偏向锁?
如果一个锁大多数时间都被同一个线程访问,那么就可以假设这个锁在将来也极有可能被同一个线程访问,从而省去一些不必要的检查,提高程序的性能。但是,如果有其他线程开始竞争这个锁,那么偏向锁就会撤销,回复到更严格的锁状态。
- 执行init方法:在上面工作完成后,从虚拟机的视角来看,一个新的对象已经产生,但从Java程序的视角来看,对象创建才刚开始,因为构造函数,即class文件中的方法还没有执行,所有的字段仍然为零,对象需要的其他资源和状态信息还没有按照预定的意图构造完成。所以一般来说,执行new指令之后还会接着执行方法,将对象按照程序员的意愿进行初始化,如此一个真正可用的对象才算被完成构造出来。
对象的生命周期
对象的生命周期包括 创建、使用、销毁 三个阶段:
- 创建:对象通过关键词new在堆内存中被实例化,构造函数被调用,对象的内存空间被分配。
- 使用:对象被引用并执行相应的操作,可以通过引用访问对象的属性和方法,在程序运行过程中被不断使用。
- 销毁:当对象不再被引用时,通过垃圾回收机制自动回收对象所占用的内存空间。垃圾回收器会在适当的时候检测并回收不再被引用的对象,释放对象占用的内存空间,完成对象的销毁过程。
类加载过程 和 双亲委派原则
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括以下 7 个阶段:
其中Java 的类加载过程分为三个主要步骤:加载、链接、初始化。
- 第一阶段——加载(Loading)
通过类的全限定名(包名 + 类名),获取到该类的.class
文件的二进制字节流,将二进制字节流所代表的静态存储结构,转化成方法区运行时的数据结构,在内存中生成一个代表该类的Java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
Java将字节码数据从不同的数据源读取到JVM中,并映射为JVM认可的数据结构(Class对象)。
数据源可能是各种各样的形态,如jar文件、class文件,甚至是网络数据源等;如果输入数据不是 ClassFile
的结构,就会抛出ClassFormatError
。
- 第二阶段——链接(Linking)
类加载过程的核心步骤,简单来说指 将原始的类定义信息平滑地转化入JVM运行的过程中。此处可进一步细分成三个步骤。
验证(verification),是虚拟机安全的重要保障,JVM需要核验字节信息是否符合Java虚拟机规范,否则被认为是
VerifyError
,如此一来就防止恶意信息或者不合规的信息危害JVM的运行,验证阶段有可能触发更多class的加载。
准备(preparation),创建类或接口中的静态变量(static),并初始化静态变量的初始值。但此处的"初始化"和"显式初始化阶段"存在区别,此处侧重点在于分配所需要的内存空间,不会去执行更进一步的JVM指令。
解析(resolution),将常量池的符号引用(symbolic reference)替换为直接引用。
- 第三阶段——初始化(initialization)
真正地去执行类初始化地代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理完成,父类型的初始化逻辑优先于当前类型的逻辑。
public class Example {// 静态字段public static int staticField = 10;// 静态常量public static final String STATIC_CONSTANT = "This is a static constant";// 静态初始化块static {// 在这里可以进行静态字段的赋值或其他初始化操作System.out.println("Inside static initializer block.");staticField = 20; // 改变静态字段的值}// 构造函数public Example() {System.out.println("Inside constructor.");}// 主方法,程序的入口点public static void main(String[] args) {// 输出静态字段的值System.out.println("Static field value: " + staticField);// 输出静态常量的值System.out.println("Static constant value: " + STATIC_CONSTANT);// 创建类的实例,观察构造函数的调用new Example();}
}
双亲委派模型:当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载Java类型。
算法
560.和为k的子数组(枚举)
项目
苍穹外卖
尽管沉寂了许久,但是项目还是花了点时间抽空在做。