❶ java类加载机制之Classloader以及打破加载机制的方式
在jDK1.8中,Classloader加载class的字节码到JVM,它是遵循双亲委派模型的加载机制,主要是由BootstrapClassLoader,ExtClassLoader、AppClassloader,然后是自定义的Classloader加载,这样做主要是保证JDK的内部类加载安全性,所以优先加载JDK的ClassPath的jar包的类.双亲委派模型的如下图所示,其实就是由两级父Classloader加载,分别是BootstrapClassloader和ExtClassloader。
JDK的双亲委派模型以下是Classloader的loadClass的和核心代码:
首先获取class的name的锁,是为了防止多线程并发的加载一个类,只有一个线程去加载,
findLoadedClass获取JVM已经加载的缓存中该Classloader实例是否已经加载该类, JVM底层实际是SystemDictionary,其底层是HASH表实现。
如果JVM的缓存没有加载过这个className,则先交给parent加载器加载,如果parent为空,则由BootstrapClassloader去加载,由于BootstrapClassloader是由C++实现,java没有这个类,所以通过JNI调用JVM的函数去加载。
果还没有加载到,则子类实现的findClass方法去加载。
最后判断resolve参数,是否处理class的链接。
最后返回class,没有加载到,由子类抛出ClassNotFoundException异常.
protectedClass<?>loadClass(Stringname,booleanresolve)throwsClassNotFoundException{synchronized(getClassLoadingLock(name)){//First,<?>c=findLoadedClass(name);if(c==null){longt0=System.nanoTime();try{if(parent!=null){c=parent.loadClass(name,false);}else{c=findBootstrapClassOrNull(name);}}catch(ClassNotFoundExceptione){////fromthenon-nullparentclassloader}if(c==null){//Ifstillnotfound,theninvokefindClassinorder//tofindtheclass.longt1=System.nanoTime();c=findClass(name);//thisisthedefiningclassloader;recordthestatssun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if(resolve){resolveClass(c);}returnc;}}JDK 打破双亲委派模型的两种方式继承ClassLoader,并重写loadClass从JVM层的缓存查询当前Classloader是否加载过。
如果没有直接从本地资源查询是否有有对应的资源,如果有,则直接调用define进行加载到JVM,返回Class.
没有本地资源没有,则从parent的Classloader加载相应的资源.
最后返回Class.
@OverridepublicClass<?>loadClass(Stringname)throwsClassNotFoundException{System.out.println("loadClass:"+name);synchronized(getClassLoadingLock(name)){//First,<?>c=findLoadedClass(name);if(c!=null){returnc;}byte[]bt=classNameToContent.get(name);if(bt!=null){try{returndefineClass(name,bt,0,bt.length);}catch(Exceptione){e.printStackTrace();}}try{if(getParent()!=null){c=getParent().loadClass(name);}}catch(ClassNotFoundExceptione){////fromthenon-nullparentclassloader}if(c!=null){returnc;}}returnnull;}使用Thread的contextClassLoaderJDK中利用线程的contextClassloader去打破双亲委派模型的例子就是Serviceloader, Serviceloader是JDK里面全称是Service Provider Interface,是实现服务提供接口,实现插件的方式,例如JDBC的Driver就是使用JDK的SPI,具体实现是适配不同的数据库,由于Driver是在rt.jar是由BootstrapClassloader加载,而接口实现类是由第三方的jar包提供,所有BootstrapClassLoader就没有办法就在,所以JDK做了一个妥协, 委派给当前线程的contextloader去加载实现类 下面是ServiceLoader的load方法,可以看出是获取当亲线程的contextClassloader去加载的接口的实现类。当前主线程类加载器默认是AppClassloader加载器加载。这样就是违反了双亲委派模型.
publicstatic<S>ServiceLoader<S>load(Class<S>service){ClassLoadercl=Thread.currentThread().getContextClassLoader();returnServiceLoader.load(service,cl);}并行类加载机制前面介绍Classloader的抽象类中loadClass方法,加载开始时,需要获取类全名的锁, 如果用到了并行类加载机制,就会用到. 如果需要使用并行类加载机制,只有再自定义的类加载加一个静态代码块中,增加下面一行。
static{ClassLoader.registerAsParallelCapable();}ClassLoader#registerAsParallelCapable
将自定义继承ClassLoader的类注册到ParallelLoaders的Set集合中.
(){Class<?extendsClassLoader>callerClass=Reflection.getCallerClass().asSubclass(ClassLoader.class);returnParallelLoaders.register(callerClass);}ParallelLoaders类中其实就是维护一个Classloader实现类的Set,其中元素都是调用registerAsParallelCapable注册为并行类加载的classloader,
{privateParallelLoaders(){}//<Class<?extendsClassLoader>>loaderTypes=Collections.newSetFromMap(newWeakHashMap<Class<?extendsClassLoader>,Boolean>());static{synchronized(loaderTypes){loaderTypes.add(ClassLoader.class);}}/***.*Returns{@codetrue}issuccessfullyregistered;{@codefalse}if*loader'ssuperclassisnotregistered.*/staticbooleanregister(Class<?extendsClassLoader>c){synchronized(loaderTypes){if(loaderTypes.contains(c.getSuperclass())){////.//Note:,if//,//.loaderTypes.add(c);returntrue;}else{returnfalse;}}}/***Returns{@codetrue}ifthegivenclassloadertypeis*registeredasparallelcapable.*/staticbooleanisRegistered(Class<?extendsClassLoader>c){synchronized(loaderTypes){returnloaderTypes.contains(c);}}}Classloaer构造函数中,
如果Classloader注册过并行类加载器,则创建parallelLockMap的锁的HashMap,
privateClassLoader(Voinused,ClassLoaderparent){this.parent=parent;if(ParallelLoaders.isRegistered(this.getClass())){parallelLockMap=newConcurrentHashMap<>();package2certs=newConcurrentHashMap<>();domains=Collections.synchronizedSet(newHashSet<ProtectionDomain>());assertionLock=newObject();}else{//nofiner-grainedlock;=null;package2certs=newHashtable<>();domains=newHashSet<>();assertionLock=this;}}Classloaer#getClassLoadingLock
在loadClass中获取类锁中,会判断parallelLockMap不为空,会创建一个Object对象作为这个classloader类的锁,然后放入hashMap中. 这样进行类加载过程,就synchonized锁就不是锁Classloader实例的this指针,而是Hashmap中获取加载类全名的锁,这样不同类全名就可以并行加载,这样减少了锁的粒度,提升类加载的速度.
(StringclassName){Objectlock=this;if(parallelLockMap!=null){ObjectnewLock=newObject();lock=parallelLockMap.putIfAbsent(className,newLock);if(lock==null){lock=newLock;}}returnlock;}总结 本文主要就JDK的阐述了双亲委派模型以及JDk中打破双亲委派的两种方式,最后介绍了JDK中实现并行类加载的实现原理.
❷ 描述一下JVM加载class文件 的原理机制
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
类装载方式,有两种
1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
2.显式装载, 通过class.forname()等方法,显式加载需要的类
隐式加载与显式加载的区别:两者本质是一样?
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
Java的类加载器有三个,对应Java的三种类:(java中的类大致分为三种: 1.系统类 2.扩展类 3.由程序员自定义的类 )
Bootstrap Loader // 负责加载系统类 (指的是内置类,像是String,对应于C#中的System类和C/C++标准库中的类)
|
- - ExtClassLoader // 负责加载扩展类(就是继承类和实现类)
|
- - AppClassLoader // 负责加载应用类(程序员自定义的类)
三个加载器各自完成自己的工作,但它们是如何协调工作呢?哪一个类该由哪个类加载器完成呢?为了解决这个问题,Java采用了委托模型机制。
委托模型机制的工作原理很简单:当类加载器需要加载类的时候,先请示其Parent(即上一层加载器)在其搜索路径载入,如果找不到,才在自己的搜索路径搜索该类。这样的顺序其实就是加载器层次上自顶而下的搜索,因为加载器必须保证基础类的加载。之所以是这种机制,还有一个安全上的考虑:如果某人将一个恶意的基础类加载到jvm,委托模型机制会搜索其父类加载器,显然是不可能找到的,自然就不会将该类加载进来。
我们可以通过这样的代码来获取类加载器:
ClassLoader loader = ClassName.class.getClassLoader();
ClassLoader ParentLoader = loader.getParent();
注意一个很重要的问题,就是Java在逻辑上并不存在BootstrapKLoader的实体!因为它是用C++编写的,所以打印其内容将会得到null。
前面是对类加载器的简单介绍,它的原理机制非常简单,就是下面几个步骤:
1.装载:查找和导入class文件;
2.连接:
(1)检查:检查载入的class文件数据的正确性;
(2)准备:为类的静态变量分配存储空间;
(3)解析:将符号引用转换成直接引用(这一步是可选的)
3.初始化:初始化静态变量,静态代码块。
这样的过程在程序调用类的静态成员的时候开始执行,所以静态方法main()才会成为一般程序的入口方法。类的构造器也会引发该动作。