JVM: java virtua Machine
jdk中包含了jvm和“屏蔽操作系統(tǒng)差異的組件”
- jvm各個(gè)操作系統(tǒng)之上是一致的
- “屏蔽操作系統(tǒng)差異的組件:在各個(gè)PC上各不相同(聯(lián)想下載jdk,不同系統(tǒng) 需要下載不同版本的jdk)
類的生命周期
生命周期: 類的加載->連接->初始化->使用->卸載
類的加載
查找并加載類的二進(jìn)制數(shù)據(jù)(class文件)
硬盤上的class文件 加載到j(luò)vm內(nèi)存中-
連接 :確定類與類之間的關(guān)系 ; student.setAddress( address );
- 驗(yàn)證: .class 正確性校驗(yàn)
- 準(zhǔn)備: static靜態(tài)變量分配內(nèi)存,并賦初始化默認(rèn)值
static int num = 10 ; 在準(zhǔn)備階段,會把num=0,之后(初始化階段)再將0修改為10
在準(zhǔn)備階段,JVM中只有類,沒有對象。
初始化順序: static ->非static ->構(gòu)造方法 - 解析:把類中符號引用,轉(zhuǎn)為直接引用
前期階段,還不知道類的具體內(nèi)存地址,只能使用“com.chini.pojo.Student ”來替代Student類,“com.chini.pojo.Student ”就稱為符號引用;
在解析階段,JVM就可以將 “com.chini.pojo.Student ”映射成實(shí)際的內(nèi)存地址,會后就用 內(nèi)存地址來代替Student,這種使用 內(nèi)存地址來使用 類的方法 稱為直接引用。
初始化:給static變量 賦予正確的值
static int num = 10 ; 在連接的準(zhǔn)備階段,會把num=0,之后(初始化階段)再將0修改為10使用: 對象的初始化、對象的垃圾回收、對象的銷毀
卸載
jvm結(jié)束生命周期的時(shí)機(jī):
- 正常結(jié)束
- 異常結(jié)束/錯(cuò)誤
- System.exit()
- 操作系統(tǒng)異常
JVM內(nèi)存模型(Java Memoery Model,簡稱JMM)
JMM:用于定義(所有線程的共享變量, 不能是局部變量)變量的訪問規(guī)則
JMM將內(nèi)存劃分為兩個(gè)區(qū): 主內(nèi)存區(qū)、工作內(nèi)存區(qū)
- 主內(nèi)存區(qū) :真實(shí)存放變量
- 工作內(nèi)存區(qū):主內(nèi)存中變量的副本,供各個(gè)線程所使用
注意:
1.各個(gè)線程只能訪問自己私有的工作內(nèi)存(不能訪問其他線程的工作內(nèi)存,也不能訪問主內(nèi)存)
2.不同線程之間,可以通過主內(nèi)存間接的訪問其他線程的工作內(nèi)存
完整的研究:不同線程之間交互數(shù)據(jù)時(shí) 經(jīng)歷的步驟:
1.Lock:將主內(nèi)存中的變量,表示為一條線程的獨(dú)占狀態(tài)
2.Read:將主內(nèi)存中的變量,讀取到工作內(nèi)存中
3.Load:將2中讀取的變量拷貝到變量副本中
4.Use:把工作內(nèi)存中的變量副本,傳遞給線程去使用
5.Assign:把線程正在使用的變量,傳遞給工作內(nèi)存中的變量副本中
6.Store:將工作內(nèi)存中變量副本的值,傳遞到主內(nèi)存中
7.Write:將變量副本作為一個(gè)主內(nèi)存中的變量進(jìn)行存儲
8.Unlock:解決線程的獨(dú)占狀態(tài)

JVM要求以上的8個(gè)動(dòng)作必須是原子性的;jvm但是對于64位的數(shù)據(jù)類型(long double)有些非原子性協(xié)議。說明什么問題:在執(zhí)行以上8個(gè)操作時(shí),可能會出現(xiàn)只讀?。▽懭氲龋┝税雮€(gè)long/double數(shù)據(jù),因此出現(xiàn)錯(cuò)誤。如何避免? 1.商用JVM已經(jīng)充分考慮了此問題,無需我們操作 2.可以通過volatile避免此類問題(讀取半個(gè)數(shù)據(jù)的問題) volatile double num ;
volatile關(guān)鍵字
概念:JVM提供的一個(gè)輕量級的同步機(jī)制
作用:
- 防止JVM對long/double等64位的非原子性協(xié)議進(jìn)行的誤操作(讀取半個(gè)數(shù)據(jù))
- 可以使變量對所有的線程立即可見(某一個(gè)線程如果修改了工作內(nèi)存中的變量副本,那么加上volatile 之后,該變量就會立刻同步到其他線程的工作內(nèi)存中)
- 禁止指令的“重排序”優(yōu)化
原子性 : num = 10 ;
非原子性: int num = 10 ; -> int num ; num =10 ;
重排序:排序的對象就是 原子性操作,目的是為了提高執(zhí)行效率,優(yōu)化
int a =10 ; //1 int a ; a = 10 ;
int b ;//2
b = 20 ;//3
int c = a * b ;//4
重排序“不會影響單線程的執(zhí)行結(jié)果”,因此以上程序在經(jīng)過重排序后,可能的執(zhí)行結(jié)果:1,2,3,4 ;2,3,1,4
package com.chini;
//雙重檢查式的懶漢式單例模式
public class Singleton {
private static Singleton instance = null ;//單例
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){// 主要避免每次都要排隊(duì)的問題
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton() ;//不是一個(gè)原子性操作
}
}
}
return instance ;
}
}
以上代碼可能會出現(xiàn)問題,原因 instance = new Singleton() 不是一個(gè)原子性操作,會在執(zhí)行時(shí)拆分成以下動(dòng)作:
1.JVM會分配內(nèi)存地址、內(nèi)存空間
2.使用構(gòu)造方法實(shí)例化對象
3.instance = 第1步分配好的內(nèi)存地址
根據(jù)重排序的知識,可知,以上3個(gè)動(dòng)作在真正執(zhí)行時(shí) 可能1、2、3,也可能是1、3、2
如果在多線程環(huán)境下,使用1、3、2可能出現(xiàn)問題:
假設(shè)線程A剛剛執(zhí)行完以下步驟(即剛執(zhí)行 1、3,但還沒有執(zhí)行2)
1正常0x123 , ...
3instance=0x123
此時(shí),線程B進(jìn)入單例程序的if,直接會得到Instance對象(注意,此instance是剛才線程A并沒有new的對象),就去使用該對象,例如instance.xxx() 則必然報(bào)錯(cuò)。解決方案,就是 禁止此程序使用1 3 2 的重排序順序。解決方法:使用volatile關(guān)鍵字
private volatile static Singleton instance = null ;//單例
volatile是通過“內(nèi)存屏障”防止重排序問題:
1.在volatile寫操作前,插入StoreStore屏障
2.在volatile寫操作后,插入StoreLoad屏障
3.在volatile讀操作后,插入LoadLoad屏障
4.在volatile讀操作后,插入LoadStore屏障
volatile是否能保證原子性、保證線程安全?不能!
要想保證原子性/線程安全,可以使用原子包java.util.cocurrent.aotmic中的類,該類能夠保證原子性的核心,是因?yàn)樘峁┝薱ompareAndSet()方法,該方法提供了 cas算法(無鎖算法)。
JVM運(yùn)行時(shí)的內(nèi)存區(qū)域
將JVM在運(yùn)行時(shí)的內(nèi)存,劃分為了5個(gè)部分,如圖所示。

程序計(jì)數(shù)器
程序計(jì)數(shù)器:行號指示器,指向當(dāng)前線程所執(zhí)行的字節(jié)碼指令的地址
簡單的可以理解為:class文件中的行號
注意:
- 一般情況下,程序計(jì)數(shù)器 是行號;但如果正在執(zhí)行的方法是native方法,則程序計(jì)數(shù)器的值 undefined。
- 程序計(jì)數(shù)器 是唯一一個(gè) 不會 產(chǎn)生 “內(nèi)存溢出”的區(qū)域。
goto的本質(zhì)就是改變的程序計(jì)數(shù)器的值(java中沒有g(shù)oto,goto在java中的保留字)
虛擬機(jī)棧
定義:描述 方法執(zhí)行的內(nèi)存模型
- 方法在執(zhí)行的同時(shí),會在虛擬機(jī)棧中創(chuàng)建一個(gè)棧幀
- 棧幀中包含:方法的局部變量表,操作數(shù)據(jù)棧、動(dòng)態(tài)鏈接、方法出口信息等
當(dāng)方法太多時(shí),就可能發(fā)生棧溢出異常StackOverflowError,或者內(nèi)存溢出異常OutOfMemoryError
本地方法棧
原理和結(jié)構(gòu)與虛擬機(jī)棧一致,不同點(diǎn): 虛擬機(jī)棧中存放的 jdk或我們自己編寫的方法,而本地方法棧調(diào)用的 操作系統(tǒng)底層的方法。

堆

- 存放對象實(shí)例(數(shù)組、對象)
- 堆是jvm區(qū)域中最大的一塊,在jvm啟動(dòng)時(shí)就已經(jīng)創(chuàng)建完畢
- GC主要管理的區(qū)域
- 堆本身是線程共享,但在堆內(nèi)部可以劃分出多個(gè)線程私有的緩沖區(qū)
- 堆允許物理空間不連續(xù),只要邏輯連續(xù)即可
- 堆可以分新生代、老生代 。大小比例,新生代:老生代= 1:2
- 新生代中 包含eden、s0、s1 = 8:1:1
- 新生代的使用率一般在90%。 在使用時(shí),只能使用 一個(gè)eden和一塊s區(qū)間(s0或s1)
- 新生代:存放 1.生命周期比較短的對象 2.小的對象;反之,存放在老生代中。對象的大小可以通過參數(shù)設(shè)置 -XX:PretenureSizeThredshold 。一般而言,大對象一般是 集合、數(shù)組、字符串。生命周期: -XX:MaxTenuringThredshold
新生代、老生代中年齡:MinorGC回收新生代中的對象。如果Eden區(qū)中的對象在一次回收后仍然存活,就會被轉(zhuǎn)移到 s區(qū)中;之后,如果MinorGC再次回收,已經(jīng)在s區(qū)中的對象仍然存活,則年齡+1。如果年齡增長一定的數(shù)字,則對象會被轉(zhuǎn)移到 老生代中。簡言之:在新生代中的對象,每經(jīng)過一次MinorGC,有三種可能:1從eden -> s區(qū) 2.(已經(jīng)在s區(qū)中)年齡+1 3.轉(zhuǎn)移到老生代中
GC
新生代在使用時(shí),只能同時(shí)使用一個(gè)s區(qū):底層采用的是復(fù)制算法,為了避免碎片產(chǎn)生
老生代: 1.生命周期比較長的對象 2.大的對象; 使用的回收器 MajorGC\FullGC
新生代特點(diǎn):
- 大部分對象都存在于新生代
- 新生代的回收頻率高、效率高
老生代特點(diǎn): - 空間大
- 增長速度慢
- 頻率低
意義:可以根據(jù)項(xiàng)目中對象大小的數(shù)量,設(shè)置新生代或老生代的空間容量,從提高GC的性能。
如果對象太多,也可能導(dǎo)致內(nèi)存異常。
虛擬機(jī)參數(shù):
-Xms128m :JVM啟動(dòng)時(shí)的大小
-Xmn32m:新生代大小
-Xmx128:總大小(一般來說啟動(dòng)和總大小一致)
jvm總大小= 新生代 + 老生代
堆內(nèi)存溢出的示例:java.lang.OutOfMemoryError: Java heap space
方法區(qū)
存放:類的元數(shù)據(jù)(描述類的信息)、常量池、方法信息(方法數(shù)據(jù)、方法代碼)
gc:類的元數(shù)據(jù)(描述類的信息)、常量池
方法區(qū)中數(shù)據(jù)如果太多,也會拋異常OutOfMemory異常

常量池:存放編譯期間產(chǎn)生的 字面量("abc")、符號引用
注意: 導(dǎo)致內(nèi)存溢出的異常OutOfMemoryError,除了虛擬機(jī)中的4個(gè)區(qū)域以外,還可能是直接內(nèi)存。在NIO技術(shù)中會使用到直接內(nèi)存。
類的使用方式(分為主動(dòng)使用和被動(dòng)使用)
類的初始化:JVM只會在“首次主動(dòng)使用”一個(gè)類/接口時(shí),才會初始化它們 。
主動(dòng)使用
1.new 構(gòu)造類的使用
package init;
public class Test1 {
static{
System.out.println("Test1...");
}
public static void main(String[] args) {
new Test1();//首次主動(dòng)使用
new Test1();
}
}
結(jié)果:Test1...
2.訪問類/接口的 靜態(tài)成員(屬性、方法)
package init;
class A{
static int i = 10;
static{
System.out.println("A...");
}
static void method(){
System.out.println("A method...");
}
}
public class Test2 {
public static void main(String[] args) {
// A.i = 1 ;
// A.i = 1 ;
// System.out.println(A.i);
A.method();
}
}
注:main()本身也是一個(gè)靜態(tài)方法,也此main()的所在類 也會在執(zhí)行被初始化
特殊情況:
- 如果成員變量既是static,又是final ,即常量,則不會被初始化
- 上一種情況中,如果常量的值是一個(gè)隨機(jī)值,則會被初始化 (為了安全)
- 使用Class.forName("init.B")執(zhí)行反射時(shí)使用的類(B類)
- 初始化一個(gè)子類時(shí),該子類的父類也會被初始化
public class Son extends Father {
public static void main(String[] args) {
new Son();
}
}
5.動(dòng)態(tài)語言在執(zhí)行所涉及的類 也會被初始化(動(dòng)態(tài)代理,了解即可)
被動(dòng)使用
除了主動(dòng)以外,其他都是被動(dòng)使用。
package init;
class BD
{
static {
System.out.println("BD...");
}
}
public class BeiDong {
public static void main(String[] args) {
BD[] bds = new BD[3];
}
}
以上代碼,不屬于主動(dòng)使用類,因此不會被初始化。
助記符
反編譯: cd到class目錄中, javap -c class文件名
Test.java -> javap -c Test.java
javap反編譯的是class文件
應(yīng)該:xx.java -> xx.class ->javap
aload_0: 裝載了一個(gè)引用類型
Invokespecial: init, private , super.method() : <init>存放的是初始化代碼的位置
getstatic :獲取靜態(tài)成員
bipush : 整數(shù)范圍 -128 -- 127之內(nèi) (8位帶符號的整數(shù)),放到棧頂
sipush: >127 (16個(gè)帶符號的整數(shù)),放到棧頂
注意:無論是定義int或short 等,只要在 -128 --127以內(nèi) 都是bipush,否則是sipush.
注意:特殊:-1 -- 5不是bipush
iconst_m1(-1) iconst_0 iconst_1 .... iconst_5
ldc : int float String 常量 ,放到棧頂
ldc2_w :long double常量,放到棧頂
JVM四種引用級別
如果一個(gè)對象存在著指向它的引用,那么這個(gè)對象就不會被GC回收? -- 局限性,只適用于強(qiáng)引用
Object obj = new Object() ; --強(qiáng)引用
根據(jù)引用的強(qiáng)弱關(guān)系: 強(qiáng)引用>軟引用>弱引用>虛引用
強(qiáng)引用
Object obj = new Object() ;
約定: 引用 obj,引用對象new Object()
強(qiáng)引用對象什么失效?
1.生命周期結(jié)束(作用域失效)
public void method(){
Object obj = new Object() ;
}//當(dāng)方法執(zhí)行完畢后,強(qiáng)引用指向的 引用而對象new Object()就會等待被GC回收
2.引用被置為null,引用對象被GC回收
obj = null ;//此時(shí),沒有任何引用指向new Object() 因此,new Object() 就會等待被GC回收
除了以上兩個(gè)情況以外,其他任何時(shí)候GC都不會回收強(qiáng)引用對象。
軟引用
根據(jù)JVM內(nèi)存情況: 如果內(nèi)存充足,GC不會隨便的回收軟引用對象;如果JVM內(nèi)存不足,則GC就會主動(dòng)的回收軟引用對象。
各種引用的出處:
強(qiáng)引用:new
軟引用 弱引用 虛引用 (最終引用):Reference
軟引用:java.lang.ref.SoftReference
Reference中有一個(gè)get()方法,用于返回 所引用的對象
SoftReference<SoftObject> softRef = new SoftReference<>(new SoftObject() );
softRef.get() -->返回引用所指向的SoftObject對象本身
package ref;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
//軟引用對象
class SoftObject{}
public class SoftReferenceDemo {
public static void main(String[] args) throws Exception {
//softRef -->SoftObject 設(shè)計(jì)模式中的:裝飾模式
SoftReference<SoftObject> softRef = new SoftReference<>(new SoftObject() );
List<byte[]> list = new ArrayList<>();
//開啟一個(gè)線程,監(jiān)聽 是否有軟引用已經(jīng)被回收
new Thread( ()->{
while(true) {
if (softRef.get() == null) //軟引用對象
{
System.out.println("軟引用對象已被回收..");
System.exit(0);
}
}
} ,"線程A" ) .start(); //lambda
//不斷的往集合中 存放數(shù)據(jù),模擬內(nèi)存不足
while(true){
// Thread.sleep(10);
if(softRef.get() != null)
list.add(new byte[1024*1024]) ;//每次向list中增加1m內(nèi)容
}
}
}
弱引用
回收的時(shí)機(jī):只要GC執(zhí)行,就會將弱引用對象進(jìn)行回收。
java.lang.ref.WeakReference<T>
package ref;
import java.lang.ref.WeakReference;
public class WeakReferenceDemo {
public static void main(String[] args) throws Exception {
WeakReference<Object> weakRef = new WeakReference<>(new Object());
//weakRef->Object
System.out.println( weakRef.get()==null ? "已被回收":"沒被回收" );
System.gc();//建議GC執(zhí)行一次回收(存在概率)
Thread.sleep(100);
System.out.println( weakRef.get()==null ? "已被回收":"沒被回收" );
}
}
虛引用(幻影引用或者幽靈引用)
java.lang.ref.PhantomReference<T>
是否使用虛引用,和引用對象本身 沒有任何關(guān)系; 無法通過虛引用來獲取對象本身.
引用get() -> 引用對象
虛引用get() -> null
虛引用不會單獨(dú)使用,一般會和 引用隊(duì)列(java.lang.ref.ReferenceQueue)一起使用。
價(jià)值: 當(dāng)gc回收一個(gè)對象,如果gc發(fā)現(xiàn) 此對象還有一個(gè)虛引用,就會將虛引用放入到 引用隊(duì)列中,之后(當(dāng)虛引用出隊(duì)之后)再去回收該對象。因此,我們可以使用 虛引用+引用對象 實(shí)現(xiàn):在對象被gc之前,進(jìn)行一些額外的其他操作。
GC ->如果有虛引用->虛引用入隊(duì)->虛引用出隊(duì)-> 回收對象
package ref;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
class MyObject {
}
public class PhantomReferenceDemo {
public static void main(String[] args) throws Exception {
MyObject obj = new MyObject();
//引用隊(duì)列
ReferenceQueue queue = new ReferenceQueue();
//虛引用+引用隊(duì)列
PhantomReference<MyObject> phantomRef = new PhantomReference<>(obj, queue);
//讓gc執(zhí)行一次回收操作
obj = null;
System.gc();
Thread.sleep(30);
System.out.println("GC執(zhí)行...");
//GC-> 虛引用->入隊(duì)->出隊(duì)-> obj
System.out.println(queue.poll());
}
}
特殊情況:如果虛引用對象重寫了finalize(),那么JVM會延遲 虛引用的入隊(duì)時(shí)間。
final class Finalizer extends FinalReference:最終引用
構(gòu)造方法() -> 析構(gòu)函數(shù)(),在java中存在Finalizer 可以幫我們自動(dòng)的回收一些不需要的對象,因此不需要寫析構(gòu)函數(shù)。
jvm能夠直接操作的是:非直接內(nèi)存
直接內(nèi)存:native (操作系統(tǒng)中的內(nèi)存,而不是jvm內(nèi)存)
jvm不能操作 直接內(nèi)存(非jvm操作的內(nèi)容)時(shí),而恰好 此區(qū)域的內(nèi)容 又忘了關(guān)閉,此時(shí)Finalizer就會將這些內(nèi)存進(jìn)行回收。
使用軟引用實(shí)現(xiàn)緩存的淘汰策略
java ->緩存( 90% ->60%) -> db(iphone)
LRU
一般的淘汰策略:
根據(jù)容量/緩存?zhèn)€數(shù) + LRU 進(jìn)行淘汰。
在java中 還可以用引用實(shí)現(xiàn) 淘汰策略。
MyObject obj = new MyObject();//強(qiáng)引用,不會被GC回收
map.put( id , obj ) ;
Map.put(id, 軟引用(obj) );//當(dāng)jvm內(nèi)存不足時(shí),會主動(dòng)回收。
package ref;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
class MyObject10{}
public class SoftDemo {
//map: key:id ,value:對象的軟引用 (拿對象: 對象的軟引用 .get() )
Map<String, SoftReference<MyObject10>> caches = new HashMap<>();
//java -> caches -> db
//set: db->caches
//get: java->cache
void setCaches(String id,MyObject10 obj){
caches.put( id, new SoftReference<MyObject10>(obj) );
}
MyObject10 getCache(String id){
SoftReference<MyObject10> softRef = caches.get(id) ;
return softRef == null ? null : softRef.get() ;
}
//優(yōu)勢:當(dāng)jvm內(nèi)存不足時(shí),gc會自動(dòng)回收軟引用。因此本程序 無需考慮 OOM問題。
}
雙親委派
前置:類的加載
package com.chini.parents;
class MyClass{
static int num1 = 100 ;
static MyClass myClass = new MyClass();
public MyClass(){
num1 = 200 ;
num2 = 200 ;
}
static int num2 = 100 ;
public static MyClass getMyClass(){
return myClass ;
}
@Override
public String toString() {
return this.num1 + "\t" + this.num2 ;
}
}
public class MyClassLoader {
public static void main(String[] args) {
MyClass myc = MyClass.getMyClass() ;
System.out.println(myc);
}
}
分析
static int num1 = 100 ; 【 0 】-> 【100】->【200】
static MyClass myClass = new MyClass();【null】 ->【引用地址0x112231】
public MyClass(){
num1 = 200 ;
num2 = 200 ;
}
static int num2 = 100 ; 【0】->【200】->【100】
連接:static靜態(tài)變量并賦默認(rèn)值
初始化:給static變量 賦予正確的值
總結(jié):在類中 給靜態(tài)變量的初始化值問題,一定要注意順序問題(靜態(tài)變量 和構(gòu)造方法的順序問題)
雙親委派: JVM自帶的加載器(在JVM的內(nèi)部所包含,C++)、用戶自定義的加載器(獨(dú)立于JVM之外的加載器,Java)
1、JVM自帶的加載器
- 根加載器,Bootstrap : 加載 jre\lib\rt.jar (包含了平時(shí)編寫代碼時(shí) 大部分jdk api);指定加載某一個(gè)jar( -Xbootclasspath=a.jar)
- 擴(kuò)展類加載器,Extension:C:\Java\jdk1.8.0_101\jre\lib\ext\*.jar ;指定加載某一個(gè)jar(-Djava.ext.dirs= ....)
- AppClassLoader/SystemClassLoader,系統(tǒng)加載器(應(yīng)用加載器):加載classpath;指定加載(-Djava.class.path= 類/jar)
2、用戶自定義的加載器 - 都是抽象類java.lang.ClassLoader的子類
加載器
雙親委派:當(dāng)一個(gè)加載器要加載類的時(shí)候,自己先不加載,而是逐層向上交由雙親去加載;當(dāng)雙親中的某一個(gè)加載器 加載成功后,再向下返回成功。如果所有的雙親和自己都無法加載,則報(bào)異常。
package com.chini.parents;
//classpath: .; ..lib,其中“.”代表當(dāng)前(自己寫的類)
class MyClass2{
}
public class TestParentsClassLoader {
public static void main(String[] args) throws Exception {
Class myClass1 = Class.forName("java.lang.Math") ;
ClassLoader classLoader1 = myClass1.getClassLoader();
System.out.println(classLoader1);
/* JDK中的官方說明:
Some implementations may use null to represent the bootstrap class loader
*/
Class myClass2 = Class.forName("com.chini.parents.MyClass2") ;
ClassLoader classLoader2 = myClass2.getClassLoader();
System.out.println(classLoader2);
}
}

小結(jié):如果類是 rt.jar中的,則該類是被 bootstrap(根加載器)加載;如果是classpath中的類(自己編寫的類),則該類是被AppClassLoader加載。
定義類加載:最終實(shí)際加載類的 加載器
初始化類加載類:直接面對加載任務(wù)的類
package com.chini.parents;
import java.net.URL;
import java.util.Enumeration;
class MyCL{
}
public class JVMParentsCL {
public static void main(String[] args) throws Exception {
Class<?> myCL = Class.forName("com.chini.parents.MyCL");
ClassLoader classLoader = myCL.getClassLoader();
System.out.println(classLoader);
System.out.println("---");
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
ClassLoader parent1 = systemClassLoader.getParent();
System.out.println(parent1);
ClassLoader parent2 = parent1.getParent();
System.out.println(parent2);
System.out.println("----");
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
Enumeration<URL> resources = appClassLoader.getResources("com/yanqun/chini/MyCL.class");// a/b/c.txt
while(resources.hasMoreElements()){
URL url = resources.nextElement();
System.out.println(url);
}
}
}

自定義類的加載器
二進(jìn)制名binary names:
"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"
數(shù)字:第幾個(gè)匿名內(nèi)部類
The class loader for an array class, as returned by {@link* Class#getClassLoader()} is the same as the class loader for its element* type; if the element type is a primitive type, then the array class has no* class loader.
1.數(shù)組的加載器類型 和數(shù)組元素的加載器類型 是相同
2.原聲類型的數(shù)組 是沒有類加載器的
如果加載的結(jié)果是null: 可能是此類沒有加載器(int[]) , 也可能是 加載類型是“根加載器”
<p> However, some classes may not originate from a file; they may originate* from other sources, such as the network, or they could be constructed by an* application. The method {@link #defineClass(String, byte[], int, int)* <tt>defineClass</tt>} converts an array of bytes into an instance of class* <tt>Class</tt>. Instances of this newly defined class can be created using* {@link Class#newInstance <tt>Class.newInstance</tt>}.
xxx.class文件可能是在本地存在,也可能是來自于網(wǎng)絡(luò) 或者在運(yùn)行時(shí)動(dòng)態(tài)產(chǎn)生(jsp)
如果class文件來自原Network,則加載器中必須重寫findClas()和loadClassData().
自定義類加載器的實(shí)現(xiàn)
重寫findClas()和loadClassData()
package com.chini.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
//優(yōu)先使用的類加載器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){//擴(kuò)展類加載器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
System.out.println(name);
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
name = dotToSplit("out.production.MyJVM."+name)+".class" ;
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception{
//自定義加載器的對象
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默認(rèn)在雙親委派時(shí),會根據(jù)正規(guī)流程:系統(tǒng)——》擴(kuò)展->根
// MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某個(gè) 具體的的委派
Class<?> aClass = myClassLoader.loadClass("com.chini.parents.MyDefineCL");
System.out.println(aClass.getClassLoader());
MyDefineCL myDefineCL = (MyDefineCL)(aClass.newInstance() );
myDefineCL.say();
}
public static String dotToSplit(String clssName){
return clssName.replace(".","/") ;
}
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
實(shí)現(xiàn)流程:
1.public class MyClassLoaderImpl extends ClassLoader
2.findClass(String name){...} :直接復(fù)制文檔中的NetworkClassLoader中的即可
3.loadClassData(String name){...} :name所代表的文件內(nèi)容->byte[]
4.細(xì)節(jié):
loadClassData(String name): 是文件形式的字符串a(chǎn)/b/c.class,并且開頭out.production..
findClass(String name):是全類名的形式 a.b.c.class,并且開頭 是: 包名.類名.class
操作思路:
要先將 .class文件從classpath中刪除,之后才可能用到 自定義類加載器;否在classpath中的.class會被 APPClassLoader加載
package com.chini.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
private String path ; //null
//優(yōu)先使用的類加載器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){//擴(kuò)展類加載器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
System.out.println("findClass...");
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
System.out.println("加載loadClassData...");
if(path != null){//name: com.yanqun.parents.MyDefineCL
name = path+ name.substring( name.lastIndexOf(".")+1 )+".class" ;
}else{
//classpath ->APPClassLoader
name = dotToSplit("out.production.MyJVM."+name)+".class" ;
}
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception {
System.out.println("main...");
//自定義加載器的對象
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默認(rèn)在雙親委派時(shí),會根據(jù)正規(guī)流程:系統(tǒng)——》擴(kuò)展->根
myClassLoader.path = "d:/" ;
//MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//直接指定某個(gè) 具體的的委派
Class<?> aClass = myClassLoader.loadClass("com.chini.parents.MyDefineCL");
System.out.println(aClass.getClassLoader());
// MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
}
public static String dotToSplit(String clssName){ return clssName.replace(".","/") ; }
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
代碼流程:
loadClass() ->findClass()->loadClassData()
一般而言,啟動(dòng)類加載loadClass();
實(shí)現(xiàn)自定義加載器,只需要:
1.繼承ClassLoader
2重寫的 findClass()
情況一:用APPClassLoader
classpath中的MyDefineCL.class文件:
1163157884
1163157884
d盤中的MyDefineCL.class文件:
356573597
說明,類加載器 只會把同一個(gè)類 加載一次; 同一個(gè)class文件 加載后的位置
結(jié)論:
自定義加載器 加載.class文件的流程:
先委托APPClassLoader加載,APPClassLoader會在classpath中尋找是否存在,如果存在 則直接加載;如果不存在,才有可能交給 自定義加載器加載。
package com.chini.parents;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
//public class MyException extends Exception{...}
public class MyClassLoaderImpl extends ClassLoader{
private String path ; //null
//優(yōu)先使用的類加載器是:getSystemClassLoader()
public MyClassLoaderImpl(){
super();
}
public MyClassLoaderImpl(ClassLoader parent){//擴(kuò)展類加載器
super(parent);
}
//com.yq.xx.class
public Class findClass(String name) {
// System.out.println("findClass...");
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//“com/yq/xxx.class” -> byte[]
private byte[] loadClassData(String name) {
// System.out.println("加載loadClassData...");
if(path != null){//name: com.yanqun.parents.MyDefineCL
// System.out.println("去D盤加載;;");
name = path+ name.substring( name.lastIndexOf(".")+1 )+".class" ;
}
byte[] result = null ;
FileInputStream inputStream = null ;
ByteArrayOutputStream output = null ;
try {
inputStream = new FileInputStream( new File( name) );
//inputStream -> byte[]
output = new ByteArrayOutputStream();
byte[] buf = new byte[2];
int len = -1;
while ((len = inputStream.read(buf)) != -1) {
output.write(buf, 0, len);
}
result = output.toByteArray();
}catch (Exception e){
e.printStackTrace(); ;
}finally {
try {
if(inputStream != null )inputStream.close();
if(output != null ) output.close();
}catch (Exception e){
e.printStackTrace();
}
}
return result ;
}
public static void main(String[] args) throws Exception {
// System.out.println("main...");
//自定義加載器的對象
// MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl();//默認(rèn)在雙親委派時(shí),會根據(jù)正規(guī)流程:系統(tǒng)——》擴(kuò)展->根
// myClassLoader.path = "d:/" ;
// Class<?> aClass = myClassLoader.loadClass("com.chini.parents.MyDefineCL");
// System.out.println(aClass.hashCode());
MyClassLoaderImpl myClassLoader2 = new MyClassLoaderImpl();//默認(rèn)在雙親委派時(shí),會根據(jù)正規(guī)流程:系統(tǒng)——》擴(kuò)展->根
Class<?> aClass2 = myClassLoader2.loadClass("com.chini.parents.MyDefineCL");
System.out.println(aClass2.hashCode());
// System.out.println(aClass.getClassLoader());
// MyDefineCL myDefineCL = (MyDefineCL)( aClass.newInstance()) ;
}
public static String dotToSplit(String clssName){ return clssName.replace(".","/") ; }
}
class MyDefineCL{
public void say(){
System.out.println("Say...");
}
}
通過以下源碼可知,在雙親委派體系中,“下面”的加載器 是通過parent引用 “上面”的加載器。即在雙親委派體系中,各個(gè)加載器之間不是繼承關(guān)系。
雙親委派機(jī)制優(yōu)勢: 可以防止用戶自定義的類 和 rt.jar中的類重名,而造成的混亂
根據(jù)雙親委派, 越上層的加載器越優(yōu)先執(zhí)行。最頂層的加載器是 根加載器,根加載器就會加載rt.jar中的類。因此rt.jar中的Math會被優(yōu)先加載。 即程序最終加載的是不是我們自己寫的Math,而是jdk/rt.jar中 內(nèi)置的Math;而內(nèi)置的Math根本沒有提供main()方法,因此報(bào) 無法找到main()。
實(shí)驗(yàn):將相關(guān)聯(lián)的類A.class和B.class分別用 不同的類加載器加載
A和B是繼承關(guān)系
public class B{
public B(){
System.out.println("B被加載了,加載器是: "+ this.getClass().getClassLoader());//對象使用之前,必然先把此對象對應(yīng)的類加載
}
}
public class A extends B
{
public A(){
super();
System.out.println("A被加載了,加載器是: "+ this.getClass().getClassLoader());//對象使用之前,必然先把此對象對應(yīng)的類加載
}
}
//AppClassLoader.class : TestMyClassLoader2
//自定義加載器: A.class/B.class
public class TestMyClassLoader2 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定義加載路徑
myClassLoader.path = "d:/" ;
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.A");
Object aObject = aClass.newInstance();//newInstance()會調(diào)用 該類的構(gòu)造方法(new 構(gòu)造方法())
System.out.println(aObject);
}
}
A和B不是繼承關(guān)系
public class Y {
public Y(){
System.out.println("Y被加載了,加載器是: "+ this.getClass().getClassLoader());//對象使用之前,必然先把此對象對應(yīng)的類加載
}
}
public class X {
public X(){
new Y() ;//加載Y(系統(tǒng)加載器)
System.out.println("X被加載了,加載器是: "+ this.getClass().getClassLoader());//對象使用之前,必然先把此對象對應(yīng)的類加載
}
}
//AppClassLoader.class : TestMyClassLoader2
//自定義加載器: A.class/B.class
public class TestMyClassLoader3 {
public static void main(String[] args) throws Exception{
MyClassLoaderImpl myClassLoader = new MyClassLoaderImpl() ;
//自定義加載路徑
myClassLoader.path = "d:/" ;
//程序第一次加載時(shí)(X),使用的是 自定義加載器
Class<?> aClass = myClassLoader.loadClass("com.yanqun.parents.X");
Object aObject = aClass.newInstance();//newInstance()會調(diào)用 該類的構(gòu)造方法(new 構(gòu)造方法())
System.out.println(aObject);
}
}
存在繼承關(guān)系
A.class: classpath
B.class: classpath
原因
同一個(gè)AppClassLoader 會同時(shí)加載A.class和B.class
--
A.class: d:\
B.class: classpath
原因
A.class:自定義加載器加載
B.class:被AppClassLoader加載
因此,加載A.class和B.class的不是同一個(gè)加載器
IllegalAccess
---
A.class: classpath
B.class: d:\
NoClassDefFoundError
原因:
A.class: 被AppClassLoader加載
B.class: 自定義加載器加載
因此,加載A.class和B.class的不是同一個(gè)加載器
--
A.class d:\
B.class d:\
TestMyClassLoader2 can not access a member of class com.yanqun.parents.A with modifiers "public"
A.class/B.class: 自定義加載器加載
原因是 main()方法所在類在 工程中(APPClassLoader),而A和B不在工程中(自定義加載器)。
造成這些異常的核心原因: 命名空間(不是由同一個(gè)類加載器所加載)
----
沒有繼承關(guān)系
X.class: D: 自定義加載器
Y.class: classpath 系統(tǒng)加載器
Y被加載了,加載器是: sun.misc.Launcher$AppClassLoader@18b4aac2
X被加載了,加載器是: com.yanqun.parents.MyClassLoaderImpl@74a14482
---
X.class: classpath 系統(tǒng)加載器
Y.class: D: 自定義加載器
java.lang.NoClassDefFoundError: com/yanqun/parents/Y
--
如果存在繼承關(guān)系: 繼承的雙方(父類、子類)都必須是同一個(gè)加載器,否則出錯(cuò);
如果不存在繼承關(guān)系: 子類加載器可以訪問父類加載器加載的類(自定義加載器,可以訪問到 系統(tǒng)加載器加載的Y類);反之不行(父類加載器 不能訪問子類加載器)
核心: 雙親委派
如果都在同一個(gè)加載器 ,則不存在加載問題; 如果不是同一個(gè),就需要雙親委派。
如果想實(shí)現(xiàn)各個(gè)加載器之間的自定義依賴,可以使用ogsi規(guī)范
OSGi:
1.網(wǎng)狀結(jié)構(gòu)的加載結(jié)構(gòu)
2.屏蔽掉硬件的異構(gòu)性。例如,可以將項(xiàng)目部署在網(wǎng)絡(luò)上,可以在A節(jié)點(diǎn)上 遠(yuǎn)程操作B節(jié)點(diǎn)。在操作上,可以對硬件無感。也可以在A節(jié)點(diǎn)上 對B節(jié)點(diǎn)上的項(xiàng)目進(jìn)行運(yùn)維、部署,并且立項(xiàng)情況下 在維護(hù)的期間,不需要暫時(shí)、重啟。
類的卸載
1.系統(tǒng)自帶(系統(tǒng)加載器、擴(kuò)展加載器、根加載器):這些加載器加載的類 是不會被卸載。
2.用戶自定義的加載器,會被GC卸載GC

