一:众所周知,我们平时编写的java源文件,需要被编译成class文件,然后交给jvm,jvm在自己的内存中运行。那么jvm是如何加载class到自己的内存的呢?

通过类加载器ClassLoader

二:类加载器有四种类型

1.Bootstrap ClassLoader

Bootstrap类加载器是用来加载java核心类库的,即%JRE_HOME%\lib下的jar包,包括rt.jar,resource.jar,charset.jar等,jvm一旦启动,就会默认去加载这些基础类到内存中,保证程序的正常运行

2.Extension ClassLoader

Extension类加载器是用来加载java核心类库的,加载%JRE_HOME%\ext下的jar包

3.Application ClassLoader

主要负责加载当前应用的classpath下的所有类

4.User ClassLoader

用户自定义的类加载器,可直接指定路径的class文件

三:双亲委派机制

1.原理说明

把上述四个类加载器看成是继承关系(实质不是继承关系。而是组合关系),则Bootstrap ClassLoader是最顶层的加载器,接下来分别是Extension ClassLoader,Application ClassLoader,User ClassLoader。所谓双亲委派机制,就是加载一个类,优先使用父加载器,当父加载器无法加载类时,才用子类加载器加载。比如Integer类,会优先使用BootStrap类加载器来加载,当无法加载时,才会轮到Extension ClassLoader,一直轮到User ClassLoader。

2.为什么需要双亲委派机制 

一个最大的原因就是为了保证安全。有了双亲委派机制,就可以保证系统类不会被破坏

比如自定义了一个java.lang包下的Integer类,若没有双亲委派机制,Application ClassLoader会加载该自定义的类,那么系统的Integer类就不会被加载,也就是相当于被破坏了。有了双亲委派机制,类的加载就相当于有了优先级,也就是优先保护系统的类,即优先加载系统的类

另外一个重要原因是为了避免重复加载。jvm判断一个是否是同一个class的原则是全类名+类加载器。其实就是相当于先问问大哥们是否有加载,如果没有自己就加载,避免大哥们加载了,自己又再次加载的情况

四:打破双亲委派机制

类加载除了双亲委派机制,还有全盘负责机制。也就是说,当某个类使用了特定的类加载加载后,该类所引用的所有依赖,都会使用该类加载器来加载,若加载不到,则抛出异常。因此,想要加载一些系统类的第三方实现,就需要打破该机制

        1.例子1:jdbc

        通常情况下都是使用DriverManager.getConnection()来获取数据库连接,DriverManager是系统类,会使用BootstrapClassLoader来加载,但是加载这个类时,会执行这个类的静态代码块,进而加载Driver接口下的实现类(SPI)。但是该接口是由第三方提供的,如mysql-connector下的Driver,就是Driver接口的实现类,但是第三方的jar包,BootStrap ClassLoader是加载不到的,因此使用了SPI的方式来加载(通过指定线程上下文ClassLoader去加载),直接加载类路径下对应接口的实现类

        2.例子2:自定义类加载器,重写loadClass()方法

                类加载器的双亲委派机制都存放在ClassLoader类的loadClass()方法中,通过重写该方法,就可以自定义加载类的逻辑,破坏双亲委派机制。

                注意:若想自定义类加载器的加载逻辑,但是又不想破坏双亲委派机制,可以通过选择重写findClass()来实现。因为loadClass的逻辑是父类加载失败,就会调用自己的findClass()来加载

五:知道了类加载进内存的方法,但是具体的类加载过程并不只是把类加载到内存

    java类加载过程一共分为3步

        1.加载到内存:有类加载器参与,使用双亲委派机制加载,在堆区生成一个唯一的class对象(该对象的描述信息存在方法区)

        2.连接:

                (1)验证:主要验证类是否符合jvm规范,是否有安全问题,比如是否继承了不该继承的类,是否实现了接口的所有方法等

                (2)准备:为static类型变量分配内存空间(只是初始化,不赋值)

                  (3)解析:虚拟机常量池的符号引用替换为字节引用,比如解析接口方法,类方法等,找到对应引用的方法,类等;符号引用是指字面量,而直接引用是指直接指向目标地址

        3.初始化:初始化静态变量,为静态变量赋值以及执行静态代码块等;如果有直接父类,则也初始化父类,执行父类的静态代码块以及为父类的静态变量赋值

六:类加载的方式

        1.隐式加载

                程序在运行过程中,碰到使用new方式创建的对象时,调用一个类的静态方法或者get一个类的静态变量(简而言之,就是需要用到该类时),会自动利用类加载器把该来加载进内存

        2.显式加载

                通过Class.forNam()手动指定加载一个类到内存中

(jvm的类加载是动态的,会先把基础类加载到jvm中,然后其他类则是在程序运行过程中,需要时再加载,目的是为了节省内存开销)

七:对象的创建过程(假设class对象已经被加载进运行时数据区,相关信息已经存在方法区中,若没有加载,则先加载进运行时数据区)

1.到方法区找到类元信息

2.堆区分配内存

3.对象初始化

        (1)虚拟机层面:设置必要信息到对象头,如hashCode,锁持有信息,分代年龄等

        (2)java程序层面:执行构造函数,按照初始化变量等并设置数据信息到对象的实例数据区域