JAVA类加载的全过程是怎样的?什么是双亲委派机制?有什么作用?
前言:
最近没事,正好翻翻源码,学习学习
有哪些类加载器
先打印出来看一下有哪些类加载器,可以发现BootStrap ClassLoader打印出来是null,实际上BootStrap ClassLoader的类加载器是c++实现的,C++ 实现的类加载器在 Java 中没有与之对应的类的,所以结果是 null
在打印一下对应的类加载的目录
可以发现我的Application ClassLoader 加载的是我编译的代码的目录,也就是说实际上Application ClassLoader可以进行自定义,可以使用自定义加载器。
由于每个加载器都加载不同目录下的类,这样可以保证核心类在传递到父加载器的时候才会进行加载核心的类保证一个java内部核心类的一个安全
BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%/lib 下的jar包和class文件。
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类。
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。
继承ClassLoader实现自定义类加载品
类加载器的相关代码
我们跟一个类加载器源码看一下
进来发现和上面三个说的AppClassLoader <- ExtClassLoader <- BootStrap CLassLoader 这三个类加载器都没有?不要慌我带你找源码
全局搜索一下源码发现这两个类加载器都是继承的一个URLClassLoader类加载器
继续跟源码发现URLClassLoader extends SecureClassLoader
并且SecureClassLoader extends ClassLoader,是不是就清晰了?
跟进去看一下ClassLoader源码,这不就是刚刚上面说的那个ClassLoader吗? ok 稍微懂一点设计模式的其实就知道怎么回事了,实际上就是组合 和 子类继承的父类,调用的时候如果有需求可以子类重写父类的方法,也可以使用父类将通用的方法统一抽出来
这里的loadClass方法属性是一个protected类型的,说明子类是可以访问的,也就是说子类是可以重写这个方法的(也就是说如果重写这个方法可以避免一个双亲委派的一个模式,自定义加载机制)
JAVA的类加载器:AppClassloader -> ExtClassloader -> BootStrap Classloader 这三个指的是java类加载器的总类,相当于他们并不是一种继承的关系,只是使用parent属性进行指向,和下面的这些类加载器要区分开来
每种类加载器都有他自己的加载目录。
JAVA中的类加载器: AppClassLoader, ExtClassLoader -> URLClassLoader ->SecureClassLoader -> ClassLoader 这些类加载是一个继承的过程,刚刚上面源码中也很明确
双亲委派核心
下面继续看一下loadClass方法,其实就是双亲委派的核心,可以发现类加载器如果在本会优先将类传递给父类加载器进行加载,也就是很多八股文中说的:自地向下查找类是否被加载,自顶向下尝试类加载器。 实际上看 我圈的这几行代码就懂了这句话了,每次进行加载的时候都会在 当前的类加载器进行查找类是否加载,如果没有加载则传递给父类加载器。
findLoadedClass实际上就是去找缓存,看一下这个类是否之前加载过,这个方法也是一个native方法。
如果说父类等于null的话,就会进入到findBootstrapClass,也就是BootStrap类加载器,这个加载器也是一个native方法,c++。
如果父类加载器加载不到类,就会调用当前类加载器的 findClass() 方法。如果当前类加载器是用户自定义的 ClassLoader,那么就会执行用户自定义的加载逻辑。
loadClass()
↓
父加载器 loadClass()
↓
找不到
↓
当前类加载器 findClass()
也就是下面这个图,先找缓存看一下当前类有没有加载,没有缓存的话就委托给上面进行加载,同样的上面顶部的加载路径也是不一样的,BootStrap加载的是sun.misc.boot.class路径下的,比如java.lang.String就是这个路径下的,我在自己代码中写一个java.lang.String这样的对象就无法加载,因为会优先加载父加载器目录下的对象,而不会加载自定义的类防止造成一个破坏
很多人画图进行背诵,实际上看上面两行代码就明白了
双亲委派:向上委托查找,向下委托加载
双亲委派作用:保护java底层的类不会被应用程序覆盖
如何打破双亲委派?
自定义加载器的话,需要继承ClassLoader。如果我们不想打破双亲委派模型,就重写 ClassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写loadClass()方法。
类加载过程
看完上面的代码后,实际上这步骤也就很简单了
加载 -> 连接 -> 初始化
初始化完了就可以使用了,使用完了就卸载(被GC掉)
加载:把Java的字节码数据加载到JVM内存当中,并映射成JVM认可的数据结构。(将 .class是文件加载到内存中)
loadClass方法其实用来查找类对象的,真正把 .class 字节码转换为 JVM 运行时数据结构的是defineClass()方法 
连接: 同样就是loadClass方法中的resolveClass的方法,这个是一个native方法
里面帮你做的几个事情:
1、验证:检查加载到的字节信息是否符合JM规范
可以看一下对应的.class文件的二进制规范,最直观的就是必须以 “cafe babe” 开头的二进制文件才会加载,只有符合规范的class文件才会加载

2、准备:创建类或撩口的静态变量,并赋初始值。就是半初始化状态(比如new String 会在堆里面初始化一块地方,将内存占用出来,然后把成员变量初始化一个默认值)
3、解析:就是在栈里面创建一个指针,将指针指向刚刚的堆内存中
初始化: 初始化过程就已经到自己写的代码层面了,比如自己写的类static变量、static方法、自己写的构造方法等等在这个过程中执行
QA:
1.为什么上面说的验证.class文件是否正确的阶段要在加载阶段后面?
因为 JVM 必须先把 class 文件读进内存,才能验证它。
jvm中的一个对象的生命周期
上面说的,类加载过程,当类加载完毕后,对象使用完了然后被GC掉的一个过程,最后的GC清除过程都经历了什么?
对象在整个jvm中的一个生命周期
1、用户创建一个对象,JVM首先需要到方法区去找对象的类型信息。然后再创建对象。
2、JVM要实例化一个对象,首先要在堆当中先创建一个对象。->半初始化状态
3、对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GC,对象如果存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。到对应的年龄后自动转入老年代
4、当方法执行结束后,栈中的指针会先移除掉。
5、堆中的对象,经过Full GC (Full GC是老年代的时候的一个GC),就会被标记为垃圾,然后被GC线程清理掉。
多大年龄才会移入老年代?
可以发现MarkWord里面记录分代年龄的就4bit,4bit最大记录到15,也就说最大到15次就进入老年代

版权属于:戏人看戏博客网
本文链接:https://blog.web3er.cn/archives/2075.html
若无注明均为戏人看戏原创,转载请注明出处,感谢您的支持!