Java 类加载器

Java 类加载器

一个类要得以运行,必须要被类加载器加载到内存中,那么什么是类加载器呢?学习一下

1.   类加载器(ClassLoader)的定义

顾名思义,类加载器就是将Java类从其它位置(如硬盘,网络等)加载到内存中的工具,JVM中可以有多个类加载器,系统默认的类加载器有:BootStrap、ExtClassLoader、AppClassLoader,它们分别负责加载特定位置的类;当然了,类加载器也是一个类

1)        BootStrap(引导类加载器)

负责加载JRE目录下的rt.jar文件里的Java系统核心类库

这个类加载器比较特殊,它不是一个Java类,它是嵌套在Java虚拟机内核中的,它不需要被别的类加载。

2)        ExtClassLoader

负责加载加载/jre/lib/ext/目录下的扩展类库

3)        AppClassLoader

负责加载加载环境变量CLASSPATH路径下的类

 

其中的MyClassLoader都是自定义加载器

2.   类加载的过程

Java类的加载分为三个过程,分别是:加载(Load)、链接(Link)和初始化(Initialize),其中链接又分为三个步骤,如下图所示:

这三个步骤分别干了些什么呢?

1)        装载:

查找并加载类的二进制数据。

2)        链接:

a)        验证:确保被加载类的正确性;

b)        准备:为类的静态变量分配内存,并将其初始化为默认值;

c)        解析:把类中的符号引用转换为直接引用。

3)        初始化:

为类的静态变量赋予正确的初始值。

3.   类加载器的委托机制

当一个类要被Java虚拟机加载时,:

1)        首先当前线程的类加载器会去加载线程中的第一个类;

2)        如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B;

3)        还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类;

4)        每个类加载器加载类时,又先委托给其上级类加载器,当所有父类加载器都没有加载到类时,则返回到发起者类加载器,还加载不了,则抛出ClassNotFoundException异常。

委托机制如图所示

4.   类的初始化

类的初始化情况:

1)        创建类的实例,也就是new一个对象

2)        访问某个类或接口的静态变量,或者对该静态变量赋值

3)        调用类的静态方法

4)        反射(Class.forName("com.lyj.load"))

5)        初始化一个类的子类(会首先初始化子类的父类)

6)        JVM启动时标明的启动类,即文件名和类名相同的那个类

只有这6中情况才会导致类的类的初始化。

类的初始化步骤:

1)        如果这个类还没有被加载和链接,那先进行加载和链接

2)        假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)

3)        加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

说了那么多的概念,下面来写一段代码说明一下:

测试BootStrap、ExtClassLoader、AppClassLoader的加载路径:

package learn.classloader;
 
 
import java.net.URL;
import java.net.URLClassLoader;
 
/*
分析BootstrapClassLoader/ExtClassLoader/AppClassLoader的加载路径
*/
 
public class ClassLoaderTest
{
       public static void main(String[] args)
       {
              System.out.println("BootstrapClassLoader的加载路径:");
             
              URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
              for(URL url : urls)
                     System.out.println(url);
                           
              //取得扩展类加载器
              URLClassLoader extClassLoader =(URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
 
              System.out.println(extClassLoader);
              System.out.println("扩展类加载器 的加载路径: ");
             
              urls = extClassLoader.getURLs();
              for(URL url : urls)
                     System.out.println(url);
             
 
                           
             
              //取得应用(系统)类加载器
              URLClassLoader appClassLoader =(URLClassLoader)ClassLoader.getSystemClassLoader();
             
              System.out.println(appClassLoader);
              System.out.println("应用(系统)类加载器 的加载路径: ");
             
              urls = appClassLoader.getURLs();
              for(URL url : urls)
                     System.out.println(url);
       }
}
 


程序输出:

BootstrapClassLoader 的加载路径:
file:/C:/Application/JDK/jre/lib/resources.jar
file:/C:/Application/JDK/jre/lib/rt.jar
file:/C:/Application/JDK/jre/lib/sunrsasign.jar
file:/C:/Application/JDK/jre/lib/jsse.jar
file:/C:/Application/JDK/jre/lib/jce.jar
file:/C:/Application/JDK/jre/lib/charsets.jar
file:/C:/Application/JDK/jre/lib/jfr.jar
file:/C:/Application/JDK/jre/classes
sun.misc.Launcher$ExtClassLoader@15db9742
扩展类加载器 的加载路径:
file:/C:/Application/JDK/jre/lib/ext/access-bridge-64.jar
file:/C:/Application/JDK/jre/lib/ext/cldrdata.jar
file:/C:/Application/JDK/jre/lib/ext/dnsns.jar
file:/C:/Application/JDK/jre/lib/ext/jaccess.jar
file:/C:/Application/JDK/jre/lib/ext/jfxrt.jar
file:/C:/Application/JDK/jre/lib/ext/localedata.jar
file:/C:/Application/JDK/jre/lib/ext/nashorn.jar
file:/C:/Application/JDK/jre/lib/ext/sunec.jar
file:/C:/Application/JDK/jre/lib/ext/sunjce_provider.jar
file:/C:/Application/JDK/jre/lib/ext/sunmscapi.jar
file:/C:/Application/JDK/jre/lib/ext/sunpkcs11.jar
file:/C:/Application/JDK/jre/lib/ext/zipfs.jar
sun.misc.Launcher$AppClassLoader@73d16e93
应用(系统)类加载器 的加载路径:
file:/F:/Java/Learn/bin/

这个测试很好的体现了三个系统类加载器的加载机制

5.   自定义类加载器

1)        自定义加载器原理分析:

类加载器中的loadClass方法内部实现了父类委托机制,因此我们没有必要自己覆盖loadClass,否则需要自己去实现父类委托机制。我们只需要覆盖findClass方法。loadClass方法中调用了findClass方法,使用的是模板设计模式。我们得到了Class文件后,就可以通过defineClass方法将二进制数据转换成字节码。加载过程中会先检查类是否被已加载,检查顺序是自底向上,从自定义类加载器(Custom ClassLoader)到BootStrap逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次,而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类,这就是自定义类加载器的编写原理。

下面我们通过代码来自己写一个类加载器对类进行加密和解密

 

加密解密(因为是对二进制数进行取反操作,加密函数encrypt()既可以加密也可以解密):

package learn.classloader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader {
       /**类目录*/
       private String classDir;
 
       public MyClassLoader(String classDir) {
              this.classDir = classDir;
       }
       /**加密算法*/
       public static void encrypt(InputStream ips, OutputStream ops)
                     throws Exception {
              int by = 0;
              while ((by = ips.read()) != -1) {
                     /*对每一位二进制取反,进行加密*/
                     ops.write(by ^ 0xff);
              }
       }
      
       @SuppressWarnings("deprecation")
       @Override
       protected Class<?> findClass(String name) throws ClassNotFoundException{
 
              String classFileName = classDir + "\\"
                            + name.substring(name.lastIndexOf(".") + 1) + ".class";
              try {
                     /*获取class文件二进制流*/
                     InputStream ips = new FileInputStream(classFileName);
                     ByteArrayOutputStreambos = newByteArrayOutputStream();
                     /*加密*/
                     encrypt(ips, bos);
                     ips.close();
                     byte[] bytes = bos.toByteArray();
                     return defineClass(bytes, 0, bytes.length);
              } catch (Exception e) {
                     e.printStackTrace();
              }
              return super.findClass(name);
       }
      
       /**测试*/
       public static void main(String[] args) throws Exception {
 
              String srcPath = args[0];
              String destPath = args[1]
                            + "\\"
                            + srcPath.substring(srcPath.lastIndexOf("\\") + 1,
                                          srcPath.length());
              InputStream ips = new FileInputStream(srcPath);
              OutputStream ops = new FileOutputStream(destPath);
              /*加密*/
              encrypt(ips, ops);
              ips.close();
              ops.close();
              /*创建类实例
              MyClassLoader myClassLoader=newMyClassLoader("");
              Class clazz=myClassLoader.findClass("");
              clazz.newInstance();*/
       }     
}


 

关于类加载器就见到这里。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值