java类加载器(用户自定义类加载器实现)

java类加载器主要分为如下几种:

  • jvm提供的类加载器
  1. 根类加载器:底层实现,主要加载java核心类库(如:java.lang.*)
  2. 扩展类加载器:使用java代码实现,主要加载如:jre/lib/ext/ 下的扩展类库。(父类加载器为根类加载器)
  3. 系统类加载器(应用类加载器):使用java代码实现,加载classpath目录下的类。(父类加载器为扩展类加载器)
  • 用户自定义类加载器:去继承ClassLoader类实现自定义类加载器。
类加载器负责将java字节码文件加载到虚拟机内存中也就是类的生命周期的装载过程。(如下为类的生命周期)
类的生命周期图

下面是用户自定义类加载器的实现过程及代码:

实现一个用户自定义类加载器需要去继承ClassLoader类并重写findClass方法,代码如下

package com.space;

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

public class MyClassLoader extends ClassLoader {
    
    private String path="/home/luciel/";    //默认加载路径
    
    private String name;                    //类加载器名称
    
    private final String  filetype=".class"; //文件类型
    
    
    public MyClassLoader(String name) {
        // TODO Auto-generated constructor stub
        super();
        this.name=name;
    }
    
    public MyClassLoader(ClassLoader parent,String name){
        super(parent);
        this.name=name;
    }
    
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // TODO Auto-generated method stub
        byte[] b=loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }
    
    private byte[] loadClassData(String name) {
        byte[] data=null;
        InputStream in=null;
        name=name.replace('.', '/');
        ByteArrayOutputStream out=new ByteArrayOutputStream();
        try {
            in=new FileInputStream(new File(path+name+filetype));
            int len=0;
            while(-1!=(len=in.read())){
                out.write(len);
            }
            data=out.toByteArray();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return data;
    }
    
    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return this.name;
    }

}
public MyClassLoader(String name) {
        // TODO Auto-generated constructor stub
        super();
        this.name=name;
 }

这个构造方法中去调用ClassLoader无参构造方法从ClassLoader源码中可以得出:调用此构造方法会让系统类加载器成为该类加载器的父加载器。(注意:此处父类加载器不一定是继承关系,只是一种包装关系)。

在重写findClass方法时参照java API中实现一个网络类加载器的例子,API例子如下:

class NetworkClassLoader extends ClassLoader {
    String host;
    int port;

    public Class findClass(String name) {
     byte[] b = loadClassData(name);
     return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassData(String name) {
     // load the class data from the connection
      . . .
    }
}

下面是测试类以及main方法类的测试代码:

1 package com.space;
2 
3 public class Color {
4     public Color() {
5         // TODO Auto-generated constructor stub
6         System.out.println("Color is loaded by "+this.getClass().getClassLoader());
7         
8     }
9 }
 1 package com.space;
 2 
 3 public class Red extends Color{
 4     
 5     public Red() {
 6         // TODO Auto-generated constructor stub
 7         System.out.println("Red is loaded by "+this.getClass().getClassLoader());
 8         
 9     }
10 
11 }
 1 package com.space;
 2 
 3 public class TestMyClassLoader {
 4     
 5     public static void main(String[] args) throws Exception {
 6         
 7         MyClassLoader loader1=new MyClassLoader("loader1");
 8         
 9         loader1.setPath("/home/luciel/test1/");
10         
11         MyClassLoader loader2=new MyClassLoader(loader1, "loader2");
12         
13         loader2.setPath("/home/luciel/test2/");
14         
15         MyClassLoader loader3=new MyClassLoader(null, "loader3");
16         
17         loader3.setPath("/home/luciel/test3/");
18         
19         loadClassByMyClassLoader("com.space.Red",loader2);
20         
21         loadClassByMyClassLoader("com.space.Red",loader3);
22     }
23     
24     private static void loadClassByMyClassLoader(String name,ClassLoader loader) throws Exception{
25         
26         Class<?> c=loader.loadClass(name);
27         Object obj=c.newInstance();
28     }
29 
30 }

按照main方法中给三个类加载器传入的路径创建相应的环境并将com.space.Red、com.space.Color的class类拷贝到

/home/luciel/test1/和/home/luciel/test2/以及/home/luciel/test3/目录中去将com.space.TestMyClassLoader类和com.space.MyClassLoader拷贝
/home/luciel/main/ 中去并在该目录下执行
最终运行结果如下显示:

[root@localhost main]# java com.space.TestMyClassLoader
Color is loaded by loader1
Red is loaded by loader1
Color is loaded by loader3
Red is loaded by loader3

loadClassByMyClassLoader("com.space.Red",loader2);如测试代码中 我们调用了loader2去加载Red类但Red类却打印出由loader1加载,这是由于类加载器秉承的是父委托机制loader2在创建时包装了loader1为其父类加载器,而loader1创建时由于调用的是没有传入父类加载器的构造方法,因此它的父加载器为系统类加载器。因此几个加载器的关系如下:


由于loader1的路径下有Red类class文件所以loader1可以加载,因此载Red类构造方法中打印的类加载器为loader1.
我门看似只去加载了Red类但运行结果却将Color父类加载了,而且Color类的加载在Red类之前,那是由于Red类主动使用 了Color类,因此在初始化Red类之前必须先初始化Color类,要初始化就必须先加载,所以先打印出了Color类的输出信息。(关于类的主动使用大家如果不清楚可以查查,一共有6种)
loadClassByMyClassLoader("com.space.Red",loader3);再分析第二个测试代码,由于loader3创建时传入的父类加载器为 null,看下面关于ClassLoader类源码部分代码或查看java API

/**
     * Returns the parent class loader for delegation. Some implementations may
     * use <tt>null</tt> to represent the bootstrap class loader. This method
     * will return <tt>null</tt> in such implementations if this class loader's
     * parent is the bootstrap class loader.
     *
     * <p> If a security manager is present, and the invoker's class loader is
     * not <tt>null</tt> and is not an ancestor of this class loader, then this
     * method invokes the security manager's {@link
     * SecurityManager#checkPermission(java.security.Permission)
     * <tt>checkPermission</tt>} method with a {@link
     * RuntimePermission#RuntimePermission(String)
     * <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
     * access to the parent class loader is permitted.  If not, a
     * <tt>SecurityException</tt> will be thrown.  </p>
     *
     * @return  The parent <tt>ClassLoader</tt>
     *
     * @throws  SecurityException
     *          If a security manager exists and its <tt>checkPermission</tt>
     *          method doesn't allow access to this class loader's parent class
     *          loader.
     *
     * @since  1.2
     */
    @CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }

getParent方法的说明如下部分文字:

Returns the parent class loader for delegation. Some implementations may * use <tt>null</tt> to represent the bootstrap class loader. This method * will return <tt>null</tt> in such implementations if this class loader's * parent is the bootstrap class loader.

意思是说我们可以使用null表示 the bootstrap class loader(根类加载器)那么loader3的父类加载器就是** 根类加载器 **,而根类加载器只会去加载那些系统核心类库,显然我们的Red和Color类不属于此范围,而就只能让loader3加载,loader3的加载路径下有这两个类对应的字节码可以成功加载,所以大引出Red和Color类的类加载器为loader3



以上是自己最近在学习jvm时有关类加载器的相关知识总结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容