❶ 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()才會成為一般程序的入口方法。類的構造器也會引發該動作。