Java虚拟机系列六:类加载器

405 阅读7分钟

[toc]

一.类的生命周期

整个类的生命周期分为加载和使用两个阶段。

简单来描述是这样的:一个物理的class文件通过二进制流被加载到内存中,jvm会检查这个class文件是否合法合规,合规的class文件经过解析转化为Klass结构体存放于方法区,同时在Java侧生成一个Klass的镜像类对象Class。

当我们通过new 或者反射实例化一个对象时,这个对象的对象头中会存放Klass的指针指向堆中的Klass结构体。这样通过Klass结构体,我们就可以知道这个class中定义的方法和属性。

1.1.类的加载

程序员通过ide编码完的java/kotlin/groovy代码,经过编译生成.class文件,它是一个结构化的二进制数据流。

通过classLoader我们可以将class二进制数据流加载到内存中,并根据class的dsl规范解析各个字段内容,生成c++侧的klass结构体,并将Klass存放到堆区的方法区(元数据区)中,同时生成一个java侧的Class对象放到堆区。

这里比较绕口,简单的理解可以这样类比:

Persion.class -----> Persion对象

Klass --------> Class对象

当我们new一个对象时,这个对象的对象头中包含三部分内容mark word , klass指针,数组长度,其中klass指针中存放的是指向的就是方法区中的klass对象的地址。我们常说的oop-klass模型,核心就是将klass的内容与对象本身分开,通过指针来关联起来,避免每个同类型的对象都需要存储一份klass的信息。

这一步完成后,class文件由物理磁盘上的字节码转化为堆中的klass对象存放在方法区。

1.2.类的链接

当类被加载转化为klass后,需要进入链接阶段才能被正常使用。链接过程包含三个阶段:

  • 校验阶段
  • 准备阶段
  • 解析阶段

校验阶段

格式检查 - 魔数(cafebaby)检查,版本检查,长度校验 语义检查 - 抽象方法是否有实现,是否有父类等 字节码校验 - 操作数地址是否正确,跳转指令地址是否正确 符号引用检查 - 符号引用是否存在(#xxx 对应的常量是否存在)

准备阶段

为类的静态变量分配内存,并初始化默认值

解析阶段

将接口,类,方法,属性的符号引用改为直接引用

符号引用:一般表示为#xxx,这个字符串会从常量池中查找实际的内容。

直接引用:内存地址或者偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量

1.3.类的初始化

执行内置的cinit方法,给变量赋予初始值,一些用static包裹的方法都放在cinit中。

1.4.类的使用

根据是否执行cinit方法,类的使用分为主动使用和被动使用。主动使用会执行类的cinit方法,而被动使用不会。

主动使用的场景

  • new XXX()
  • 使用类的静态字段和方法
  • class.forName("xxx")
  • xxx.newInstantce()

被动使用场景

  • 通过数据定义的类
  • ClassLoader.loadClass("xxx")
  • 使用类中的常量,常量是在编译阶段处理的,因此不会触发cinit
  • 通过数组定义来引用类不会触发初始化

二.ClassLoader的种类

2.1.Bootstrap ClassLoader

加载Java_Home/lib下的jar包

2.2.Extension ClassLoader

加载Java_Home/lib/ext/下的jar包

2.3.Application ClassLoader

负责加载java -classPath指定的路径下的jar包

2.4.自定义ClassLoader

可以指定自己某个目录下的jar包

三.android的ClassLoader

3.1.BootClassLoader

BootClassLoader是ClassLoader的一个内部类,无法直接使用

路径:java/lang/ClassLoader



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;
    }

    public BootClassLoader() {
        super(null);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }
    
    @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            clazz = findClass(className);
        }

        return clazz;
    }
    
}

重点分析:

  • BootClassLoader 是ClassLoader的内部类,无法直接使用,它继承自ClassLoader,
  • 它的loadClass是所有加载类的核心方法,先查找已经加载过的class,没有的情况下会通过Class.classForName查找,参考3.2节

3.2 Class.classForName

路径:java/lang/Class.java

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

这是一个native方法,根据jni的规则,它调用java_lang_Class下的Class_classForName方法

3.3 java_lang_Class.Class_classForName

路径:art/runtime/Native.cc

static jclass Class_classForName(JNIEnv* env, jclass, jstring javaName, jboolean initialize,
                                 jobject javaLoader) {
  ScopedFastNativeObjectAccess soa(env);
  ScopedUtfChars name(env, javaName);
  if (name.c_str() == nullptr) {
    return nullptr;
  }

  // We need to validate and convert the name (from x.y.z to x/y/z).  This
  // is especially handy for array types, since we want to avoid
  // auto-generating bogus array classes.
  if (!IsValidBinaryClassName(name.c_str())) {
    soa.Self()->ThrowNewExceptionF("Ljava/lang/ClassNotFoundException;",
                                   "Invalid name: %s", name.c_str());
    return nullptr;
  }

  std::string descriptor(DotToDescriptor(name.c_str()));
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::ClassLoader> class_loader(
      hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader)));
  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
  Handle<mirror::Class> c(
      hs.NewHandle(class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader)));
  if (c == nullptr) {
    ScopedLocalRef<jthrowable> cause(env, env->ExceptionOccurred());
    env->ExceptionClear();
    jthrowable cnfe = reinterpret_cast<jthrowable>(
        env->NewObject(WellKnownClasses::java_lang_ClassNotFoundException,
                       WellKnownClasses::java_lang_ClassNotFoundException_init,
                       javaName,
                       cause.get()));
    if (cnfe != nullptr) {
      // Make sure allocation didn't fail with an OOME.
      env->Throw(cnfe);
    }
    return nullptr;
  }
  if (initialize) {
    class_linker->EnsureInitialized(soa.Self(), c, true, true);
  }
  return soa.AddLocalReference<jclass>(c.Get());
}

通过ClassLinker->FindClass查找class信息

3.4.ClassLinker FindClass

路径:art/runtime/class_linker.cc

这个方法非常长,我仅保留其中比较重要的部分

ObjPtr<mirror::Class> ClassLinker::FindClass(Thread* self,
                                             const char* descriptor,
                                             Handle<mirror::ClassLoader> class_loader) {
  ....
  
  const size_t hash = ComputeModifiedUtf8Hash(descriptor);
  // Find the class in the loaded classes table.
  ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());
  if (klass != nullptr) {
    return EnsureResolved(self, descriptor, klass);
  }
  // Class is not yet loaded.
  if (descriptor[0] != '[' && class_loader == nullptr) {
    // Non-array class and the boot class loader, search the boot class path.
    ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
    if (pair.second != nullptr) {
      return DefineClass(self,
                         descriptor,
                         hash,
                         ScopedNullHandle<mirror::ClassLoader>(),
                         *pair.first,
                         *pair.second);
    } else {
      // The boot class loader is searched ahead of the application class loader, failures are
      // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to
      // trigger the chaining with a proper stack trace.
      ObjPtr<mirror::Throwable> pre_allocated =
          Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
      self->SetException(pre_allocated);
      return nullptr;
    }
  }
  
  .....
}

这个类非常长,重点分析如下

ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());

if (klass != nullptr) {
    return EnsureResolved(self, descriptor, klass);
 }

从当前的classLoader中根据descriptor从map中查找是否已经加载了class,找到方法区中的klass,生成class对象返回

ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
if (pair.second != nullptr) {
  return DefineClass(self,
                     descriptor,
                     hash,
                     ScopedNullHandle<mirror::ClassLoader>(),
                     *pair.first,
                     *pair.second);
}

如果没有加载,会先从dex中加载字节码,然后通过DefineClass推到方法区生成klass并返回,这个里面的内容有兴趣的同学可以自行查询。

3.5 ClassLoader

public abstract class ClassLoader {

    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }

    private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }
    
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    
    protected 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);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
    
    .....
}

class BootClassLoader extends ClassLoader{....}

ClassLoader无参构造方法中,默认的parent classLoader是一个PathClassLoader,而这个PathClassLoader默认传入的parent就是BootClassLoader。

因此在ClassLoader中调用loadClass时,如果已经加载,则直接返回,如果没有加载,则进入PathClassLoader.loadClass(),由于PathClassLoader没有重写loadClass,进而进入到BootClassLoader的loadClass的过程,最终从classLinker中得到需要的klass。

3.6 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);
    }

    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public PathClassLoader(
            @NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,
            @Nullable ClassLoader[] sharedLibraryLoaders) {
        super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
    }
}

PathClassLoader 只是简单的重写了BaseDexClassLoader的构造方法,并且将parent(BootClassLoader)传递到了BaseDexClassLoader

3.7.BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, librarySearchPath, parent, null, false);
    }
    
    public BaseDexClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
            boolean isTrusted) {
        super(parent);
        // Setup shared libraries before creating the path list. ART relies on the class loader
        // hierarchy being finalized before loading dex files.
        this.sharedLibraryLoaders = sharedLibraryLoaders == null
                ? null
                : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        // Run background verification after having set 'pathList'.
        this.pathList.maybeRunBackgroundVerification(this);

        reportClassLoaderChain();
    }
    
}

重点说明:

  • dexPath:包含目标类或资源的apk/jar列表
  • optimizedDirectory:优化后dex文件存在的目录 ,api 26之后废弃了
  • librarySearchPath:native库路径列表;采用:分割
  • ClassLoader:父类的类加载器.
  • classLoader在加载dex时,会把加载的jar/apk/dex转化为dexFile,通过makeDexElements转化为Element数组,当我们通过findClass查找class时,会有序的从element数组中获取class返回,因此总是排在element数组前面的dex中的class会被优先返回,我们也可以通过自己插入修改过的dex来实现热修复

3.8.DexClassLoader

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

DexClassLoader也是简单的重写了BaseDexClassLoader的构造方法,它与PathClassLoader的核心区别在于提供了optimizedDirectory作为外部参数传入。

  • 在api26之前,DexClassLoader通过optimizedDirectory指定输出的odex的位置,PathClassLoader使用默认的odex目录/data/dalvik-cache/xxx@classes.dex
  • api26之后废弃了optimizedDirectory,因此在api26之后,PathClassLoader与DexClassLoader作用完全一致。