ClassLoader工作机制及几种加载类错误分析

1、ClassLoader工作机制

 

职责:

负责将class加载到JVM中,审查每个类由谁加载(一种父优先的等级加载机制),将class字节码重新解析成JVM统一要求的对象格式

 

类定义:

ClassLoader(抽象类)   <-----     URLClassLoader(常用的实现类)

  • defineClass(byte[],int,int) -- 将byte字节流解析成JVM能够识别的对象
  • findClass(String) -- 实现类的加载规则,取得上述函数需要的字节码
  • loadClass(String) -- 获取这个类的Class对象
  • resolveClass(Class<?>) -- 类的链接(link)

 

2、等级加载机制

 

说明:上级委托接待机制,首先检查是否已被自己加载过,之后询问上一级,上一级同样检查是否加载过,然后询问上一级是否需要上一级加载,如果不需要,则检查自己的加载规则,判断是否自己加载。

BootStrapClassLoader -- JVM自身工作需要的类,服务自身,无父/子加载器

ExtClassLoader -- 服务特定目标,System.getProperty("java.ext.dirs")

AppClassLoader -- 父类是ExtClassLoader,System.getProperty("java.class.path")

 

相关阅读:

深入理解Java虚拟机——JVM类加载机制(类加载过程和类加载器):

https://blog.csdn.net/zangdaiyang1991/article/details/86534077

 

3、常见加载类错误分析

 

1、概念澄清:

 

JVM加载class文件到内存有两种方式。


◎ 隐式加载:所谓隐式加载就是不通过在代码里调用ClassLoader来加载需要的类,而是通过JVM来自动加载需要的类到内存的方式。例如,当我们在类中继承或者引用某个类时,JVM在解析当前这个类时发现引用的类不在内存中,那么就会自动将这些类加载到内存中。


◎ 显式加载:相反的显式加载就是我们在代码中通过调用ClassLoader类来加载一个类的方式,例如,调用this.getClass.getClassLoader().loadClass()或者Class. forName(),或者我们自己实现的ClassLoader的findClass()方法等。


其实这两种方式是混合使用的,例如,我们通过自定义的ClassLoader显示加载一个类时,这个类中又引用了其他类,那么这些类就是隐式加载的。

 

2、ClassNotFoundException

 

这个异常通常发生在显式加载类的时候,例如,用如下方式调用加载一个类时就报这个错了:

public class notfountexception {
   public static void main(String[] args) {
     try {
         Class.forName("notFountClass");
     } catch (ClassNotFoundException e) {
         e.printStackTrace();
     }
   }
}


显式加载一个类通常有如下方式:
◎ 通过类Class中的forName() 方法。
◎ 通过类ClassLoader中的loadClass() 方法。
◎ 通过类ClassLoader中的findSystemClass() 方法。


出现这类错误也很好理解,就是当JVM要加载指定的文件的字节码到内存时,并没有找到这个文件对应的字节码,也就是这个文件并不存在。解决的办法就是检查当前的classpath目录下有没有指定的文件存在。如果不知道当前classpath路径,可以通过如下命令来获取:
this.getClass().getClassLoader().getResource("").toString()

 

3、NoClassDefFoundError

 

NoClassDefFoundError是另外一个经常遇到的异常,这个异常在第一次使用命令行执行Java类时很可能会碰到,如下面这种情况:
 

java –cp example.jar Example


这个jar包里面就一个类,这个类是net.xulingbo.Example,可能让你感到郁闷的是,明明这个jar包里有这个类为啥会报如下这个错误呢?
 

Exception in thread "main" java.lang.NoClassDefFoundError: example/jar
Caused by: java.lang.ClassNotFoundException: example.jar
     at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
     at java.security.AccessController.doPrivileged(Native Method)
     at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
     at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)


这是因为你在命令行中没有加类的包名,正确的写法是这样的:
 

java –cp example.jar net.xulingbo.Example


这里同时报了NoClassDefFoundError和ClassNotFoundException异常,原因是Java虚拟机隐式加载example.jar后再显式加载Example这个类没有找到,所以是ClassNotFoundException引发了NoClassDefFoundError异常。
JVM的规范中描述了出现NoClassDefFoundError可能的情况就是使用new关键字、属性引用某个类、继承了某个接口或类,以及方法的某个参数中引用了某个类,这时会触发JVM隐式加载这些类时发现这些类不存在的异常。


解决这个错误的办法就是确保每个类引用的类都在当前的classpath下面。

 

4、UnsatisfiedLinkError


这个异常倒不是很常见,但是出错的话,通常是在JVM启动的时候,如果一不小心将JVM中的某个lib删除了,可能就会报这个错误了,如下:

 

public class NoLibException {
   public native void nativeMethod();
   static {
     System.loadLibrary("NoLib");
   }
   public static void main(String[] args) {
     new NoLibException().nativeMethod();
   }
}


这个错误通常都是在解析native标识的方法时JVM找不到对应的本机库文件时出现,如下:
 

 Exception in thread "main" java.lang.UnsatisfiedLinkError: no NoLib in
java.library.path
   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)
   at java.lang.Runtime.loadLibrary0(Runtime.java:840)
   at java.lang.System.loadLibrary(System.java:1084)
   at  EmptyProject.classloader.NoLibException.<clinit>(NoLibException.
java:12)
   at java.lang.Class.forName0(Native Method)
   at java.lang.Class.forName(Class.java:186)
   at com.intellij.rt.execution.application.AppMain.main(AppMain.java:107)


5、 ClassCastException


这个错误也很常见,通常在程序中出现强制类型转换时出现这个错误,如下面这段代码所示:
 

public class CastException {
   public static Map m = new HashMap(){{
     put("a","2");
   }};
   public static void main(String[] args) {
     Integer isInt = (Integer)m.get("a");
     System.out.print(isInt);
   }
}


当强制将本来不是Integer类型的字符串转成Integer类型时会报如下错误:
 

Exception in thread "main" java.lang.ClassCastException: java.lang.String
cannot be cast to java.lang.Integer
   at EmptyProject.classloader.CastException.main(CastException.java:17)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessor-
Impl.java:57)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethod-
AccessorImpl.java:43)
   at java.lang.reflect.Method.invoke(Method.java:613)
   at
com.intellij.rt.execution.application.AppMain.main(AppMain.java:110)


JVM在做类型转换时会按照如下规则进行检查:
◎ 对于普通对象,对象必须是目标类的实例或目标类的子类的实例。如果目标类是接口,那么会把它当做实现了该接口的一个子类。
◎ 对于数组类型,目标类必须是数组类型或java.lang.Object、java.lang. Cloneable 、 java.io.Serializable。


如果不满足上面的规则,JVM就会报这个错误了。要避免这个错误有两种方式:
◎ 在容器类型中显式地指明这个容器所包含的对象类型,如上面的Map中可以这写为Map<String,Integer> m = new HashMap<String,Integer>(),这样上面的代码在编译阶段就会检查通过。
◎ 先通过instanceof检查是不是目标类型,然后再进行强制类型转换。


6、ExceptionInInitializerError


这个错误JVM规范中是这样定义的:


◎ 如果Java虚拟机试图创建类ExceptionInInitializerError的新实例,但是因为出现Out-Of-Memory-Error而无法创建新实例,那么就抛出OutOfMemoryError对象作为代替。
◎ 如果初始化器抛出一些Exception,而且Exception类不是Error或者它的某个子类,那么就会创建ExceptionInInitializerError类的一个新实例,并用Exception作为参数,用这个实例代替Exception。


将上面的代码例子稍微改一下:
 

public class CastException {
   public static Map m = new HashMap(){{
     m.put("a","2");
   }};
   public static void main(String[] args) {
     Integer isInt = (Integer)m.get("a");
     System.out.print(isInt);
 }
}


这段代码执行时就会报如下错误:
 

 Exception in thread "main" java.lang.ExceptionInInitializerError
   at java.lang.Class.forName0(Native Method)
   at java.lang.Class.forName(Class.java:186)
   at com.intellij.rt.execution.application.AppMain.main(AppMain.java:107)
 Caused by: java.lang.NullPointerException
   at     EmptyProject.classloader.CastException$1.<init>(CastException.
java:14)
   at     EmptyProject.classloader.CastException.<clinit>(CastException.
java:13)
   ... 3 more


在初始化这个类时,给静态属性m赋值的时候出现了异常导致抛出ExceptionInInitializerError错误。

 

参考书籍:

《深入分析Java Web技术内幕》
 



 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值