类加载器作用就是将.class文件读进方法区,并将其封装成一个java.lang.Class对象。

类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果。

分类

1.jvm自己的类加载器也就是引导类加载器和自定义加载器,引导类加载器由c/c++实现,而自定义加载器由java实现并且派生于抽象类ClassLoader。
2.在虚拟机中存在引导类加载器BootStrapClassLoader、自定义类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader)
image.png
启动类加载器(Bootstrap Class Loader)
1、这个类加载器使用c/c++实现,嵌套在jvm内部
2、它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、
resource.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。 3、并不继承自java.lang.ClassLoader,没有父加载器
扩展类加载器
1、java语言编写,由sun.misc.Launcher$ExtClassLoader实现 2、从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的JAR 放在此目录下,也会自动由扩展类加载器加载;派生于 ClassLoader。 3、父类加载器为启动类加载器
系统类加载器
1、java语言编写,由 sun.misc.Lanucher$AppClassLoader 实现 2、该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载的,它负责加载环境变量classpath或系统属性java.class.path 指定路径下的类库;派生于 ClassLoader 3、父类加载器为扩展类加载器 4、通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

双亲委派

概念

当类加载器收到类加载的请求始,当前类加载器不会立马去执行,而是去委派给父类去加载。只有父类加载不到的时候子类才会去加载。

作用

防止恶意注入,如果写了一个与java自带类相同全限定名的类,在这个类中注入不可靠的代码时,如果没有双亲委派的机制,jvm就会去直接加载到这个类,存在风险。存在双亲委派模型时由于启动类已经加载了系统中的这个类,当自己的类开始加载时,会往父类加载器中寻找有没有加载过该类,有加载过就直接使用而不会去加载自己写的同名类。

java中类加载时双亲的实现

1.先找是否存在当前这个类
2.不存在则开始加载,判断当前类有没有父类,有父类的话交给父类加载做一个递归,不停的去找父类加载,没有父类就直接返回一个由bootstrap类加载器加载的类;如果没有找到,则返回null。
3.当返回null时,就该调用自己的类加载器来进行加载

  1. protected Class<?> loadClass(String name, boolean resolve)
  2. throws ClassNotFoundException
  3. {
  4. synchronized (getClassLoadingLock(name)) {
  5. // First, check if the class has already been loaded
  6. Class c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // ClassNotFoundException thrown if class not found
  17. // from the non-null parent class loader
  18. }
  19. if (c == null) {
  20. // If still not found, then invoke findClass in order
  21. // to find the class.
  22. long t1 = System.nanoTime();
  23. c = findClass(name);
  24. // this is the defining class loader; record the stats
  25. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  26. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  27. sun.misc.PerfCounter.getFindClasses().increment();
  28. }
  29. }
  30. if (resolve) {
  31. resolveClass(c);
  32. }
  33. return c;
  34. }
  35. }

实现自定义加载器的时候要重写这个findClass方法

  1. protected Class<?> findClass(String name) throws ClassNotFoundException {
  2. throw new ClassNotFoundException(name);
  3. }

自定义类加载器

为什么要使用自定义

隔离加载类: 在tomcat这类web应用服务器,内部自定义了好几中类加载器,用于隔离web应用服务器上的不同应用程序。
修改类的加载方式:除了Bootstrap加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
扩展加载源:比如还可以从数据库、网络、或其他终端上加载
防止源码泄漏:java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

自定义类加载的调用时机

image.png

实现

存在两种实现方式

1) 重写loadClass方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制,不推荐)
2)重写findClass方法 (推荐)
重写findClass方法即可,在类加载的过程中如果找不到父加载器的时候会调用findClass方法,此时就调用到自己写的实现类中的方法

  1. protected Class<?> findClass(String name) throws ClassNotFoundException {
  2. //读取字节码的流
  3. BufferedInputStream bfis = null;
  4. //在类加载器加载class文件的时候就会把文件封装成字节数组(原生就这么操作的)
  5. ByteArrayOutputStream baos = null;
  6. String file = path+name+".class";
  7. try {
  8. //输入流
  9. bfis = new BufferedInputStream(new FileInputStream(file));
  10. //输出流
  11. baos = new ByteArrayOutputStream();
  12. //io读取
  13. int length;
  14. byte[] data = new byte[1024];
  15. while ((length = bfis.read(data)) != -1){
  16. baos.write(data,0,length);
  17. }
  18. //获取字节数组
  19. byte[] byteCode = baos.toByteArray();
  20. //将字节数组转为Class对象
  21. Class<?> aClass = defineClass(null, byteCode, 0, byteCode.length);
  22. return aClass;
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. } finally {
  26. try {
  27. baos.close();
  28. bfis.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. return null;
  34. }