欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java && Android中类加载机制ClassLoader

程序员文章站 2022-07-15 17:50:14
...

一 Java中的ClassLoader

1 ClassLoader的类型

Java中的类加载器主要由两种类型:系统类加载器 & 自定义类加载器

  系统类加载器包括三种:

  • Bootstrap ClassLoader
  • Extensions ClassLoader
  • App ClassLoader      

1.1 Bootstrap ClassLoader

        用c/c++代码实现的加载器,用于加载Java虚拟机运行时所需要的系统类

        也可通过启动java虚拟机时,指定-Xbootclasspath选项,来改变Bootstrap ClassLoader的加载目录

        Java虚拟机的启动就是通过Bootstrap ClassLoader创建一个初始类完成的,由于Bootstrap ClassLoader是使用C/C++语言实现,所以该加载器不能被Java代码访问到,

        注意:Bootstrap ClassLoader并不继承java.lang.ClassLoader

1.2 Extensions ClassLoader

        用于加载Java的拓展类,拓展类的jar一般会放在$JAVA_HOME/jre/lib/ext目录下,用来提供除了系统类之外的额外功能。也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径

1.3 App ClassLoader

        负责加载当前应用程序Classpath目录下的所有jar和Class文件,也可通过-Djava.class.path选项所指定的目录下的jar和Class文件

1.4 Custom ClassLoader

        自定义类加载器通过继承java.lang.ClassLoader类的方式来实现自己的类加载器,Extensions ClassLoader & App ClassLoader也继承了java.lang.ClassLoader类。

2 ClassLoader的继承关系        

         运行一个java程序需要用到几种类型的类加载器呢?

public class ClassLoaderTest {
    public static void main(String[] args){
//        System.out.println(System.getProperty("sun.boot.class.path"));
//        System.out.println(System.getProperty("java.ext.dirs"));
        ClassLoader loader = ClassLoaderTest.class.getClassLoader();
        while(loader != null){
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

结果如下所示

Java && Android中类加载机制ClassLoader

说明:第一行说明加载ClassLoaderTest的类加载器是AppClassLoader,第二行说明AppClassLoader的父类时ExtClassLoader。 AppClassLoader的父类加载器为ExtClassLoader,并不代表AppClassLoader继承自ExtClassLoader 

关系图

Java && Android中类加载机制ClassLoader

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;
  • SecureClassLoader继承了ClassLoader,但是SecureClassLoader并不是ClassLoader的实现类,他只是拓展了ClassLoader在权限方面的功能,加强了ClassLoader的安全性;
  • URLClassLoader继承自SecureClassLoader,用来通过URL路径从jar文件和文件夹中加载类和资源;
  • ExtClassLoader & AppClassLoader都继承自URLClassLoader,它们都是Launcher的内部类,Launcher是java虚拟机的入口应用,ExtClassLoader & AppClassLoader都是在Launcher中进行初始化的

3 双亲委托模式

3.1 双亲委托模式的特点

类加载器查找Class采用的是双亲委托模式,所谓双亲委托模式就是判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader查找到了,则直接返回,如果没找到,则继续依次向下查找,如果还没找到最后会交由自身查找。

Java && Android中类加载机制ClassLoader

3.2 双亲委托模式的好处

  • 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是直接从缓存中直接读取;
  • 更加安全,

4 自定义ClassLoader

        系统提供的类加载器只能够加载指定目录下的jar包和Class文件,如果想要加载网络上的或者是D盘某一文件中的jar包和Class文件则需要自定义ClassLoader。

        实现ClassLoader需要两个步骤:

            1.定义一个自定义ClassLoader并继承抽象类ClassLoader;

            2.复写findClass方法,并在findClass方法中调用defineClass方法

4.1 编写一个自定义ClassLoader加载位于D:\test下的Class文件

    a:编写测试类并生成Class文件 Javac TestClass.java  生成TestClass.class

public class TestClass {
    public void say() {
        System.out.println("One more thing");
    }
}

 4.2 编写自定义ClassLoader

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 DiskClassLoader extends ClassLoader {
    private  String mPath;
    public DiskClassLoader(String path) {
        this.mPath = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        byte[] classData = loadClassData(name);
        if(classData == null){
            throw new ClassNotFoundException();
        }else{
            clazz = defineClass(name,classData,0,classData.length);
        }
        return clazz;
    }
    private byte[] loadClassData(String name) {
        String fileName = getFileName(name);
        File file = new File(mPath,fileName);
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length = 0;
            while ((length = in.read(buffer))!= -1){
                out.write(buffer,0,length);
            }
            return out.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
    private String getFileName(String name) {
        int index = name.lastIndexOf(".");
        //如果没有找到".",则直接在末尾添加.class
        if(index == -1){
            return name + ".class";
        }else{
            return name.substring(index + 1) + ".class";
        }
    }
}

最后验证DiskClassLoader是否可用:
public class ClassLoaderTest {
    public static void main(String[] args){
        DiskClassLoader diskClassLoader = new DiskClassLoader("F:\\Test");
        try {
            Class c = diskClassLoader.loadClass("com.aliang.a001_kotlin.TestClass");
            if(c != null){
                try {
                    Object obj = c.newInstance();
                    System.out.println(obj.getClass().getClassLoader());
                    try {
                        Method method = c.getDeclaredMethod("say", null);
                        try {
                            method.invoke(obj,null);
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

值的注意的是:在工程中不能在项目工程中存在名为com.aliang.a001_kotlin.TestClass的Java文件,否则就不会使用DiskClassLoader来加载,

而是AppClassLoader来负责加载,这样我们定义DiskClassLoader就变得毫无意义。

错误的用法:不调用自定义的ClassLoader

Java && Android中类加载机制ClassLoader

删掉com.aliang.a001_kotlin.TestClass 之后调用自定义的ClassLoader。

Java && Android中类加载机制ClassLoader

二 Android中的ClassLoader

1 ClassLoader的类型:

      Android中的ClassLoader也分为两种:系统ClassLoader和自定义ClassLoader

      系统ClassLoader分类:

  • BootClassLoader
  • PathClassLoader
  • DexClassLoader

1.1 BootClassLoader:

        Android系统启动时会使用BootClassLoader来预加载常用类,与Java中不同的是他不是用C/C++代码实现,而是用Java实现,BootClassLoader的代码如下:

class BootClassLoader extends ClassLoader {
    private static BootClassLoader instance;
    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }
        return instance;
    }

BootClassLoader是ClassLoader的内部类,并继承自ClassLoader,BootClassLoader是一个单例类,需要注意的是他的访问修饰符是默认的,只能在同一个包名中才能访问,因此我们再应用程序中是无法调用的。

1.2 DexClassLoader

        DexClassLoader可以加载dex文件以及包含dex的压缩文件(apk 和jar文件) ,不管是加载那种文件,最终都是要加在dex文件,代码如下:

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
	    }
}

DexClassLoader的构造方法由四个参数:

  • dexPath:dex相关文件路径集合,多个路径有文件分隔符分隔,默认文件分隔符为":";
  • optimizedDirectory:解压的文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data/<Package Name>/...
  • librarySearchPath:包含C/C++库的路径的集合,多个路径用文件分隔符分隔分割,可以为null
  • parent:父加载器

DexClassLoader 继承自BaseDexClassLoader,方法实现都在BaseDexClassLoader中实现

1.3 PathClassLoader

        Android系统使用PathClassLoader来加载系统类和应用程序类,代码如下:

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader继承自BaseDexClassLoader,实现也都在BaseDexClassLoader中.

PathClassLoader的构造方法中没有参数optimizedDirectory,这是因为PathClassLoader已经默认了此参数的值为:/data/dalvik-cache,很显然PathClassLoader无法自定义解压的dex文件存储路径,因此PathClassLoader通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)

2 ClassLoader的继承关系

运行一个Android程序需要用到几种类型的类加载器呢?

代码如下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader loader = MainActivity.class.getClassLoader();
        while (loader != null){
            Log.e("alvin-moon",loader.toString());
            loader = loader.getParent();
        }
    }

结果如下:可以看到有两种类加载器:一种是PathClassLoader;另一种是BootClassLoader,DexPathList中包含了很多apk的路径,

"/data/app/com.aliang.a001_kotlin-1/base.apk"就是示例应用按炸U能够在手机上的位置

09-06 14:24:31.858 12039-12039/com.aliang.a001_kotlin E/alvin-moon: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.aliang.a001_kotlin-1/base.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_0_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_2_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_4_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_5_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_7_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.aliang.a001_kotlin-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.aliang.a001_kotlin-1/lib/arm64, /vendor/lib64, /system/lib64]]]
    aaa@qq.com

和Java的ClassLoader一样,虽然系统所提供的类加载器主要由3中类型,但是系统提供的ClassLoader相关类却不只有3个.ClassLoader的继承关系如下图所示:

Java && Android中类加载机制ClassLoader

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能,BootClassLoader是他的内部类
  • SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,他继承抽象类ClassLoader,SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
  • URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
  • InMemoryDexClassLoader是Android8.0新增的类加载器,继承I自BaseDexClassLoader,用于加载内存中的dex文件
  • BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。

3.BootClassLoader的创建

BootClassLoader是何时被创建的呢?这要从Zygote进程开始说起

Zygote的main方法如下所示:

public static void main(String argv[]) {
  ...
       try {
            ...
               preload(bootTimingsTraceLog);
            ... 
       }
   }

main方法时Zygote入口方法,其中调用了ZygoteInit的preload()方法,preload方法中又调用了ZygoteInit的preloadClasses方法:

private static void preloadClasses() {
       final VMRuntime runtime = VMRuntime.getRuntime();
       InputStream is;
       try {
           is = new FileInputStream(PRELOADED_CLASSES);//1
       } catch (FileNotFoundException e) {
           Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
           return;
       }
       ...
       try {
           BufferedReader br
               = new BufferedReader(new InputStreamReader(is), 256);//2
           int count = 0;
           String line;
           while ((line = br.readLine()) != null) {//3
               line = line.trim();
               if (line.startsWith("#") || line.equals("")) {
                   continue;
               }
                 Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
               try {
                   if (false) {
                       Log.v(TAG, "Preloading " + line + "...");
                   }
                   Class.forName(line, true, null);//4
                   count++;
               } catch (ClassNotFoundException e) {
                   Log.w(TAG, "Class not found for preloading: " + line);
               } 
       ...
       } catch (IOException e) {
           Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
       } finally {
           ...
       }
   }

preloadClasses方法用于Zygote进程初始化时预加载常用类,注释1处将/system/etc/preloaded-classes文件封装成FileInputStream,preloaded-classes文件中存有预加载类目标,这个文件在系统源码中的路径为frameworks/base/preloaded-classes,这里列举一些preloaded-classes文件中的预加载类名称,如下所示:

android.app.ApplicationLoaders
android.app.ApplicationPackageManager
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$ApplicationContentResolver
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DownloadManager
android.app.Fragment

可以看到preloaded-classes文件中的预加载类的名称有很多都是我们非常熟知的。预加载属于拿空间换时间的策略,Zygote环境配置的越健全越通用,应用程序进程需要单独做的事情也就越少,预加载除了预加载类,还有预加载资源和预加载共享库,因为不是本文重点,这里就不在延伸讲下去了。
回到preloadClasses方法的注释2处,将FileInputStream封装为BufferedReader,并注释3处遍历BufferedReader,读出所有预加载类的名称,每读出一个预加载类的名称就调用注释4处的代码加载该类,Class的forName方法如下所示。

@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    if (loader == null) {
        loader = BootClassLoader.getInstance();//1
    }
    Class<?> result;
    try {
        result = classForName(name, initialize, loader);//2
    } catch (ClassNotFoundException e) {
        Throwable cause = e.getCause();
        if (cause instanceof LinkageError) {
            throw (LinkageError) cause;
        }
        throw e;
    }
    return result;
}

注释1处创建了BootClassLoader,并将BootClassLoader实例传入到了注释2处的classForName方法中,classForName方法是Native方法,它的实现由c/c++代码来完成,如下所示

@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
        ClassLoader classLoader) throws ClassNotFoundException;

4.PathClassLoader的创建

PathClassLoader的创建也得从Zygote进程开始说起,Zygote进程启动SyetemServer进程时会调用ZygoteInit的startSystemServer方法,如下所示。

private static boolean startSystemServer(String abiList, String socketName)
           throws MethodAndArgsCaller, RuntimeException {
    ...
        int pid;
        try {
            parsedArgs = new ZygoteConnection.Arguments(args);//2
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
            /*1*/
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }
       if (pid == 0) {//2
           if (hasSecondZygote(abiList)) {
               waitForSecondaryZygote(socketName);
           }
           handleSystemServerProcess(parsedArgs);//3
       }
       return true;
   }

注释1处,Zygote进程通过forkSystemServer方法fork自身创建子进程(SystemServer进程)。注释2处如果forkSystemServer方法返回的pid等于0,说明当前代码是在新创建的SystemServer进程中执行的,接着就会执行注释3处的handleSystemServerProcess方法:

private static void handleSystemServerProcess(
           ZygoteConnection.Arguments parsedArgs)
           throws Zygote.MethodAndArgsCaller {
   ...
       if (parsedArgs.invokeWith != null) {
          ...
       } else {
           ClassLoader cl = null;
           if (systemServerClasspath != null) {
               cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
               Thread.currentThread().setContextClassLoader(cl);
           }
           ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
       }
   }

注释1处调用了createPathClassLoader方法,如下所示。

static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
    String libraryPath = System.getProperty("java.library.path");
    return PathClassLoaderFactory.createClassLoader(classPath,
                                                    libraryPath,
                                                    libraryPath,
                                                    ClassLoader.getSystemClassLoader(),
                                                    targetSdkVersion,
                                                    true /* isNamespaceShared */);
  }

createPathClassLoader方法中又会调用PathClassLoaderFactory的createClassLoader方法,看来PathClassLoader是用工厂来进行创建的。

public static PathClassLoader createClassLoader(String dexPath,
                                                  String librarySearchPath,
                                                  String libraryPermittedPath,
                                                  ClassLoader parent,
                                                  int targetSdkVersion,
                                                  boolean isNamespaceShared) {
      PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
    ...
      return pathClassloader;
  }

在PathClassLoaderFactory的createClassLoader方法中会创建PathClassLoader。

总结:

BootClassLoader是在Zygote进程的入口方法中创建的,PathClassLoader则是在Zygote进程创建SystemServer进程时创建的。