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进程时创建的。

猜你喜欢