Java一个类的加载过程:
1.加载
a.jvm会根据类名找到对应的类文件
b.进行文件内容读取
2.链接
链接主要是验证类中数据是否合法,然后把刚加载进来的类和其他类的关系建立清楚,主要有以下几个步骤
a.验证
b.准备
c.解析
3.初始化
a.在内存对应位置申请空间
b.按照jvm的组织方式,将数据放到各自位置
c.执行类的静态初始化
说到这里就不得不说一下静态属性的初始化顺序:
1.父类的初始化必须在子类的初始化之前
2.每个类中的初始化可以按照代码书写顺序依次进行,这里可能有两种书写风格:一种是定义时初始化,一种是静态代码块初始化,具体可以看以前这篇博客 关于Java静态属性初始化
顺便提一下JVM会保证以上过程线程安全,如果是自己实现类加载器的话,就需要我们自己保证线程安全。
大概了解了类的加载过程之后,我们来讨论一下类加载器是怎么将类加载到内存中的。
类加载器
JVM原生的类加载器有三个,不同的类加载器负责不同类的加载,他们之间又是根据双亲委派模式进行类的加载。
启动类加载器(BoostrapClassLoader)
主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 {jdk}/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
扩展类加载器(ExtensionClassLoader)
由Java语言实现的,是Launcher的静态内部类,它负责加载{jdk}/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
应用类加载器(ApplicationClassLoader)
这个类加载器主要加载我们编写过的类和通过maven等工具引入的第三方类。
当JVM执行引擎在执行过程中需要一个类并且这个类目前还不在内存中时,类加载器就会去加载,这里就遵循双亲委派原则。
双亲委派原则
首先要知道的是当每个类是由A类加载器加载后,这个类使用的所有类,默认都是有A类加载器进行加载,举个例子:
public class Main{
public static void main(String[] args){
String str = "hello";
Person p = new Person();
}
}
运行这个类 --> 需要执行Main.main方法 --> 触发Main类的加载,有APP加载器进行加载 --> Main 类用到了 String 类和Person 类
以上两个类也由加载Main类的App加载器加载,这里就有疑问了:String类不应该由Bootstrap类加载器加载吗?
是的,它也确实是由Bootstrap类加载器加载的,怎么做到的呢?
他们三个类加载器有这样一个上下级的关系,下级拿着上级的引用,每次去加载类的时候都会从最高级也就是Bootstrap类加载器开始加载,Bootstrap类加载器找到了相应的类,那么下级的类加载器就不用管了,如果没找到,才有下一级Ext类加载器去找,还是找到了相应的类App类加载器就不用管了,没找到再有App类加载器去找。这就是双亲委派原则。
理解了上述内容的话就可以回答上面的问题了。因为加载上面Main类是用App类加载器去加载的,那么Main类内部用到的所有类都是由App类加载器去加载,但是因为双亲委派原则的存在,Sring类会被Bootstrap类加载器找到,Person这个自己写的类会被App类加载器找到。
那么为什么要制定这样一套规则呢?
大家试想以下这个场景,如果有坏人定义了和JDK中的原生类同名的类比如Sring类吧,但是他在实现了相同功能的同时放了后门,我们在加载String类的过程中是不是会把他这个恶意代码加载进来,但是有了双亲委派原则,每次加载的时候都会从Bootstrap类加载器去加载,因为Bootstrap类加载器只会加载{jdk}/lib路径下的核心类库或-Xbootclasspath参数指定的路径的类,就避免了这种情况的发生,保证了系统的安全性。
其次还有一个好处就是因为有了这样一个优先级的关系,上级加载过的类,下级就不会加载了,避免了重复加载类。
我们在重写类加载器的时候是可以打破双亲委派原则的,但是安全性就只能由自己保障了,最典型的例子是Tomcat。
这里做一个总结:
1.执行引擎在执行字节码时(包括启动类),需要哪一个类,并且该类不在方法区中,则将类名交给类加载器,由其进行类的加载。
2.具体那个类加载器进行类的加载,需要看字节码所在类是由那个类加载器加载的(启动类默认交给App类加载器加载)。
3.遵循双亲委派原则,优先让上级类加载器加载。
4.具体的类加载器加载的过程是根据类名,对应到一个硬盘路径,进行文件读取、验证,然后进行初始化。
5.加载成功后,执行引擎继续执行,过程中保证线程安全。