如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必触发这个类的链接以及初始化。)
符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
举个例子:
在程序执行方法时,系统需要明确知道这个方法所在的位置。
Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。
当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。
通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
所以,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
================================================================
初始化就是执行类的构造器方法,是类加载的最后一步,这一步 JVM才开始真正执行类中定义的 Java 程序代码
这个方法不需要定义,是javac
编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。
若该类具有父类,jvm会保证父类的init()
先执行,然后在执行子类的init()
。
对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化,只有主动去使用类才会初始化类:
-
当遇到
new
、getstatic
、putstatic
或invokestatic
这 4 条直接码指令时 -
当遇到一个类,读取一个静态字段(未被
final
修饰)、或调用一个类的静态方法时。 -
当
JVM
执行new
指令时会初始化类。即当程序创建一个类的实例对象。 -
当
JVM
执行getstatic
指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。 -
当
JVM
执行putstatic
指令时会初始化类。即程序给类的静态变量赋值。 -
当
JVM
执行invokestatic
指令时会初始化类。即程序调用类的静态方法。 -
对类进行反射调用时,如果类没初始化,需要触发其初始化。
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main
方法的那个类),虚拟机会先初始化这个类。
MethodHandle
和 VarHandle
可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle
来初始化要调用的类。
「补充,来自issue745」 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
=================================================================
了解了类加载过程后,我们来看看类加载器。
类加载器(ClassLoader)
用来加载 Java 类到 Java 虚拟机中。
JVM 中内置了三个重要的 ClassLoader
,同时按如下顺序进行加载:
-
BootstrapClassLoader
启动类加载器:最顶层的加载类,由C++实现,负责加载%JAVA_HOME%/lib
目录下的核心jar包和类或者或被-Xbootclasspath
参数指定的路径中的所有类。 -
ExtensionClassLoader
扩展类加载器:主要负责加载目录%JRE_HOME%/lib/ext
目录下的jar包和类,或被java.ext.dirs
系统变量所指定的路径下的jar包。 -
AppClassLoader
应用程序类加载器:面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
除了BootstrapClassLoader
其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
:
类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器。
需要注意的是,Java虚拟机对Class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的Class文件加载到内存生成Class对象。
===================================================================
===============================================================
每一个类都有一个对应它的类加载器。在加载类的时候,是采用的双亲委派模型,即把请优求先交给父类处理的一种任务委派模式。
系统中的类加载器在协同工作的时候会默认使用 双亲委派模型 。//加入Java开发交流君样:756584822一起吹水聊天
双亲委派模型的理论很简单,分为如下几步:
-
即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。
-
加载的时候,首先会把该请求委派给该父类加载器的
loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器BootstrapClassLoader
中。
当父类加载器无法处理时,才由自己来处理。
- AppClassLoader的父类加载器为
ExtensionClassLoader
,ExtensionClassLoader
的父类加载器为null,当父类加载器为null时,会使用启动类加载器BootstrapClassLoader
作为父类加载器。
=========================================================================
试想一种情况,我们在项目目录下,手动创建了一个java.lang 包,并在该包下创建了一个Object,这时候我们再去启动Java程序,原生Object会被篡改吗?当然是不会的!
因为Object类是Java的核心库类,由BootstrapClassLoader
加载,而自定义的java.lang.Object类应该是由AppClassLoader来加载。
BootstrapClassLoader
先于AppClassLoader
进行加载,根据上面的双亲委派模型的概念,我们可以知道,java.lang.Object类已经被加载,并且AppClassLoader
要加载类之前都要先给其父类过目,所以自己写的野类是无法撼动核心库类的。
===============================================================
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。
=================================================================
双亲委派模型的都集中在java.lang.ClassLoader 的 loadClass()
中,相关代码如下所示:
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查请求的类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//父加载器不为空,调用父加载器loadClass()方法处理
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
。**
[外链图片转存中…(img-eQlIQTJx-1710818755324)]
[外链图片转存中…(img-Pth0P7pN-1710818755325)]
[外链图片转存中…(img-nZqjigU9-1710818755325)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-Ox1dNVhY-1710818755326)]