dlopen failed与so的命名空间

之前的Android热更新实践里面使用替换默认ClassLoader的方式实现了热修复,但偶然发现这种方式在加载某些系统so库的时候出现了问题。

背景是我们的某个功能依赖了我司开发的XXX.so这个系统so,这个XXX.so依赖了libandroid_runtime.so,而libandroid_runtime.so又依赖了libandroidicu.so:

image.png

然后在应用层调用System.loadLibrary去加载XXX.so的时候报了下面的异常:

03-26 02:27:07.385  3671  3695 E Demo: err : java.lang.UnsatisfiedLinkError: dlopen failed: library "libandroidicu.so" not found: needed by /system/lib64/libandroid_runtime.so in namespace classloader-namespace
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.Runtime.loadLibrary0(Runtime.java:1077)
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.Runtime.loadLibrary0(Runtime.java:998)
03-26 02:27:07.385  3671  3695 E Demo:       at java.lang.System.loadLibrary(System.java:1661)

so加载

从报错来看是libandroidicu.so在classloader-namespace这个命名空间里面不可访问。原生库的命名空间是安卓7.0引入的东西,目的在于限制native层私有api的访问。

从文档上并不能直接定位到我们的问题,但是从触发异常的条件"默认的ClassLoader去加载这个so是没有问题的,用我们热修复的ClassLoader去加载就会出现异常"来看,

问题原因应该就是出在我们热修复的ClassLoader的对应的命名空间没有权限去加载libandroidicu.so了。

我们先用find命令看看libandroidicu.so在系统的什么目录:

console:/ # find . -name libandroidicu.so 2> /dev/null
./apex/com.android.i18n/lib/libandroidicu.so
./apex/com.android.i18n/lib64/libandroidicu.so
./system/apex/com.android.i18n/lib/libandroidicu.so
./system/apex/com.android.i18n/lib64/libandroidicu.so

的确不在/system/lib64下面,应用加载不到它是合理的。但我好奇的是默认的ClassLoader又是怎么加载到的?

从错误堆栈的Runtime.loadLibrary0一路往jni追踪可以发现so实际并不是由ClassLoader去加载的,而是通过ClassLoader找到了对应的NativeLoaderNamespace,然后用NativeLoaderNamespace::Load去加载的:

image.png

具体的代码调用如下:


// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:libcore/ojluni/src/main/java/java/lang/Runtime.java
public class Runtime {
    ...
    public void loadLibrary(String libname) {
        loadLibrary0(Reflection.getCallerClass(), libname);
    }
    
    void loadLibrary0(Class<?> fromClass, String libname) {
        ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
        loadLibrary0(classLoader, fromClass, libname);
    }
    
    private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
        ...
        nativeLoad(filename, loader);
        ...
    }

    private static String nativeLoad(String filename, ClassLoader loader) {
        return nativeLoad(filename, loader, null);
    }

    private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);
    ...
}
// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:libcore/ojluni/src/main/native/Runtime.c
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

static JNINativeMethod gMethods[] = {
  FAST_NATIVE_METHOD(Runtime, freeMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, totalMemory, "()J"),
  FAST_NATIVE_METHOD(Runtime, maxMemory, "()J"),
  NATIVE_METHOD(Runtime, nativeGc, "()V"),
  NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
  NATIVE_METHOD(Runtime, nativeLoad,
                "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Class;)"
                    "Ljava/lang/String;"),
};

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/openjdkjvm/OpenjdkJvm.cc
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
    ...
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &error_msg);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/runtime/jni/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg) {
    ...
    void* handle = android::OpenNativeLibrary(
      env,
      runtime_->GetTargetSdkVersion(),
      path_str,
      class_loader,
      (caller_location.empty() ? nullptr : caller_location.c_str()),
      library_path.get(),
      &needs_native_bridge,
      &nativeloader_error_msg);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader.cpp
LibraryNamespaces* g_namespaces = new LibraryNamespaces;

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
                        jobject class_loader, const char* caller_location, jstring library_path,
                        bool* needs_native_bridge, char** error_msg) {
  ...
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace* ns;

  if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    Result<NativeLoaderNamespace*> isolated_ns =
        CreateClassLoaderNamespaceLocked(env,
                                         target_sdk_version,
                                         class_loader,
                                         /*is_shared=*/false,
                                         /*dex_path=*/nullptr,
                                         library_path,
                                         /*permitted_path=*/nullptr,
                                         /*uses_library_list=*/nullptr);
    if (!isolated_ns.ok()) {
      *error_msg = strdup(isolated_ns.error().message().c_str());
      return nullptr;
    } else {
      ns = *isolated_ns;
    }
  }

  return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
  ...
}

void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
                                   bool* needs_native_bridge, char** error_msg) {
  auto handle = ns->Load(path);
  ...
  return handle.ok() ? *handle : nullptr;
}

ld.config.txt可以看到不同的namespace有不同的search paths:

additional.namespaces = com_android_adbd,com_android_art,com_android_conscrypt,com_android_cronet,com_android_i18n,com_android_media,com_android_neuralnetworks,com_android_os_statsd,com_android_resolv,com_android_runtime,com_product_service1,product,rs,sphal,vndk,vndk_product
...
namespace.default.search.paths = /system/${LIB}
namespace.default.search.paths += /system_ext/${LIB}
...
namespace.com_android_i18n.search.paths = /apex/com.android.i18n/${LIB}

public libs

g_namespaces负责NativeLoaderNamespace的创建和缓存,NativeLoaderNamespace也是通过g_namespaces去Create出来的:

Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,
                                                                int32_t target_sdk_version,
                                                                jobject class_loader,
                                                                bool is_shared,
                                                                jstring dex_path,
                                                                jstring library_path,
                                                                jstring permitted_path,
                                                                jstring uses_library_list)
    REQUIRES(g_namespaces_mutex) {
  Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,
                                                           target_sdk_version,
                                                           class_loader,
                                                           is_shared,
                                                           dex_path,
                                                           library_path,
                                                           permitted_path,
                                                           uses_library_list);
  ...
  return ns;
}

我们看看g_namespaces的Create方法创建NativeLoaderNamespace干了些啥:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/library_namespaces.cpp
Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env, uint32_t target_sdk_version,
                                                         jobject class_loader, bool is_shared,
                                                         jstring dex_path_j,
                                                         jstring java_library_path,
                                                         jstring java_permitted_path,
                                                         jstring uses_library_list) {
  ...
  auto app_ns = NativeLoaderNamespace::Create(
      namespace_name, library_path, permitted_path, parent_ns, is_shared,
      target_sdk_version < 24 /* is_exempt_list_enabled */, also_used_as_anonymous);
  ...
  for (const auto&[apex_ns_name, public_libs] : apex_public_libraries()) {
    auto ns = NativeLoaderNamespace::GetExportedNamespace(apex_ns_name, is_bridged);
    // Even if APEX namespace is visible, it may not be available to bridged.
    if (ns.ok()) {
      linked = app_ns->Link(&ns.value(), public_libs);
      if (!linked.ok()) {
        return linked.error();
      }
    }
  }
  ...
  // 缓存并返回app_ns
}

可以看到它在创建出app_ns之后会遍历apex_public_libraries去链接apex里面的公共库:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/public_libraries.cpp
constexpr const char* kApexLibrariesConfigFile = "/linkerconfig/apex.libraries.config.txt";

const std::map<std::string, std::string>& apex_public_libraries() {
  static std::map<std::string, std::string> public_libraries = InitApexLibraries("public");
  return public_libraries;
}

static std::map<std::string, std::string> InitApexLibraries(const std::string& tag) {
  std::string file_content;
  if (!base::ReadFileToString(kApexLibrariesConfigFile, &file_content)) {
    // config is optional
    return {};
  }
  auto config = ParseApexLibrariesConfig(file_content, tag);
  if (!config.ok()) {
    LOG_ALWAYS_FATAL("%s: %s", kApexLibrariesConfigFile, config.error().message().c_str());
    return {};
  }
  return *config;
}

在实机的/linkerconfig/apex.libraries.config.txt下可以看到:

jni com_android_appsearch libicing.so
public com_android_art libnativehelper.so
jni com_android_btservices libbluetooth_jni.so
jni com_android_conscrypt libjavacrypto.so
public com_android_i18n libicui18n.so:libicuuc.so:libicu.so
public com_android_neuralnetworks libneuralnetworks.so
jni com_android_os_statsd libstats_jni.so
jni com_android_tethering libframework-connectivity-jni.so:libframework-connectivity-tiramisu-jni.so:libandroid_net_connectivity_com_android_net_module_util_jni.so:libservice-connectivity.so
jni com_android_uwb libuwb_uci_jni_rust.so

com_android_i18n的libicui18n.solibicuuc.solibicu.so是公共的可以直接访问会被链接到app_ns允许访问。而报错的libandroidicu.so的确没有在public里面,所以不会被链接,于是无法访问。

shared libs

实际上除了这个public libs配置之外,还有个shared libs的配置可以用于配置NativeLoaderNamespace对哪些NativeLoaderNamespace暴露哪些so。

详细的规则可以参考链接器命名空间的文档

具体的原理我们可以继续往下追一层看看NativeLoaderNamespace::Create是怎么创建app_ns的:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader_namespace.cpp
Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
    const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
    const NativeLoaderNamespace* parent, bool is_shared, bool is_exempt_list_enabled,
    bool also_used_as_anonymous) {
  ...
  // All namespaces for apps are isolated
  uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;

  ...
  if (is_shared) {
    type |= ANDROID_NAMESPACE_TYPE_SHARED;
  }
  ...
  android_namespace_t* raw =
    android_create_namespace(name.c_str(), nullptr, search_paths.c_str(), type,
                              permitted_paths.c_str(), effective_parent.ToRawAndroidNamespace());
  if (raw != nullptr) {
    return NativeLoaderNamespace(name, raw);
  }
  ...
}

可以看到is_shared为true的时候会给type添加一个ANDROID_NAMESPACE_TYPE_SHARED的flag,这个flag最终在create_namespace会判断:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:bionic/linker/linker.cpp
static android_namespace_t* g_anonymous_namespace = &g_default_namespace;
...
android_namespace_t* create_namespace(const void* caller_addr,
                                      const char* name,
                                      const char* ld_library_path,
                                      const char* default_library_path,
                                      uint64_t type,
                                      const char* permitted_when_isolated_path,
                                      android_namespace_t* parent_namespace) {
  if (parent_namespace == nullptr) {
    // if parent_namespace is nullptr -> set it to the caller namespace
    soinfo* caller_soinfo = find_containing_library(caller_addr);

    parent_namespace = caller_soinfo != nullptr ?
                       caller_soinfo->get_primary_namespace() :
                       g_anonymous_namespace;
  }

  ProtectedDataGuard guard;
  std::vector<std::string> ld_library_paths;
  std::vector<std::string> default_library_paths;
  std::vector<std::string> permitted_paths;
  ...
  android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
  ...
  if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) {
    // append parent namespace paths.
    std::copy(parent_namespace->get_ld_library_paths().begin(),
              parent_namespace->get_ld_library_paths().end(),
              back_inserter(ld_library_paths));

    std::copy(parent_namespace->get_default_library_paths().begin(),
              parent_namespace->get_default_library_paths().end(),
              back_inserter(default_library_paths));

    std::copy(parent_namespace->get_permitted_paths().begin(),
              parent_namespace->get_permitted_paths().end(),
              back_inserter(permitted_paths));

    // If shared - clone the parent namespace
    add_soinfos_to_namespace(parent_namespace->soinfo_list(), ns);
    // and copy parent namespace links
    for (auto& link : parent_namespace->linked_namespaces()) {
      ns->add_linked_namespace(link.linked_namespace(), link.shared_lib_sonames(),
                               link.allow_all_shared_libs());
    }
  } else {
    // If not shared - copy only the shared group
    add_soinfos_to_namespace(parent_namespace->get_shared_group(), ns);
  }
  
  ns->set_ld_library_paths(std::move(ld_library_paths));
  ns->set_default_library_paths(std::move(default_library_paths));
  ns->set_permitted_paths(std::move(permitted_paths));
  ...
}

可以看到添加了ANDROID_NAMESPACE_TYPE_SHARED这个flag的话会添加父namespace的所有so和链接父namespace连接过的namespace。而如果为false则只会添加父namespace的shared so。

系统默认创建的Classloader对应的namespace的parent_namespace为null,于是会把parent_namespace设置成默认的"default"这个namespace。

而从/linkerconfig/ld.config.txt里面可以看到"default"这个namespace是链接了com_android_i18n这个namespace,可以从里面访问libandroidicu.so:

...
namespace.default.links = com_android_adbd,com_android_i18n,default,com_android_art,com_android_resolv,com_android_tethering,com_android_neural
namespace.default.link.com_android_adbd.shared_libs = libadb_pairing_auth.so
namespace.default.link.com_android_adbd.shared_libs += libadb_pairing_connection.so
namespace.default.link.com_android_adbd.shared_libs += libadb_pairing_server.so
namespace.default.link.com_android_i18n.shared_libs = libandroidicu.so
namespace.default.link.com_android_i18n.shared_libs += libicu.so
namespace.default.link.com_android_i18n.shared_libs += libicui18n.so
namespace.default.link.com_android_i18n.shared_libs += libicuuc.so
...

所以默认的ClassLoader创建NativeLoaderNamespace的时候is_shared是true可以加载到libandroidicu.so

而我们自定义的ClassLoader创建NativeLoaderNamespace的时候is_shared是false没有继承"default"这个parent_namepsace的links配置无法加载libandroidicu.so

另外也可以看到默认的Classloader对应的namespace会连接com_android_i18n这个命名空间两次

第一次在create\_namespace函数里由于ANDROID_NAMESPACE_TYPE_SHARED继承了defalut这个parent_namespace的links配置能访问/linkerconfig/ld.config.txt里的namespace.default.link.com_android_i18n.shared_libs配置的shared so,

第二次则是在LibraryNamespaces::Create里面赌钱/linkerconfig/apex.libraries.config.txtpublic com_android_i18n配置链接com_android_i18n的public so。

链接器命名空间这个文档里也有提到:

此属性与 public.libraries.txt 文件在底层实现上是相同的。这两种机制都通过使用库名称过滤器指定链接的方式来控制导入的共享库。

is shared

从前面CreateClassLoaderNamespaceLocked的传参来看我们的自定义Classloader创建namespace的时候is_shared的确为false:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:art/libnativeloader/native_loader.cpp
LibraryNamespaces* g_namespaces = new LibraryNamespaces;

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
                        jobject class_loader, const char* caller_location, jstring library_path,
                        bool* needs_native_bridge, char** error_msg) {
  ...
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace* ns;

  if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    Result<NativeLoaderNamespace*> isolated_ns =
        CreateClassLoaderNamespaceLocked(env,
                                         target_sdk_version,
                                         class_loader,
                                         /*is_shared=*/false,
                                         /*dex_path=*/nullptr,
                                         library_path,
                                         /*permitted_path=*/nullptr,
                                         /*uses_library_list=*/nullptr);
    if (!isolated_ns.ok()) {
      *error_msg = strdup(isolated_ns.error().message().c_str());
      return nullptr;
    } else {
      ns = *isolated_ns;
    }
  }

  return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
  ...
}

那默认的ClassLoader的is_shared是怎么设置成true的呢?从LoadedApk代码里面可以看到系统app最终就会调用ClassLoaderFactory.createClassLoader在里面创建Classloader的同时调用createClassloaderNamespace创建namespace,传入的is_shared为isBundledApp即true:

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/android/app/LoadedApk.java
public ClassLoader getClassLoader() {
    synchronized (mLock) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        }
        return mClassLoader;
    }
}

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
    ...
    boolean isBundledApp = mApplicationInfo.isSystemApp()
            && !mApplicationInfo.isUpdatedSystemApp();

    ...
    mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
        zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
        libraryPermittedPath, mBaseClassLoader,
        mApplicationInfo.classLoaderName, sharedLibraries.first, nativeSharedLibraries,
        sharedLibraries.second);

    mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/android/app/ApplicationLoaders.java
ClassLoader getClassLoaderWithSharedLibraries(
        String zip, int targetSdkVersion, boolean isBundled,
        String librarySearchPath, String libraryPermittedPath,
        ClassLoader parent, String classLoaderName,
        List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
        List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    // For normal usage the cache key used is the same as the zip path.
    return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                          libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                          nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
}


ClassLoader getClassLoaderWithSharedLibraries(
        String zip, int targetSdkVersion, boolean isBundled,
        String librarySearchPath, String libraryPermittedPath,
        ClassLoader parent, String classLoaderName,
        List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
        List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    // For normal usage the cache key used is the same as the zip path.
    return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                          libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                          nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
}

rivate ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                   String librarySearchPath, String libraryPermittedPath,
                                   ClassLoader parent, String cacheKey,
                                   String classLoaderName, List<ClassLoader> sharedLibraries,
                                   List<String> nativeSharedLibraries,
                                   List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    ...
    ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                        zip,  librarySearchPath, libraryPermittedPath, parent,
                        targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
                        nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
    ...
    return loader;
    ...
}

// https://cs.android.com/android/platform/superproject/+/android-13.0.0_r39:frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
    String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
    int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
    List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
    List<ClassLoader> sharedLibrariesAfter) {

final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
        classLoaderName, sharedLibraries, sharedLibrariesAfter);
  ...
  String errorMessage = createClassloaderNamespace(classLoader,
                                                   targetSdkVersion,
                                                   librarySearchPath,
                                                   libraryPermittedPath,
                                                   isNamespaceShared,
                                                   dexPath,
                                                   sonameList);
  ...
  return classLoader;
}

解决方案

方法1

由于ClassLoaderFactory.createClassloaderNamespace是private的不能在外部调用,所以解决自定义classloader找不到libandroidicu.so的方法就是不要自己直接new ClassLoader,而是调用ClassLoaderFactory.createClassLoader去创建,传入isNamespaceShared为true。

方法2

由于系统默认的classloader对应的namespace已经加载了libandroid_runtime.so,如果将我们自定义的classloader的父classloader设置成系统默认的classloder,则自定义classloader对应的namespace的parent_namespace也会指向默认classloader的namespace。

然后这个namespace已经加载了libandroid_runtime.so,于是在后面的add_soinfos_to_namespace(parent_namespace->get_shared_group(), ns);里面就能直接使用已经加载好的shared so libandroid_runtime.so:

image.png

使用setprop debug.ld.all dlopen,dlerror命令打开全部linker打印也可以看到libandroid_runtime.so Already loaded的日志:

03-29 04:36:10.024 6046 6069 D linker : find_library_internal(ns=classloader-namespace, task=libandroid_runtime.so): Already loaded (by sona me): /system/lib64/libandroid_runtime.so`

但是当父classloader设置成系统默认的classloader之后由于双亲委托的机制,会先从父classloader去加载class,达不到热修复的需求。

于是我们需要修改自定义classloader的loadClass打破双亲委托机制,先自己去加载class,加载不到再让父classloder去加载:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    //先从自己查找,找不到再从父classloader查找,实现热修复
    Class<?> c = null;
    try {
        c = findClass(name);
    } catch (ClassNotFoundException e) {
        // ignore
    }
    if (c == null) {
        c = getParent().loadClass(name);
    }
    return c;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容