ClassNotFoundException,经常在开发中出现,原因就是加载的过程中找不到这个类,在第一次主动使用的时候报出的异常。
在开发之前需要配置路径,我们一般做了JAVA_HOME,PATH,CLASSPATH,这三项配置。
java_home 对应了java的jdk路径,我的路径安排在D盘
C:\Users\Administrator>echo %JAVA_HOME%
D:\Java\jdk-10.0.1
C:\Users\Administrator>echo %PATH%
C:\Python27\;
C:\Python27\Scripts;
D:\Python\Scripts\;
D:\Python\;
D:\Java\jdk-10.0.1\bin;
D:\Program Files\nodejs\;
C:\ProgramData\chocolatey\bin;
D:\Java\jdk-10.0.1\jre\bin;
可以看到,PATH中包含了很多路径,其中就包括bin和jre\bin路径
C:\Users\Administrator>echo %CLASSPATH%
D:\Java\jdk-10.0.1\lib;
D:\Java\jdk-10.0.1\lib\tools.jar
可以看到,CLASSPATH中包含了很多路径,其中就包括lib路径和lib下的tools.jar
通过一个类的全限定名来获取描述此类的二进制字节流,简单说就是加载class文件,将class的二进制数据转化JVM运行时内存中的对象。
从JAVA虚拟机的角度看,只有两种ClassLoader,用C++实现的BootStrapClassLoader和用JAVA实现的其他ClassLoader。 从开发者角度看,有三种ClassLoader,分别是,BootStrapClassloader、ExtensionClassLoader、ApplicationClassLoader,他们分别加载的class路径不同。
不同的路径,对应不同的加载器,加载器如何有效的去加载class?通过双亲委托模式!
每个class都有个classloader,classloader也有父classloader,如果classloader.getparent()为null,并不是说他没有父加载器,而是说父加载器是BootStrapClassLoader,
appclassloader的父加载器是extclassloadder,extclassloadder的父加载器是BootStrapClassLoader。
在加载的时候其实和view的点击事件分发一样。先dispatch,然后handle。
1.在缓存中查看是否有,如果有直接从缓存获取,否则交给父加载器去加载
2.递归1
3.如果BootstrapClassloader在自己路径(sun.misc.boot.class)下找不到class,则告诉孩子加载器,你去加载吧,我在我这找不到。
4.如果ExtClassLoader在自己路径(java.ext.dirs)下找不到class,则告诉孩子加载器,你去加载吧,我在我这找不到
5.最终appclassloader去自己路径(java.class.path)下加载,如果appclassloader也找不到class则说明class损坏,或者不存在。
当子类(继承、实现、实例化)被加载的时候,加载器是父类的加载器。
1.隐式加载
没有代码,不调用classloader.loadclass(),由jvm自动加载
2.显式加载
调用classload.loadclass()或者Class.forName();
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;
}
1.通过findloadedclass()方法查找是否已经加载过
2.如果加载过了直接返回,否则查看parent是否为null
3.parent为null的话直接调用findbootstrapclassornull去判断,否则调用parent.loadclass去加载
4.如果parent.loadclass仍然没有找到则调用自定义的classloader去加载
上面的三个classloader都指定了加载的class的路径,如果要加载其他路径的class,我们就需要自定义classloader
自定义classloader的时候我们尽量只重写findclass方法,不要尝试去重写loadclass
1.自定义ClassLoader继承ClassLoader
2.重写findclass方法
3.在findclass方法中调用ondefine返回class对象
//1.创建测试对象
package zjl.com.classloadermodule;
public class Test {
public void TestMethod(){
System.out.println("TestMethod2222");
}
}
//2.自定义ClassLoader
package zjl.com.classloadermodule;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader {
public String libPath;
public MyClassLoader(String libPath) {
this.libPath = libPath;
}
@Override
protected Class> findClass(String s) throws ClassNotFoundException {
File file = new File(libPath,s);
try {
FileInputStream fileInputStream = new FileInputStream(file);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int len = 0;
while ((len = fileInputStream.read())!= -1){
byteArrayOutputStream.write(len);
}
byte[] data = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
fileInputStream.close();
return defineClass(s,data,0, data.length );
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(s);
}
}
//3.测试,如果正常在Terminal中应该输出TestMethod2222
TestMethod2222
Process finished with exit code 0
证明加载了Test.class,测试结果符合预期,这也可以作为热修复的基础例子之一。
public
class Thread implements Runnable {
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
/**
* Returns the context ClassLoader for this Thread. The context
* ClassLoader is provided by the creator of the thread for use
* by code running in this thread when loading classes and resources.
* If not {@linkplain #setContextClassLoader set}, the default is the
* ClassLoader context of the parent Thread. The context ClassLoader of the
* primordial thread is typically set to the class loader used to load the
* application.
*
* If a security manager is present, and the invoker's class loader is not
* {@code null} and is not the same as or an ancestor of the context class
* loader, then this method invokes the security manager's {@link
* SecurityManager#checkPermission(java.security.Permission) checkPermission}
* method with a {@link RuntimePermission RuntimePermission}{@code
* ("getClassLoader")} permission to verify that retrieval of the context
* class loader is permitted.
*
* @return the context ClassLoader for this Thread, or {@code null}
* indicating the system class loader (or, failing that, the
* bootstrap class loader)
*
* @throws SecurityException
* if the current thread cannot get the context ClassLoader
*
* @since 1.2
*/
@CallerSensitive
public ClassLoader getContextClassLoader() {
return contextClassLoader;
}
/**
* Sets the context ClassLoader for this Thread. The context
* ClassLoader can be set when a thread is created, and allows
* the creator of the thread to provide the appropriate class loader,
* through {@code getContextClassLoader}, to code running in the thread
* when loading classes and resources.
*
*
If a security manager is present, its {@link
* SecurityManager#checkPermission(java.security.Permission) checkPermission}
* method is invoked with a {@link RuntimePermission RuntimePermission}{@code
* ("setContextClassLoader")} permission to see if setting the context
* ClassLoader is permitted.
*
* @param cl
* the context ClassLoader for this Thread, or null indicating the
* system class loader (or, failing that, the bootstrap class loader)
*
* @throws SecurityException
* if the current thread cannot set the context ClassLoader
*
* @since 1.2
*/
public void setContextClassLoader(ClassLoader cl) {
contextClassLoader = cl;
}
线程上下文加载器,通过源码注释可以看出,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。
每个Thread都有一个ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader。
Android中类加载器和Java类加载器差不多,也是遵循双亲委派,只不过JVM加载的是class,Android加载的是dex文件。Android中常用的类加载器如下:
graph LR
A(ClassLoader)-->B(BootClassLoader)
A-->C(BaseDexClassLoader)
A-->D(SecureClassLoader)
C-->E(PathClassLoader)
C-->F(DexClassLoader)
C-->G(InMemoryDexClassLoader)
E-->H(DelegateLastClassLoader)
D-->I(URLClassLoader)
用来加载Android Framework层dex文件,他是ClassLoader的内部类,由java代码实现,是Android平台上所有ClassLoader的最终parent,这个内部类是包内可见,开发者不可用,在zygote启动的时候创建。
java中根加载器是C++写的,开发者看不到,Android的BootClassLoader是可见的
是Path、Dex、InmemoryDex加载器的父类,在构造的时候传入不同的参数,传入参数后,交给DexPathList处理,自身并没有处理。
public class BaseDexClassLoader extends ClassLoader {
/**
* Hook for customizing how dex files loads are reported.
*
* This enables the framework to monitor the use of dex files. The
* goal is to simplify the mechanism for optimizing foreign dex files and
* enable further optimizations of secondary dex files.
*
* The reporting happens only when new instances of BaseDexClassLoader
* are constructed and will be active only after this field is set with
* {@link BaseDexClassLoader#setReporter}.
*/
/* @NonNull */ private static volatile Reporter reporter = null;
private final DexPathList pathList;
/**
* Constructs an instance.
* Note that all the *.jar and *.apk files from {@code dexPath} might be
* first extracted in-memory before the code is loaded. This can be avoided
* by passing raw dex files (*.dex) in the {@code dexPath}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android.
* @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
* @param librarySearchPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
BaseDexClassLoader构造参数:
1.dexPath:dex相关文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’
2.optimizedDirectory:解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径:/data/data//…
3.librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null
4.parent:父加载器。
是DelegateLastClassLoader的父类,
类似Java的AppClassLoader,用于加载系统类和已经安装到系统中的apk里的dex文件(/data/app目录),在zygote启动system_server进程的时候创建。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("TAG", this.getClass().getClassLoader().toString());
Log.e("TAG1", this.getClass().getClassLoader().getParent().toString());
}
//E/TAG: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/oa.zjl.com.myapplication-1/base.apk"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]]
//E/TAG1: java.lang.BootClassLoader@3fed1170
//E/TAG2:null error
看到Classloader是PathClassLoader,PathClassLoader的parent是BootClassLoader,BootClassLoader的父加载器是null,(因为BootClassLoader是ClassLoader的内部类,无法访问),DexPathList是包含dex的jar或apk包路径,中间用“:”隔开。
类似Java中自定义加载器,用于加载指定目录中的dex文件,一般在做插件化或者热更新的时候使用这个加载器
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);
}
}
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
对比PathClassLoader和DexClassLoader可以看到构造方法的参数不同。
PathClassloader只能指定dexpath和父加载器,而DexClassloader可以多指定一个librarySearchPath,这就决定了DexClassLoader比PathClassLoader更灵活的加载指定路径下的apk,jar,dex文件,在自定义ClassLoader的使用上更方便了。
final class DexPathList {
// 声明dex文件后缀名
private static final String DEX_SUFFIX = ".dex";
//分隔符
private static final String zipSeparator = "!/";
/** 传入的父加载器 */
private final ClassLoader definingContext;
/**
* Element内部封装了 dex/resource/native library 路径
* 这个是用来存放dex文件路径的数组
*/
private Element[] dexElements;
/** 这个是用来存放动态库路径的数组 */
private final Element[] nativeLibraryPathElements;
/** app动态库文件列表. */
private final List nativeLibraryDirectories;
/** 系统动态库文件列表. */
private final List systemNativeLibraryDirectories;
/** 各种IO异常数组. */
private IOException[] dexElementsSuppressedExceptions;
public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {
//如果父加载器为null,那么抛出异常
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
//如果dex文件路径为null 抛出异常
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
//如果将dex文件优化后放置的文件目录不为空
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {//且这个目录不存在,那就抛出异常,
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
//如果这个要加载的目标目录不能读写,抛出异常
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// 通过makeDexElements方法将dex文件路径保存到数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);
//通过splitPaths方法保存应用动态库路径到list
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
//通过splitPaths方法保存系统动态库到list
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
//合并两个数组
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
//将所有的动态库路径保存到数组
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
suppressedExceptions,
definingContext);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
......
}
加载dex方法调用如下:
BaseDexClassLoader在加载dex文件的findclass方法如下,可以看到findclass方法交到了pathlist处理,如果返回null则爆出我们上面提到的classnotfoundexception
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
List suppressedExceptions = new ArrayList();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
在DexPathList中,调用了Element.findclass方法
public Class> findClass(String name, List suppressed) {
for (Element element : dexElements) {
Class> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
Element是DexPathList的内部类,在这里调用DexFile.loadClassBinarayName()
public Class> findClass(String name, ClassLoader definingContext,
List suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
在DexFile中调用loadClassBinaryName,最终调用了defineClass中的define、ClassNative方法
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
用来加载内存中的dex文件。
在构造函数中传入bytebuffer,然后交给BaseDexClassloader
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
/**
* Create an in-memory DEX class loader with the given dex buffers.
*
* @param dexBuffers array of buffers containing DEX files between
* buffer.position() and buffer.limit().
* @param parent the parent class loader for delegation.
*/
public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
super(dexBuffers, parent);
}
/**
* Creates a new in-memory DEX class loader.
*
* @param dexBuffer buffer containing DEX file contents between
* buffer.position() and buffer.limit().
* @param parent the parent class loader for delegation.
*/
public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}
}
,在BaseDex的构造函数中new了个DexPathlist,然后跟上面不同的是调用的是makeInmemoryElement
private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(buf);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}
在new DexFile的时候会调用openinmemorydexfile
DexFile(ByteBuffer buf) throws IOException {
mCookie = openInMemoryDexFile(buf);
mInternalCookie = mCookie;
mFileName = null;
}
是URLClassLoader的父类,扩展了ClassLoader的权限安全。
通过URL来加载jar文件或文件夹下的类或资源。