java 加载器_深入理解Java类加载器

本文主要内容

类加载器基本概念

自定义类加载器

类的隔离

Android类加载器案例

虚拟机类加载机制 文中已经对类加载机制详细阐述了,这两天对类的隔离,破坏双亲委托机制等内容有了新的理解,同时阐述下Android上类加载器案例。

以双亲委托机制图镇楼:

fa2b80c1ecf7?utm_source=oschina-app

类加载器基本概念

顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。

有了Class类实例,就可以通过newInstance方法创建该类的对象。

一般来说,默认类加载器为当前类的类加载器。比如A类中引用B类,A的类加载器为C,那么B的类加载器也为C。

1、ClassLoader

ClassLoader类是一个抽象类,它定义了类加载器的基本方法。

方法

说明

getParent()

返回该类加载器的父类加载器。

loadClass(String name)

加载名称为 name的类,返回的结果是 java.lang.Class类的实例。

findClass(String name)

查找名称为 name的类,返回的结果是 java.lang.Class类的实例。

findLoadedClass(String name)

查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。

defineClass(String name, byte[] b, int off, int len)

把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。

来看看 loadClass 方法的代码:

protected Class> loadClass(String name, boolean resolve){

Class c = findLoadedClass(name);

if (c == null) {

if (parent != null) {

//使用父加载器加载此类

c = parent.loadClass(name, false);

}

if (c == null) {

// 如果父加载器没有成功加载,则自己尝试加载

c = findClass(name);

}

}

return c;

}

这段代码定义了双亲委托模型。所以自定义类加载器尽量不要去重写 loadClass ,而应该重写 findClass 方法。下边让我们来实现一个自定义类加载器。

自定义类加载器

自定义类加载器还是很有必要的,尤其是在web服务器上,比如tomcat,自定义加载器能够指定自身加载类的范围,甚至通过继承关系,达到类的隔离目的。

Android上也有自定义类加载器,Android上的策略是,每个apk都由不同的类加载器实例来加载。思考一下,如果两个apk中有相同名字的类,如果由同一个类加载器实例来加载,那肯定会混淆。另一方面也是安全问题。

通过前一章的学习,可知自定义类加载器一般只重写findClass方法即可。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。

public class FileSystemClassLoader extends ClassLoader {

private String rootDir;

public FileSystemClassLoader(String rootDir) {

this.rootDir = rootDir;

}

protected Class> findClass(String name) throws ClassNotFoundException {

byte[] classData = getClassData(name);

if (classData == null) {

throw new ClassNotFoundException();

}

else {

return defineClass(name, classData, 0, classData.length);

}

}

private byte[] getClassData(String className) {

String path = classNameToPath(className);

try {

InputStream ins = new FileInputStream(path);

ByteArrayOutputStream baos = new ByteArrayOutputStream();

int bufferSize = 4096;

byte[] buffer = new byte[bufferSize];

int bytesNumRead = 0;

while ((bytesNumRead = ins.read(buffer)) != -1) {

baos.write(buffer, 0, bytesNumRead);

}

return baos.toByteArray();

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

private String classNameToPath(String className) {

return rootDir + File.separatorChar

+ className.replace('.', File.separatorChar) + ".class";

}

}

类的隔离

类的隔离主要有以下两点:

不同的类加载器为相同名称的类创建了额外的名称空间,不同类加载器加载的类是不兼容的。

为了安全或其它目的,使某个模块无法加载某个类。

第1点比较简单,网上有很多这方面的文章,在此不再阐述。

第2点稍等复杂点,以tomcat为例,tomcat服务器所用到的jar包不希望web应用应用,于是tomcat设计了以下经典的类加载模型:

fa2b80c1ecf7?utm_source=oschina-app

其中webApp应用的类加载器为 WebApp类加载器,而tomcat服务器类加载器为 Catalina类加载器,如果webApp想加载tomcat所使用的jar包,它先会委托它的父加载器去加载,根据上图所示,它的父加载器也无法加载(因为tomcat所引用jar包全由Catalina加载,而Catalina并不是WebApp的父加载器),由于它自己也无法加载,所以实现隔离。

在双亲委托机制下,同级的类加载器,可以实现类的隔离。

另外,顶层类加载器限制较大,有时它无法加载类也无法委托子类加载器去加载,也会导致隔离。

1、线程上下文类加载器

JDBC是Java开发者经常遇到的内容,它的核心类为java.sql.DriverManager,查看它的包名,就知道此类是由 启动类加载器(Bootstrap ClassLoader)加载,但JDBC的具体驱动实现是由各个厂商自己实现的。DriverManager需要调用由厂商自己实现的接口,这些接口是由 用户程序类加载器(Application ClassLoader)加载。

由前文知,DriverManager由Bootstrap加载,当DriverManager引用厂商实现的JDBC接口时,DriverManager仍然会使用自己的类加载器,也就是Bootstrap去加载,但Bootstrap只能加载JAVA_HOME下的class文件,厂商实现的JDBC接口,Bootstrap无法加载。这种问题,双亲委托模型已经无法解决了。

为了解决此问题,Java开了后门,也就是添加了线程上下文类加载器,Java为每个线程设置了默认的线程上下文类加载器:Application ClassLoader,当出现上述情况时,直接使用线程上下文类加载器加载。

try {

loader = AppClassLoader.getAppClassLoader(extcl);

} catch (IOException e) {

throw new InternalError(

"Could not create application class loader");

}

// Also set the context class loader for the primordial thread.

Thread.currentThread().setContextClassLoader(loader);

在JDBC的例子中,DriverManager不再使用自己的类加载器(Bootstrap)去加载,而是使用线程上下文类加载器去加载,而线程上下文类加载器就是 用户程序类加载器(Application ClassLoader),这当然能加载类成功。

这种父类加载器请求子类加载器去加载类的行为,实质上已经破坏了双亲委托模型。

2、Class.forName

JDBC在使用之前,一定要调用一句话,Class.forName,很多人告诉我,这是要去加载驱动类。

Class.forName("com.mysql.jdbc.Driver");

仔细想一想,这不对,如果代码引用了某个类,会去自动加载类,不需要用户手动加载,除非是当前的类加载器无法加载此类。

查看Driver类的源码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {

//

// Register ourselves with the DriverManager

//

static {

try {

java.sql.DriverManager.registerDriver(new Driver());

} catch (SQLException E) {

throw new RuntimeException("Can't register driver!");

}

}

}

类的加载,会有一个初始化阶段,在初始化阶段会执行类的 clinit 方法,也就是会执行类的静态语句块。

通过以上线索发现,其实调用 Class.forName并不是要加载驱动类,而是调用驱动类的静态语句块,向DriverManager注册自己而已。

Android类加载器案例

Android apk动态加载研究 文中提到了Context的类加载器与当前apk的类加载器相同,其实这句话是不对的,只是Context类重写了getClassLoader方法。

public ClassLoader getClassLoader() {

return mPackageInfo != null ?

mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();

}

并不是Context的类加载器不同,而是通过LoadedApk获取的类加载器不同。

mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,

mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,

libraryPermittedPath, mBaseClassLoader);

LoadedApk的getClassLoader方法中,根据apk的路径等参数,生成了新的ClassLoader,以确保不同的apk对应着不同的ClassLoader。

PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(

zip,

librarySearchPath,

libraryPermittedPath,

parent,

targetSdkVersion,

isBundled);

比如说,在ActivityThread类中,生成新的Activity时,就使用了新生成的Classloader,确保不同apk的Activity是由不同Classloader生成的。

Activity activity = null;

try {

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();

activity = mInstrumentation.newActivity(

cl, component.getClassName(), r.intent);

StrictMode.incrementExpectedActivityCount(activity.getClass());

r.intent.setExtrasClassLoader(cl);

r.intent.prepareToEnterProcess();

if (r.state != null) {

r.state.setClassLoader(cl);

}

} catch (Exception e) {

。。。

}

newActivity的代码如下:

public Activity newActivity(ClassLoader cl, String className,

Intent intent)

throws InstantiationException, IllegalAccessException,

ClassNotFoundException {

return (Activity)cl.loadClass(className).newInstance();

}

其实就是调用ClassLoader 加载具体Activity全类名,然后调用newInstance生成一个新的对象。

阅读源码往往有意想不到的收获,当时怀疑为啥Context的类加载器不一样,一读源码,收获还挺多的,大家遇到疑问多多读源码吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值