类加载机制分析并附代码解析(下)

类加载机制上篇  https://blog.csdn.net/qq_36144187/article/details/81332970

概要

  1.      类加载器分为 启动类加载器(Bootstrap)、拓展类加载器(Extension)、应用程序类加载(Application/SystemApp)、自定义类加载器。
  2.      不同的类加载器加载的Class的类类型不相等。
  3.      类加载器采用双亲委派模型,即从上到下依次加载。

类加载器分类

启动类加载器(Bootstrap):由C++实现,负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中。注意,为了安全只接受特定名称的jar包,没有父类加载器。

拓展类加载器(Extension):Sun公司使用java实现的sun.misc.Launcher$ExtClassLoader类,负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库。父加载器为NULL。

应用程序类加载器(Application): Sun公司使用java实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,父加载器为ExtClassLoader。

自定义类加载器:由开发者自己编写的加载器,父类加载器为AppClassLoader。

假如你需要定义自定义类加载器就可以用以下方法,只需继承自ClassLoader,重载findClass方法。

public class MyClassLoader extends ClassLoader
{
	private Static String Fileurl="C:\";

	MyClassLoader(String Fileurl){
		this.Fileurl=Fileurl;
	}
	
    //重载findClass方法即可
	protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        File file = getClassFile(name);
        try
        {
            byte[] bytes = getClassBytes(file);
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    //获取文件位置
    private File getClassFile(String name)
    {
        File file = new File(Fileurl);
        return file;
    }
    //获取文件二进制流
    private byte[] getClassBytes(File file) throws Exception
    {
        // 这里要读入.class的字节,因此要使用字节流
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);
        
        while (true)
        {
            int i = fc.read(by);
            if (i == 0 || i == -1)
                break;
            by.flip();
            wbc.write(by);
            by.clear();
        }
        
        fis.close();
        
        return baos.toByteArray();
    }
}

 

类加载器命名空间

          对于任意一个类,都需要加载它的类加载器和这个类本身一同确立在JVM中的唯一性,每个类加载器,都拥有一个独立的类名称空间。可以影响equals(),isAssignableFrom(),isInstance()方法的返回值。

          以下测试类展现了该特性。

//复用了以上的MyClassLoader类
public class TestClassLoader {
	public static void main(String[] args) {
		try {
			MyClassLoader mcl =new MyClassLoader("F:/Person.class");
			MyClassLoader mcl1 =new MyClassLoader("F:/Person.class");
			Class<?> C1=Class.forName("com.xrq.classloader.Person", true, mcl);
			Class<?> C2=Class.forName("com.xrq.classloader.Person", true, mcl1);
			System.out.println("两个加载器是否相同:"+mcl.equals(mcl1));
			System.out.println("两个类是否相同:"+C1.equals(C2));
			Object person=cpreson.newInstance();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
/*\
output:
两个加载器是否相同:false
两个类是否相同:false
*/

注意,请不要使用java源码中的类测试,有可能触发双亲委派机制,导致加载的类相同。

双亲委派模型

      正如上文所说的,每个类加载器都有自己的父加载器,同时如果Class 由不同的类加载会影响某些关键判断语句,所以双亲委派机制应运而生,这样可以有效避免一个Class被加载两次。

      父类加载器和类加载器类,他们并不是继承关系,查看源码可知:

//节选自Java源码:sun.misc.Launcher 
public class Launcher {
    static class AppClassLoader extends URLClassLoader {
        //详细内容略
    }
    static class ExtClassLoader extends URLClassLoader {
        //详细内容略
    }
}
        

他们都是launcher的静态内部类,且都继承自URLClassLoader,并不是继承关系。总体继承关系为下图:

 此图出自 https://blog.csdn.net/briblue/article/details/54973413

以下部分java源码展示了,ExtClassLoader的父加载器类为null,和AppClassLoader的父加载器为ExtClassLoader的事实。以及ExtClassLoader的父加载器虽然为null,但如何交由BootstrapClassLoader的过程。

//sun.misc.Launcher
public Launcher() {
   // Create the extension class loader
   ClassLoader extcl;
   try {
       extcl = ExtClassLoader.getExtClassLoader();
   } catch (IOException e) {
       throw new InternalError(
            "Could not create extension class loader", e);
   }

   // Now create the class loader to use to launch the application
   try {
       loader = AppClassLoader.getAppClassLoader(extcl);
   } catch (IOException e) {
       throw new InternalError(
            "Could not create application class loader", e);
   //省略部分
}
//经过一顿查找之后,发现以下部分,可以部分代表双亲委派机制。
//java.lang.Classloader
protected synchronized Class<?> loadClass(String name, boolean resolve){
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
        //优先交给父加载类
        if (parent != null) {
            c = parent.loadClass(name, false);
        }
        //不然交由BootstrapClass加载器加载
        else {
            c = findBootstrapClass(name);
        }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

但是双亲委托模型并不是一个强制性约束的模型。

     1.继承ClassLoader类但是重写LoadClass方法。

     2.为了JNDI服务而产生的ContextClassLoader。

     3.为了热部署(hotSwap)替换每个Bundle(程序模块)时,连同类和类加载器一起替换。

部分内容引用地址

     https://blog.csdn.net/briblue/article/details/54973413

     https://www.cnblogs.com/szlbm/p/5504631.html

     《深入理解JVM》 周志明

     

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值