JVM类加载器原理与自定义类加载器

类加载器原理

JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class

对象,作为方法区类数据的访问入口。

类缓存

标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class过象。

类加载器数状结构

引导类加载器(bootstrap class loader)

它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.Path路径下的内容),是用原生代码来实现的,并不继承自java.lang.classloader

加载扩展类和应用程序类加载器,并指定他们的父类加载器

扩展类加载器(extensions class loader)

用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jarjava.ext.dirs路径下的内容)。

Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。

sun.misc.Launcher$ExtClassLoader实现。

应用程序类加载器(application class loader)

它根据Java应用的类路径(classpath,java.class.path类。

一般来说,Java应用的类都是由它来完成加载的。

sun.misc.Launcher$AppClassLoader实现。

自定义类加载器

开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

java.lang.ClassLoader

基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。

除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文

件和配置文件等。

相关方法:

getParent()     返回委托的父类加载器。
loadClass(String name, boolean resolve)     使用指定的二进制名称来加载类。
findClass(String name)      使用指定的二进制名称查找类。
findLoadedClass(String name)    如果Java虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。
defineClass(String name, byte[] b, int off, int len)    将一个 byte 数组转换为 Class 类的实例。
resolveClass(Class<?> c)    链接指定的类。

类加载器的代理模式–双亲委托机制

代理模式–交给其他加载器来加载指定的类。

双亲委托机制

就是某个特定的类加载器在接到加载器的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委托机制是为了保证Java核心库的类型安全。这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。

类加载器除了用于加载类,也是安全的最基本的屏障。

双亲委托机制是代理模式的一种。

**并不是所有的类加载器都采用双亲委派机制。**tomoat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。

双亲委派机制Demo:

自定义的java.lang.String

package java.lang;

public class String {
    @Override
    public String toString() {
        return "defineString";
    }
}
package JVMProcess;

public class LoadClass {
    public static void main(String[] args) {

        test();

        System.out.println("#####################");

        String a = "LYN";
        //类加载时,采用双亲委派机制,先加载父类,如果没有,再加载子类。
        //实际上加载的是jdk自己提供的包,并没有加载自己定义的java.lang.String
        System.out.println(a.getClass().getClassLoader());
        System.out.println(a);

    }

    public static void test(){
        System.out.println(ClassLoader.getSystemClassLoader());//应用类加载器
        System.out.println(ClassLoader.getSystemClassLoader().getParent());//扩展类加载器
        //引导类加载器  JAVA_HOME/jre/lib/rt.jar
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());

        System.out.println(System.getProperty("java.class.path"));
    }

}

运行结果:

sun.misc.Launcher$AppClassLoader@5b02a6
sun.misc.Launcher$ExtClassLoader@10aefdb
null
G:\program\javase\JVM\bin
#####################
null
LYN

自定义类加载器

继承:java.lang.ClassLoader

首先检查请求的类型是否已经被这个类装载器装载到命名空间中了

如果已经装载,直接返回;

如果没有装载,委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器的Class实例。

如果父类没有装载,再调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取得到,则调用defineClass(…)导入类型到方法区;

如果获取不到对应的字节码或者其他原因失败,返回异常给loadclass(…),loadclass(…)转抛异常,终止加载过程。

注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。

文件系统类加载器Demo

package JVMProcess;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定义文件系统类加载器
 */
public class FileSystemClassLoader extends ClassLoader{
    //com.lgd.User    -->  d:/myjava/    com/lgd/User.class
    private String rootDir;

    public FileSystemClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    //重写方法
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException{
        Class<?> c = findLoadedClass(name);//查找已加载的类
        //应该要先查询有没有加载过这个类。如果已经加载,不为空,则直接返回加载好的类。
        //如果没有,则加载新的类。
        if(c!=null){
            return c;
        } else {
            //获得他的父类,让父类去加载去加载
            ClassLoader parent = this.getParent();  //获得appclassloader

            //采用的是双亲委派机制
            try {
                c = parent.loadClass(name);//委派给父类加载
            } catch (Exception e) {
            }

            //如果不为空,返回父类加载。
            if(c!=null)
            {
                return c;
            }else{

                byte[] classData = getClassData(name);

                if(classData == null)
                {
                    throw new ClassNotFoundException();
                }else {
                    c = defineClass(name, classData, 0,classData.length);
                }
            }

        }
        return c;
    }

    //com.lgd.User    -->  d:/myjava/    com/lgd/User.class
    private byte[] getClassData(String classname)
    {

        String path = rootDir+"/"+classname.replace(‘.‘, ‘/‘)+".class";
        //IOUtils,可以使用它将流中的数据装换成字节数组
        InputStream is = null;
        //字节输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            byte[] buffer = new byte[1024];

            int temp = 0;
            while((temp = is.read(buffer))!=-1)
            {
                baos.write(buffer,0,temp);
            }
            return baos.toByteArray();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally
        {
            try {
                if(is!=null)
                {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(baos!=null)
                {
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
package JVMProcess;
/**
 * 测试自定义文件系统
 * @author liguodong
 */

public class TestFSClassLoader {

    public static void main(String[] args) {
        //加载器
        FileSystemClassLoader loader= new FileSystemClassLoader("G:/program/java/hello/bin");

        //另一个加载器
        FileSystemClassLoader loader2= new FileSystemClassLoader("G:/program/java/hello/bin");

        try {
            Class<?> c =  loader.loadClass("hello.hello");
            System.out.println(c);//class hello.hello

            //c与c2即是同一个类,也是同一个类加载器,所以他们是同一个对象。
            Class<?> c2 =  loader.loadClass("hello.hello");//可以加载多次
            System.out.println(c+"-->"+c.hashCode());
            System.out.println(c2+"-->"+c2.hashCode());

            //同一个类被不同的类加载器加载,JVM认为也是不同的机制
            Class<?> c3 =  loader2.loadClass("hello.hello");
            System.out.println(c3+"-->"+c3.hashCode());
            //自定义类加载器
            System.out.println("-->"+c3.getClassLoader());

            Class<?> c4 =  loader2.loadClass("java.lang.String");
            System.out.println(c4+"-->"+c4.hashCode());
            //引导类加载器
            System.out.println("-->"+c4.getClassLoader());

            Class<?> c5 =  loader2.loadClass("JVMProcess.Demo");
            System.out.println(c5+"-->"+c5.hashCode());
            //应用类加载器
            System.out.println(c5.getClassLoader());//系统默认的类加载器

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

class hello.hello

class hello.hello-->13078969
class hello.hello-->13078969

class hello.hello-->27959193
-->[email protected]3020ad

class java.lang.String-->25675463
-->null

class JVMProcess.Demo-->6164599
sun.misc.Launcher$AppClassLoader@cb6009


网络类加载器Demo

package JVMProcess;

/**
 * 自定义网络类加载器
 * @author liguodong
 */

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class NetClassLoader  extends ClassLoader{
    //com.lgd.User    --> www.google.com/myjava      com/lgd/User.class
    private String rootUrl;
    public NetClassLoader(String rootUrl){
        this.rootUrl = rootUrl;
    }

    //重写方法
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        Class<?> c = findLoadedClass(name);//查找已加载的类
        //应该要先查询有没有加载过这个类。如果已经加载,不为空,则直接返回加载好的类。
        //如果没有,则加载新的类。
        if(c!=null)
        {
            return c;
        }
        else {
            ClassLoader parent = this.getParent();  //获得appclassloader
            try {
                c = parent.loadClass(name);//委派给父类加载
            } catch (Exception e) {
            }

            if(c!=null)
            {
                return c;
            }
            else {

                byte[] classData = getClassData(name);
                if(classData == null)
                {
                    throw new ClassNotFoundException();
                }else {
                    c = defineClass(name, classData, 0,classData.length);
                }
            }

        }
        return c;
    }

    //com.lgd.User    -->  www.google.com/myjava    com/lgd/User.class
    private byte[] getClassData(String classname)
    {
        String path = rootUrl+"/"+classname.replace(‘.‘, ‘/‘)+".class";
        //IOUtils,可以使用它将流中的数据装换成字节数组
        InputStream is = null;
        //字节输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            URL url = new URL(path);
            is = url.openStream();//通过url打开一个输入流

            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp = is.read(buffer))!=-1)
            {
                baos.write(buffer,0,temp);
            }
            return baos.toByteArray();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally
        {
            try {
                if(is!=null)
                {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(baos!=null)
                {
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}


加密解密类加载器Demo

首先加密类文件:

package JVMProcess;

import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * 加密工具类
 * @author liguodong
 */

public class EncrptUtil {

    public static void main(String[] args) {
        //加密
        encrypt("G:/program/java/hello/bin/hello/HelloWorld.class",
                "G:/program/java/hello/bin/temp/hello/HelloWorld.class");
    }

    public static void encrypt(String src,String dest)
    {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dest);

            int temp = -1;

            while((temp=fis.read()) != -1)
            {
                //System.out.println("--");
                //System.out.println(temp^0xff);
                fos.write(temp^0xff);//取反操作,相当于加密

            }

            //System.out.println("--");
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            if(fis != null)
            {
                fis.close();
            }
        } catch (Exception e) {
        }
        try {
            if(fos != null)
            {
                fos.close();
            }
        } catch (Exception e) {
        }
    }
}

然后编写解密的类加载器:

package JVMProcess;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * 加载文件系统中加密后的class字节码的类加载器
 * @author liguodong
 */

public class DecrptClassLoader extends ClassLoader{
    //com.lgd.User    -->  d:/myjava/    com/lgd/User.class
    private String rootDir;

    public DecrptClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    //重写方法
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        Class<?> c = findLoadedClass(name);//查找已加载的类
        //应该要先查询有没有加载过这个类。如果已经加载,不为空,则直接返回加载好的类。
        //如果没有,则加载新的类。
        if(c!=null){
            return c;
        }else
        {
            ClassLoader parent = this.getParent();  //获得appclassloader
            try {
                c = parent.loadClass(name);//委派给父类加载
            }catch (Exception e) {
            }

            if(c!=null){
                return c;
            }
            else
            {
                byte[] classData = getClassData(name);
                if(classData == null){

                    throw new ClassNotFoundException();
                }else {
                    c = defineClass(name, classData, 0,classData.length);
                }
            }

        }
        return c;
    }

    //com.lgd.User    -->  d:/myjava/    com/lgd/User.class
    private byte[] getClassData(String classname)
    {

        String path = rootDir+"/"+classname.replace(‘.‘, ‘/‘)+".class";
        //IOUtils,可以使用它将流中的数据装换成字节数组
        InputStream is = null;
        //字节输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);
            int temp = -1;
            while((temp = is.read())!=-1)
            {
                baos.write(temp^0xff);//取反操作,解密操作
                //System.out.println(temp^0xff);
            }
            /*byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp = is.read(buffer))!=-1)
            {
                baos.write(buffer,0,temp);
            }*/
            return baos.toByteArray();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();

        }finally
        {
            try {
                if(is!=null)
                {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(baos!=null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

测试:

package JVMProcess;

/**
 * 测试简单的加密解密(取反)操作
 * @author liguodong
 *
 */
public class TestEDClassLoader {

    public static void main(String[] args) throws ClassNotFoundException {
        //int a = 3;//00000011
        //System.out.println(Integer.toBinaryString(a^0xff));//11111100

        //加密后的class文件,正常的类加载器无法加载,报classFormatError
        /*
        FileSystemClassLoader loader = new FileSystemClassLoader("G:/program/java/hello/bin");
        Class<?> c = loader.loadClass("hello.HelloWorld");
        System.out.println(c);*/

        DecrptClassLoader loader = new DecrptClassLoader("G:/program/java/hello/bin/temp");
        Class<?> c = loader.loadClass("hello.HelloWorld");
        System.out.println(c);
    }
}

运行结果:

class hello.HelloWorld

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-29 22:33:44

JVM类加载器原理与自定义类加载器的相关文章

java类加载器学习2——自定义类加载器和父类委托机制带来的问题

一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载器.如果父类加载器加载不了,依次再使用其子类进行加载.当然这类所说的父类加载器,不一定他们之间是继承的关系,有可能仅仅是包装的关系. Java之所以出现这条机制,因为是处于安全性考虑.害怕用户自己定义class文件然后自己写一个类加载器来加载原本应该是JVM自己加载的类.这样会是JVM虚拟机混乱或者

CXF拦截器介绍及自定义拦截器实现

CXF拦截器是功能的主要实现单元,也是主要的扩展点,可以在不对核心模块进行修改的情况下,动态添加功能.当服务被调用时,会经过多个拦截器链(Interceptor Chain)处理,拦截器链在服务输入(IN)或输出(OUT)阶段实现附加功能,拦截器可以在客户端加入,也可以在服务端加入. 拦截器链的阶段: 拦截器链有多个阶段,每个阶段都有多个拦截器.拦截器在拦截器链的哪个阶段起作用,可以在拦截器的构造函数中声明. 输入拦截器链有如下几个阶段,这些阶段按照在拦截器链中的先后顺序排列. 阶段名称 阶段功

嵌入式调试器原理和各类调试器集锦(JLINK、STLINK、CCDEBUG)

工欲善其事,必先善其器.调试器在嵌入式开发调试中的重要性不言而喻,单步.断点和监察的效率远高于串口打印.但是,调试器对于一般开发人员往往是一个黑匣子.今天我们就来谈谈调试器的原理,顺便把自己的几类调试器接线和注意事项记录下来,以便查找.我常常要面对几个方案,而各个方案的调试器都不一样,接线有时连自己都记不住.所以这个帖子应值得嵌入式开发工程师收藏. 一.嵌入式调试多样性 我们先来回想调试的场景,思考一下这几个问题: 1. ARM开发环境有Keil.IAR.ADS等等,我们发现这几个平台都能用同一

嵌入式调试器原理和各类调试器集锦

工欲善其事,必先善其器.调试器在嵌入式开发调试中的重要性不言而喻,单步.断点和监察的效率远高于串口打印.但是,调试器对于一般开发人员往往是一个黑匣子.今天我们就来谈谈调试器的原理,顺便把自己的几类调试器接线和注意事项记录下来,以便查找.我常常要面对几个方案,而各个方案的调试器都不一样,接线有时连自己都记不住.所以这个帖子应值得嵌入式开发工程师收藏. 一.嵌入式调试多样性 我们先来回想调试的场景,思考一下这几个问题: 1. ARM开发环境有Keil.IAR.ADS等等,我们发现这几个平台都能用同一

Java内存管理-掌握自定义类加载器的实现(七)

勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoader的设计思想,读源码相对来说是比较枯燥的,还是这个是必须要走的过程,学习源码中的一些思想,一些精髓,看一下大神级人物是怎么写出那么牛逼的代码.我们能够从中学到一点点东西,那也是一种进步和成长了.本文基于上一篇文章内容,手把手写一个自定义类加载器,并且通过一些简单的案例(场景)让我们更加细致和静距

10、自定义类加载器

自定义类加载器的3个步骤: 1.继承ClassLoader: 2.重写findClass()方法: 3.在findClass()方法调用defineClass()方法: package com.shtec.classLoader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.

struts2内置拦截器和自定义拦截器详解(附源码)

一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截器,拦截器才可以正常的工作和运行.Struts 2已经为您提供丰富多样的,功能齐全的拦截器实现.大家可以至struts2的jar包内的struts-default.xml查看关于默认的拦截器与 拦截器链的配置.内置拦截器虽然在struts2中都定义了,但是并不是都起作用的.因为并不是所有拦截器都被加

struts登录案例和自定义拦截器

struts登录案例:struts.xml<struts> <constant name="struts.devMode" value="true" /> <constant name="struts.custom.i18n.resources" value="messages"></constant> <package name="basic" ext

Ember.js 入门指南——自定义序列号器

在Ember应用中,序列化器会格式化与后台交互的数据,包括发送和接收的数据.默认情况下会使用JSON API序列化数据.如果你的后端使用不同的格式,Ember Data允许你自定义序列化器或者定义一个完全不同的序列化器. Ember Data内置了三个序列化器.JSONAPISerializer是默认的序列化器,用与处理后端的JSON API.JSONSerializer是一个简单的序列化器,用与处理单个JSON对象或者是处理记录数组.RESTSerializer是一个复杂的序列化器,支持侧面加