一、類加載
1.1、在java代碼中,類型的加載,連接,初始化過程都是在程序運行期間完成的。
圖示:
1.2、類型的加載——這里的類型是指的什么?
答:類型就是指的我們Java源代碼通過編譯后的class文件。
1.3、類型的來源有哪些?
(1)本地磁盤
(2)網絡下載,class文件
(3)war,jar下加載,class文件
(4)從專門的數據庫中讀取,class文件(少見)
(5)將java源文件動態編譯成class文件
1)典型的就是動態代理,通過運行期生成class文件
2)我們的jsp會被轉換成servlet,而我們的serlvet是一個java文件,會被編譯成class文件
1.4、通過什么來進行加載?(類加載器)
1.5、類加載的分類以及各種加載職責以及層級結構
(1)系統級別
1)啟動類加載器
2)擴展類加載器
3)系統類加載器(App類加載器)
(2)用戶級別的
自定義類加載器(繼承我們的ClassLoader)
(3)層級結構
二、類加載器加載我們的Class的時候遵循我們的雙親委派模型
在雙親委派機制中,各個加載器按照父子關系形成樹型結構,除了根加載器以外,每一個加載器有且只有一個父加載器
1、源碼分析:
1 protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 //檢查當前的class對象是否被加載過,被加載過就返回 6 Class<?> c = findLoadedClass(name); 7 if (c == null) { 8 long t0 = System.nanoTime(); 9 try { 10 //判斷當前的classLoader是否有父類 11 //若存在父類 12 if (parent != null) { 13 //調用父類的loadClass 14 c = parent.loadClass(name, false); 15 } else {//不存在父類,表示當前的classLoader是extClassLoader 16 //那么就會調用啟動類判斷是否加載過 17 c = findBootstrapClassOrNull(name); 18 } 19 } catch (ClassNotFoundException e) { 20 // ClassNotFoundException thrown if class not found 21 // from the non‐null parent class loader 22 } 23 //到目標位置,app ext boot都沒有去加載過 24 if (c == null) { 25 // If still not found, then invoke findClass in order 26 // to find the class. 27 long t1 = System.nanoTime(); 28 //委托我們的子類的classLoader去找 29 c = findClass(name); 30 31 // this is the defining class loader; record the stats 32 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 ‐ t0); 33 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 34 sun.misc.PerfCounter.getFindClasses().increment(); 35 } 36 } 37 if (resolve) { 38 resolveClass(c); 39 } 40 return c; 41 } 42 }
2、雙親委派模型加載的流程圖
3、類加載器的雙親委派模型的好處:
總所周知:java.lang.object類是所有類的父類,所以我們程序在運行期間會把java.lang.object類加載到內存中,假如java.lang.object類能夠被我們自定義類加載器去加載的話,那么jvm中就會存在多份Object的Class對象,而且這些Class對象是不兼容的。
所以雙親委派模型可以保證java核心類庫下的類型的安全。
借助雙親委派模型,我們java核心類庫的類必須是由我們的啟動類加載器加載的,這樣可以確保我們核心類庫只會在jvm中存在一份這就不會給自定義類加載器去加載我們核心類庫的類。
根據我們的演示案例,一個class可以由多個類加載器去加載,同事可以在jvm內存中存在多個不同版本的Class對象,這些對象是不兼容的。
4、如何手寫一個自定義類加載器(根據ClassLoader的doc文檔)
(1)我們自定義類加載器必須要繼承ClassLoader
(2)我們必須要findClass(String name)方法
1 /** 2 * Finds the class with the specified <a href="#name">binary name</a>. 3 * This method should be overridden by class loader implementations that 4 * follow the delegation model for loading classes, and will be invoked b y 5 * the {@link #loadClass <tt>loadClass</tt>} method after checking the 6 * parent class loader for the requested class. The default implementatio n 7 * throws a <tt>ClassNotFoundException</tt>. 8 * 9 * @param name 10 * The <a href="#name">binary name</a> of the class 11 * 12 * @return The resulting <tt>Class</tt> object 13 * 14 * @throws ClassNotFoundException 15 * If the class could not be found 16 * 17 * @since 1.2 18 */ 19 protected Class<?> findClass(String name) throws ClassNotFoundException { 20 throw new ClassNotFoundException(name); 21 }
(3)寫方法loadClassData(String name)去讀取我們的class數據(非必須)
1 /** 2 * 自定義的加載器 3 * Created by smlz on 2019/10/22. 4 */ 5 public class TulingClassLoader extends ClassLoader { 6 7 private final static String fileSuffixExt = ".class"; 8 9 private String classLoaderName; 10 11 private String loadPath; 12 13 public void setLoadPath(String loadPath) { 14 this.loadPath = loadPath; 15 } 16 17 public TulingClassLoader(ClassLoader parent, String classLoaderName) { 18 /** 19 * 指定當前類加載器的父類加載器 20 */ 21 super(parent); 22 this.classLoaderName = classLoaderName; 23 } 24 25 public TulingClassLoader(String classLoaderName) { 26 /** 27 * 使用appClassLoader加載器 作為本類的加載器 28 */ 29 super(); 30 this.classLoaderName = classLoaderName; 31 } 32 33 public TulingClassLoader(ClassLoader classLoader) { 34 super(classLoader); 35 } 36 37 /** 38 * 方法實現說明:創建我們的class 的二進制名稱 39 * @author:smlz 40 * @param name: 類的二進制名稱 41 * @return: 42 * @exception: 43 * @date:2019/10/22 14:42 44 */ 45 private byte[] loadClassData(String name) { 46 byte[] data = null; 47 ByteArrayOutputStream baos = null; 48 InputStream is = null; 49 50 try { 51 name = name.replace(".","\\"); 52 String fileName = loadPath+name+fileSuffixExt; 53 File file = new File(fileName); 54 is = new FileInputStream(file); 55 56 baos = new ByteArrayOutputStream(); 57 int ch; 58 while (‐1 != (ch = is.read())){ 59 baos.write(ch); 60 } 61 data = baos.toByteArray(); 62 }catch (Exception e) { 63 e.printStackTrace(); 64 }finally { 65 try{ 66 if(null != baos) { 67 baos.close(); 68 } 69 if(null !=is) { 70 is.close(); 71 } 72 }catch (Exception e) { 73 e.printStackTrace(); 74 } 75 } 76 77 return data; 78 } 79 80 protected Class<?> findClass(String name) throws ClassNotFoundException { 81 byte[] data = loadClassData(name); 82 System.out.println("TulingClassLoader 加載我們的類:===>"+name); 83 return defineClass(name,data,0,data.length); 84 } 85 }
(4)特別需要注意:我們自定義的類加載器默認情況下的父類加載器是我們的系統AppClassLoader
代碼證據:
1 public TulingClassLoader(String classLoaderName) { 2 /** 3 * 使用appClassLoader加載器 作為本類的加載器 4 */ 5 super(); 6 this.classLoaderName = classLoaderName; 7 } 8 9 //調用super()的時候 10 protected ClassLoader() { 11 //在這里,把getSystemClassLoader()作為我們自定義類加載器的 12 //父親 13 this(checkCreateClassLoader(), getSystemClassLoader()); 14 } 15 16 private ClassLoader(Void unused, ClassLoader parent) { 17 this.parent = parent; 18 if (ParallelLoaders.isRegistered(this.getClass())) { 19 parallelLockMap = new ConcurrentHashMap<>(); 20 package2certs = new ConcurrentHashMap<>(); 21 domains = 22 Collections.synchronizedSet(new HashSet<ProtectionDomain>()); 23 assertionLock = new Object(); 24 } else { 25 // no finer‐grained lock; lock on the classloader instance 26 parallelLockMap = null; 27 package2certs = new Hashtable<>(); 28 domains = new HashSet<>(); 29 assertionLock = this; 30 } 31 }
5、怎么用實驗證明我們的自定義類加載器的父加載器就是系統類加載器
(1)把我們的Person.class文件copy的指定的磁盤目錄下。同時classpath下 存在我們的Person.class文件
1 /** 2 * 證明系統類加載器就是我們的自定義類加載器的父類 3 * Created by smlz on 2019/11/12. 4 */ 5 public class AppClassLoaderIsCustomerClassLoaderParent { 6 7 public static void main(String[] args) throws ClassNotFoundException { 8 /** 9 * 執行test1()方法的時候打印結果式我們的系統類加載器去加載我們的 10 Person的,雖然我們是通過TulingClassLoader 去加載我們的Person.class 11 但是由於雙親委派模型會委托我們的AppClassLoader去我們的classes路面下去 12 加載Person.class由於我們的classes目錄下存在我們的Person.class 13 所以我們的Person.class被我們的AppClassLoader去加載. 14 15 16 17 =================================================== 18 若我們把classpath下的Person.class給刪除掉,那么我們的 19 TulingClassLoader嘗試去加載我們的Person.class,由於雙親委派模型下會委托父類A ppClassLoader 20 加載,但是我們人工把類路徑下的Person.class給刪除掉了后,那么我們的AppClassLo ader加載不了 21 我們的Person.class,從而是由我們的TulingClassLoader去加載. 22 **/ 23 test1(); 24 25 } 26 27 //正常情況下,把我們的AppIsCustParentDemo放到D:\\smlz的目錄下 28 public static void test1() throws ClassNotFoundException { 29 30 TulingClassLoader tulingClassLoader = new TulingClassLoader("tulingClas sLoader"); 31 //設置加載路徑 32 tulingClassLoader.setLoadPath("D:\\smlz\\"); 33 //通過自定義類加載器加載我們的AppIsCustParentDemo 34 Class<?> targetClass = tulingClassLoader.loadClass("com.tuling.smlz.jv m.open.AppIsCustParentDemo"); 35 36 System.out.println("targetClass 被class加載器加載..."+targetClass.getCla ssLoader()); 37 38 } 39 }
6、同一個Person.class文件 被我們的不同的類加載器去加載,那么我們的jvm內存種會生成二個對應的Person的Class對象,而且這二個對應的Class對象是相互不可見的(通過Class對象反射創建的實例對象相互是不能夠兼容的不能相互轉型) 這一點也很好的解釋了
1 public class Person { 2 3 private Person person; 4 5 public void setPerson(Object person) { 6 this.person = (Person) person; 7 } 8 }
1 public class Demo { 2 //需要把我們的ClassPath下的Person.class給刪除 3 public static void main(String[] args) throws ClassNotFoundException, Il legalAccessException, InstantiationException, NoSuchMethodException, Invoca tionTargetException { 4 5 TulingClassLoader classLoader1 = new TulingClassLoader("tulingClassLoade r1"); 6 classLoader1.setLoadPath("D:\\smlz\\"); 7 8 TulingClassLoader classLoader2 = new TulingClassLoader("tulingClassLoade r2"); 9 classLoader2.setLoadPath("D:\\smlz\\"); 10 11 //通過classLoader1加載我們的Person 12 Class<?> class1 = classLoader1.loadClass 13 ("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person"); 14 System.out.println("class1的類加載器:‐>"+class1.getClassLoader()); 15 16 Class<?> class2 = classLoader2.loadClass 17 ("com.tuling.smlz.jvm.open.TheSameClassLoadedByDiffClassLoader.Person"); 18 System.out.println("class2的類加載器:‐>"+class2.getClassLoader()); 19 20 System.out.println("class1==class2:"+(class1==class2)); 21 22 //模擬問題 23 Object person = class1.newInstance(); 24 25 Object person2 = class2.newInstance(); 26 27 Method method = class1.getMethod("setPerson",Object.class); 28 //會拋出類型轉換錯誤 29 method.invoke(person,person2); 30 } 31 }
7、類加載器的全盤委托機制以及 類加載器的命名空間
(1)類加載器的全盤委托機制:比如我們的Person類是由我們的AClassLoader進行加載的,那么我們Person引用的Dog類就會委托給我們的A ClassLoader進行加載
1 public class Person { 2 3 public Person() { 4 System.out.println("Dog類是由我們的類加載器:‐ >"+Dog.class.getClassLoader()); 5 } 6 } 7 8 public class Dog { 9 } 10 11 public class MainTest { 12 13 public static void main(String[] args) { 14 15 Person person = new Person(); 16 System.out.println("Person的classLoader:‐>"+person.getClass().getClassLo ader()); 17 18 } 19 }
(2)類加載器的命名空間
類加載器的命名空間 是有類加載器本身以及所有父加載器所加載出來的binary name(full class name)組成。
1)在同一個命名空間里,不允許出現二個完全一樣的binary name。
2)在不同的命名空間種,可以出現二個相同的binary name。當時二 者對應的Class對象是相互不能感知到的,也就是說Class對象的類型是不一樣的
3)子加載器的命名空間中的binary name對應的類中可以訪問 父加 載器命名空間中binary name對應的類,反之不行
8、驗證子加載器加載出來的類可以訪問父加載器加載的類
測試環境:我們的Person是由我們的自定義類加載器(把classpath下的Person.class刪除,並且把Person.class copy到磁盤文件上)TulingClassLoader進行加載的,Dog 是由我們的AppClassLoader進行加載的. 我們在Person中訪問Dog。
1 public class Dog { 2 } 3 4 public class Person { 5 6 public Person() { 7 new Dog(); 8 System.out.println("Dog的classLoader:‐‐>"+Dog.class.getClassLoader()); 9 } 10 11 } 12 13 public class TestDemo { 14 public static void main(String[] args) throws ClassNotFoundException, I llegalAccessException, InstantiationException { 15 TulingClassLoader classLoader = new TulingClassLoader("tulingClassLoade r"); 16 classLoader.setLoadPath("D:\\smlz\\"); 17 Class<?> clazz = classLoader.loadClass("com.tuling.smlz.jvm.open.classl oadernamespace.Person"); 18 clazz.newInstance(); 19 20 System.out.println("Person的類加載器:"+clazz.getClassLoader()); 21 } 22 }
9、如何證明父加載加載的類不能訪問子加載器加載的類
測試環境:把我們的Person.class放置在C:\ProgramFiles\Java\jdk1.8.0_131\jre\classes這個目錄下,那么我們的Person.class就會被我們的啟動類加載器加載,而我們的Dog類是被AppClassLoader進行加載,我們的Person類 中引用我們的Dog類會拋出異常。
1 public class Dog { 2 } 3 4 public class Person { 5 6 public Person() { 7 new Dog(); 8 System.out.println("Dog的classLoader:‐‐>"+ Dog.class.getClassLoader()); 9 } 10 } 11 12 13 public class TestDemo { 14 15 public static void main(String[] args) throws IllegalAccessException, I nstantiationException { 16 17 18 System.out.println("Person的類加載器:"+Person.class.getClassLoader()); 19 20 System.out.println("Dog的類加載器:"+Dog.class.getClassLoader()); 21 22 Class<?> clazz = Person.class; 23 clazz.newInstance(); 24 25 } 26 } 27 28 運行結果: 29 Person的類加載器:null 30 Dog的類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2 31 Exception in thread "main" java.lang.NoClassDefFoundError: com/tuling/sm lz/jvm/open/ParentClassLoaderNotAccessSonClassLoader/Dog 32 at com.tuling.smlz.jvm.open.ParentClassLoaderNotAccessSonClassLoader.Pe rson.<init>(Person.java:11) 33 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 34 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstruc torAccessorImpl.java:62) 35 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Delegating ConstructorAccessorImpl.java:45) 36 at java.lang.reflect.Constructor.newInstance(Constructor.java:423) 37 at java.lang.Class.newInstance(Class.java:442) 38 at com.tuling.smlz.jvm.open.ParentClassLoaderNotAccessSonClassLoader.Te stDemo.main(TestDemo.java:16)
10、打破雙親委派模型之 線程上下文類加載器
場景:JDBC接口技術之SPI之應用。
類的首次主動使用會觸發類的初始化。
1)調用靜態方法
2)給靜態變量賦值獲取讀取一個靜態變量
3)反射 Class.forName
4)new 出一個對象
5)執行main方法的時候
6)初始化子類會初始化他的父類
最后
關注公眾號:程序員追風。回復 003 領取2020最新Java面試題手冊(200多頁PDF文檔)