Java的类加载机制
首先概览一个对象的创建过程

1.类加载器
类加载器的作用是将一个class文件加载到内存中,Java中的ClassLoader类是所有类加载器的父类,当一个类加载进内存之后,实际上它的二进制的内容会占用一块内存空间,于此同时生成了一个Class类的对象,这个Class对象指向了二进制内容占用那一块内存。
(待验证)二进制内容这一块东西是在matespace里面。生成的Class对象是在堆内存中。
2.类加载器分类
由上至下级别依次降低
1.BootStrapClassLoader 负责加载jdk的核心类
2.ExtensionClassLoader 负责加载扩展包的各种类文件
3.AppClassLoader 负责加载classpath下面的类
4.CustomerClassLoader 可以加载任何类,看如何去定义
可以通过一个简单的main方法去查看各个加载器的加载范围
System.out.println("----------BootStrapClassLoader-sun.boot.class.path-----------");
System.out.println(System.getProperty("sun.boot.class.path").replaceAll(":", System.lineSeparator()));
System.out.println("----------ExtensionClassLoader-java.ext.dirs-----------------");
System.out.println(System.getProperty("java.ext.dirs").replaceAll(":", System.lineSeparator()));
System.out.println("----------AppClassLoader-java.class.path---------------------");
System.out.println(System.getProperty("java.class.path").replaceAll(":", System.lineSeparator()));
3.双亲委派机制
Classloader类中的loaderClass方法就是双亲委派的具体实现,通过一个很像递归的方式,去逐级上推的在各个加载器的Cache中寻找需要加载的Class,如果都没有找到再逐级的下传去调用子类具体实现的findClass方法来加载对应的类,最后如果还不能加载,抛出异常classNotFountException。
也就是说如果自定义一个类加载器的话,只需要继承Classloader类并重写findClass方法就可以了。双亲委派其实具体是指从子到父,再从父到子的一次循环查找/加载类的过程。这个命名方式确实很容易让人产生继承方面的误解。
Why Use: 主要是为了安全,为了避免重复加载覆盖。
PS: 父加载器不是父类!不是继承!而是组合关系,代码里是通过定义一个parent属性,来达到拥有父加载器的效果。
如果还是不懂就说的直白一些:加载一个类的时候首先通过customer的缓存去找,没有找到,就去app的缓存中找,也没找到就去exdention的缓存找,如果还没有就去bootstrap去找。如果最后还没有,就会去委托bootstrap去加载,不在负责范围内,再去委托extension加载,不在负责范围内,再去委托app去加载,不在负责范围内,再去委托customer加载,最后如果还不能加载,抛出异常classNotFountException。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized(this.getClassLoadingLock(name)) { Class<?> c = this.findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (this.parent != null) { c = this.parent.loadClass(name, false); } else { c = this.findBootstrapClassOrNull(name); } } catch (ClassNotFoundException var10) { } if (c == null) { long t1 = System.nanoTime(); c = this.findClass(name); PerfCounter.getParentDelegationTime().addTime(t1 - t0); PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); PerfCounter.getFindClasses().increment(); } } if (resolve) { this.resolveClass(c); } return c; } }
4.如何打破双亲委派? 什么情况下需要打破?
只要重写loadClass就可以打破双亲委派
jdk1.2的时候必须重写loadClass,所以是强制性的打破。
一个类的不同版本的热部署。
5.自定义类加载器的实现
package com.wjr.jvm; import java.io.*; import java.lang.reflect.InvocationTargetException; public class J001_CustomerClassLoader extends ClassLoader{ @Override public Class<?> findClass(String name){ String filePath = "/Users/banana/myproject/MyJava/MY/jlab/src/main/java/"+name.replace(".","/")+".class"; File file = new File(filePath); FileInputStream fileInputStream; byte[] bytes = new byte[0]; try { fileInputStream = new FileInputStream(file); bytes = fileInputStream.readAllBytes(); } catch (IOException e) { e.printStackTrace(); } return defineClass(name, bytes, 0, bytes.length); } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { J001_CustomerClassLoader j001_customerClassLoader = new J001_CustomerClassLoader(); Class<?> aClass = j001_customerClassLoader.loadClass("com.wjr.jvm.Test_HelloWorld"); Test_HelloWorld test_helloWorld = (Test_HelloWorld) aClass.getDeclaredConstructor().newInstance(); System.out.println(j001_customerClassLoader.getParent().getParent()); System.out.println(j001_customerClassLoader.getParent()); System.out.println(aClass.getClassLoader()); System.out.println(aClass); test_helloWorld.sayHello(System.currentTimeMillis()); } } 自定义类加载器代码
6.类加载过程
Loading(加载) -> Linking(链接) -> Initalization(初始化)
其中Linking分成三个小部分:
Validation(验证) -> Preparation(准备) -> Resolution(解析)
Loading
将二进制文件装载进入内存
Linking
Valifation
校验二进制文件是否合规
Preparation
给Class文件的静态成员变量付默认值
Resolution
将一些类、方法符号引用转换成内存地址引用,比如将java/lang/Object转换成实际地址引用
Initalization
给对象副初始值,调用静态代码块
7.半初始化问题
半初始化状态引发的单例初始值被使用的问题
invokespecial 和 aload指令可以重排序,导致它的半初始化,aload已经把
比如一个DCL(Double Check Loading)的单例模式如果单例不加volatile修饰,那么由于上面的指令重排序的问题,会导致aload提前执行,其他线程会提前拿到没有完全初始化的对象,那么对象里的各种属性没有被初始化,只有默认值。这个时候就会出现问题。

浙公网安备 33010602011771号