目录
吃透JVM篇(1)-JVM包含什么,如何运行的码
吃透JVM篇(2)-class字节码里都是啥
吃透JVM篇(3)-jvm的classLoader和android的classLoadder
看了这篇文章可能能知道什么?
1,类加载器都干了什么?
2,jvm中都包含哪些类加载器
3,android中包含哪些类加载器
为什么要看类加载器?
因为类加载器的内容涉及到热更新,插件化相关的基础,了解类加载器逻辑后,可以更加快速的了解热更新和插件化的原理
开始研究类加载器
类加载器什么时候加载类?类加载器都干了什么?
什么时候加载
当执行到以下五种情况的时候会加载类
1、使用new字节码指令创建类的实例,或者使用getstatic、putstatic读取或设置一个静态字段的值(放入常量池中的常量除外),或者调用一个静态方法的时候时。
2、通过java.lang.reflect包的方法对类进行反射调用的时候,如果类没有加载时。
3、当初始化一个类的时候,如果发现其父类没有加载,则首先触发父类初始化。
4、当虚拟机启动时,用户需要指定一个主类(包含main()方法的类)时。
5、使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化时。
都干了什么
类加载器主要就是将code码读到机器内存中执行
虽然上面一句话说的很简单,但是在jvm里处理了很多逻辑,大体分为3步
加载-链接-创建
加载
加载分为3步
打开冰箱门
通过限定名找类
把大象装进去
将字节码(class里面的各种code内容)转换到方法区
关闭冰箱门
生成一个lang.java.class标记当前加载类信息,提供方法区的入口
链接
链接也分为三部
验证
前面把code码已经放入方法区了,现在需要验证这个加载进来的类的结构是否正确,数量啊是否对得上等等
准备
上面正确了,开始分配内存了
引用
之前类里面各种#号变成真实的地址的引用
创建
创建主要是为了给当前类的变量赋值
以上就是类加载器的工作流程
JVM中都包含哪些类加载器?
4个类加载器
BootstrapClassLoader(根类加载器)
ExtensionClassLoader(扩展类加载器)
SystemClassLoader\APP ClassLoader(程序类加载器)
ClassLoader(自定义类加载器)
都是干啥的?
BootstrapClassLoader 这个主要是负责加载java核心代码的(system啥的)
ExtensionClassLoader 负责加载扩展包的类加载(javax啥的)
SystemClassLoader\APP ClassLoader 负责程序类的加载(自己写的代码)
ClassLoader咋加载的?
执行一个程序前会启动一个jvm实例,这个jvm实例启动时,首先在虚拟机启动的时候会先启动BootstrapClassLoader,这个类加载器会先加载各种各样的核心代码,然后将launcher这个关键类加载并实例,launcher实例以后会依次将 ExtensionClassLoader和SystemClassLoader\APP ClassLoader实例(注意这两个classLoader是单例的,也就证明一个jvm实例中只存在一个ExtensionClassLoader和SystemClassLoader\APP ClassLoader)
其中launcher代码如下
常说的双亲委托是什么?
看双亲委托之前,我们先看下ExtensionClassLoader和APP ClassLoader的代码
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
//单例模式
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
//创建时传入Launcher.ExtClassLoader.getExtDirs
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
//指定路径为java.ext.dirs
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
...省略部分代码
}
在URLClassLoader中关键代码如下
public class URLClassLoader extends SecureClassLoader implements Closeable {
/* The search path for classes and resources */
//这里注释说明了该变量是用的查找class的路径
private final URLClassPath ucp;
//构造函数第一个参数为路径,第二个参数用来指定父Loader,还有很多构造函数就不一一列举了
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = AccessController.getContext();
ucp = new URLClassPath(urls, acc);
}
URLClassLoader(URL[] urls, ClassLoader parent,
AccessControlContext acc) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
this.acc = acc;
ucp = new URLClassPath(urls, acc);
}
寻找class时通过
protected Class> findClass(final String name)
throws ClassNotFoundException
{
final Class> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
public Class> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
...省略其他代码
这里可以看出ExtensionClassLoader的class查找路径在java.ext.dirs目录也就是我们常说的扩展包目录,如果你把编译好的class放到这个目录也可以被ExtensionClassLoader加载,AppClassLoader也继承自URLClassLoader url指定到java.class.path,这个路径包含很多地方,程序运行的代码最后也会放到该路径。
URLClassLoader 都是继承自SecureClassLoader,SecureClassLoader继承自ClassLoader,ClassLoader中如果parent没有被当做参数传入,会直接执行getSystemClassLoader()获取Launcher里的loader,这个loader就是launcher初始化的时候创建的AppClassLoader
说了上面那么多,最后开始研究双亲委托,核心代码如下
位于ClassLoader类
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从上面的代码可以看到,当前类加载器如果要加载一个class的时候,首先判断parent是否为空,一般自定义classLoader的parent都是APP AppClassLoader ,所以不为空,然后执行parent.loadClass,相当于执行AppClassLoader.loadClass,在AppClassLoader的loadClass时也会执行判断parent是否为空,AppClassLoader的parent在Launcher初始化的时候代码已经说明是ExtClassLoader,所以又会执行ExtClassLoader.loadClass,ExtClassLoader执行loadClass时,parent为空,所以会执行findBootstrapClassOrNull。这个时候如果加载的类是核心类的话,findBootstrapClassOrNull会找到,如果不是核心类,返回null就会往下执行ExtClassLoader.findclass,我们上面代码已经确定了ExtClassLoader在扩展库的路径里去找,如果你要找的是扩展库里的类,此时就能拿到,如果不是扩展库里的类,就会AppClassLoader. parent.findClass返回null,往下执行自己的findClass,而大多程序的代码类都在这个时候找到,如果没有找到,就会回到自定义的findClass。
嗯。。。罗里吧嗦一大堆,可能各位没看懂,来个图
以上图片来源于该文章
简而言之就是,如果存在父加载器,先执行父加载器的loadClass(同时判断父加载器缓存是否存在),不停的这样套娃后,直到没有父加载器,就到boot里找,boot里没找到的话,就一层一层的findClass,直到找到或者抛到最外层为止。
双亲:大多指的是根(BootstrapClassLoader)和父类(parent,包含ExtClassLoader和APPClassLoader)
委托:就是一层一层的往上找。
好处:网上一大堆,主要是加载过得可以通过缓存拿,没加载过的优先父加载器加载,方便以后拿,而且通过url可以分割查找区域
坏处:不常用类也会优先父类加载,占用class缓存。
android中包含哪些类加载器?
android的类加载器大的方向也可以分为三个
1,BootClassLoader(zygote初始化加载preloaded-classes时通过单例初始化,主要用来加载preloaded-classes里的文件,系统常用类)
2,PathClassLoader(zygote fork后,初始化前通过PathClassLoaderFactory.createClassLoader创建的,主要用来加载/data/dalvik-cache-已安装的apk路径,热更新一般都是hook这个classLoader来达到修复bug的)
3,DexClassLoader(可以理解为具有扩展功能的PathClassLoader,主要是可以指定dex的存储路径,默认都是程序内部存储路径,一般插件化都用这个来做)
在android8.0以后,google又增加了一个内存类加载器InMemoryDexClassLoader,这个是一个更特殊的DexClassLoader,主要将本地文件转化为ByteBuffer,相比较DexClassLoader,使用更加开放
类的加载机制也和java一样,至于父子类关系,我给一张debug图,大家自己看
以上就是本篇文章内容