java类加载及动态代理之JVM的classloader

本篇文章主要总结一下JVM核心知识之一的类加载机制以及实现原理,最后再介绍一个如何实现自定义类加载器?首先说一下java的运行机制,比如编写完一个java文件,jvm到底是怎么执行的?一般来说需要5个过程:

  • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

  • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

  • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

  • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析

  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

JVM存在的几种类加载形式:

系统启动类:BootstrapClassLoader

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar

扩展类加载器:ExtAppClassLoader

扫描二维码关注公众号,回复: 1679743 查看本文章

扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器

系统类加载器:AppClassLoader

也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

在了解各种类加载的作用之后,接下来还要看类加载过程尝尝使用的一种模式'双亲委托模式'

原理:除顶层的启动类加载器之外,其余的类都应该首先有自己的父类的类加载来加载,如果父类能够直接加载,就返回,如果父类

加载不了,再由自己的类加载来加载完成,具体流程可以看一下如图:



优势:

1 避免类的重复加载:因为这种加载方式,让类的加载有一种的层次关系和优先级关系,如果父类加载了,子类就不需要再加载

2 避免不安全的类被加载:java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改

自定义类加载器:

有时候因为特殊的业务需求,我们需要自己手动写一个类加载器来加载自己的类或者外部类,这个时候就需要自定义类加载了,如何实现呢?其实很简单就是我们定义一个类继承ClassLoader,然后重写里面对应的方法:主要下面三个方法

& loadClass:该方法加载指定名称的二进制文件(完整的包名类名)不过在JDK1.2版本之后,不建议重写该方法,而是直接调用,因为该方法是有classloader自己实现的

& findClass:完成自己的类加载逻辑

& defineClass:用来将byte字节流解析成JVM能够识别的Class对象

接下来实现一个简单的类加载器:

编写一个简单的person对象 就是我们要加载的class文件

public class Person {
    public void eat(){
        System.out.println("i am person,i like to eat banana...");
    }
}
 
 

定义一个classloader的实现:

package com.suning.dynamic_proxy.classloader;

/**
 * Created by jack on 2018/6/19.
 * 自定义类加载器 用户把字节码转换为class对象
 */
public class PersonClassLoader extends ClassLoader{

    public Class<?> defineMyClass(byte[] b, int off, int len)  {
        return super.defineClass(b, off, len);
    }
    
}
 
 

编写一个测试类:

package com.suning.dynamic_proxy.classloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
/**
 * Created by jack on 2018/6/19.
 * 测试自定义的类加载器
 */
public class ClassLoaderTest {
    public static void main(String[] args)throws Exception {
        File file = new File(".");
        //指定要加载的class文件
        String classPath = "/target/classes/com/suning/dynamic_proxy/classloader/Person.class";
        InputStream  input = new FileInputStream(file.getCanonicalPath()+classPath);
        byte [] result = new byte[1024];
        int count = input.read(result);

        //使用自定义类加载起
        PersonClassLoader personClassLoader = new PersonClassLoader();
        //通过类加载器获取对应的class对象
        Class<?> personClass = personClassLoader.defineMyClass(result, 0, count);

        System.out.println("class info:"+personClass.getSimpleName());
        //通过反射实例户class对象
        Object object = personClass.newInstance();
        //通过反射调用class的相关方法
        personClass.getMethod("eat").invoke(object,null);
    }
}
ok,到此为止有关classloader基础知识就差不多说完了,之后可以看一下运行效果.

猜你喜欢

转载自blog.csdn.net/qq_18603599/article/details/80743664