1.描述一下 JVM 加載 class 文件的機(jī)制
說明:
Java 中的所有類都需要被類加載器加載到 JVM 中(類加載本身也是一個類,其主要工作是把 class 文件從磁盤讀取到內(nèi)存中)。
1.1 類加載的方式有兩種:
① 隱式加載,程序運(yùn)行過程中通過 new 指令實例化對象,即把類隱式地加載到 JVM 中;
例子1:加載一個類的時候,類加載會隱式加載它的父類,例如,Child 繼承 Parent,Class.forName("Child") 的時候,會加載 Parent
例子2:執(zhí)行一個類之前,類加載會隱式加載它全部的依賴類(全盤責(zé)任機(jī)制),例如,執(zhí)行 WordCount 的 main 方法之前,JVM 會自動加載 FlatMapFunction、...、WordCountData 等依賴類,也會自動加載 FlatMapFunction 的 Public、Collector、Function 等依賴類
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.examples.wordcount.util.WordCountData;
public class WordCount {
public static void main(String[] args) throws Exception {
// 忽略
}
}
import org.apache.flink.annotation.Public;
import org.apache.flink.util.Collector;
import org.apache.flink.api.common.functions.Function;
public interface FlatMapFunction<T, O> extends Function, Serializable {
// 忽略
}
全盤責(zé)任機(jī)制:
當(dāng)一個 ClassLoader 裝載一個類時,除非顯式另一個 ClassLoader,該類所依賴的類也由這個 ClassLoader 負(fù)責(zé)加載。
② 顯示加載,通過反射 Class.forName() 等方法,把類顯式地加載到 JVM 中;
this.getclass().getClassLoader().loadClass();
Class.forName("WordCount");
1.2 類加載器的模型及其類型
類加載器采用雙親委派模型, 其類型有 4 種:
① Bootstrap ClassLoader 啟動類加載器:負(fù)責(zé)加載系統(tǒng)類和 /lib 目錄的 jar 和類,例如 String
② ExtClassLoader 擴(kuò)展類加載器:負(fù)責(zé)加載 /lib/ext 目錄下的 jar 和類
③ AppClassLoader 應(yīng)用程序類加載器:負(fù)責(zé)加載當(dāng)前應(yīng)用 ClassPath 的 jar 和類
④ UserDefinedClassLoader 用戶自定義加載器:負(fù)責(zé)加載用戶自定義的 jar 和類
2.3 類加載的過程
系統(tǒng)加載 Class 類型的文件主要三步:加載->連接->初始化。連接過程又可分為三步:驗證->準(zhǔn)備->解析

2.JVM 為什么采用雙親委派模型
雙親委派模型是 Java 類加載器的工作機(jī)制。
(1)雙親委派模型的工作原理
雙親委派模型是指如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執(zhí)行,如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請求最終將到達(dá)頂層的啟動類加載器,如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載。
注意:雙親委派模式中的父子關(guān)系并非通常所說的類繼承關(guān)系,而是采用組合關(guān)系來復(fù)用父類加載器。

雙親委派模型的關(guān)鍵代碼
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;
}
}

(2)采用雙親委派機(jī)制的優(yōu)點
① 避免重復(fù)加載類。注意:相同的類文件,被不同類加載器加載產(chǎn)出的是兩個不同的類。
② 避免 Java 核心 API 被篡改,保證程序穩(wěn)定運(yùn)行。用戶自定義編寫 java.lang.Object 類,如果沒有雙親委派模型,每個類加載器加載各自的類,導(dǎo)致系統(tǒng)出現(xiàn)多個不同的 Object 類。
(3)如何實現(xiàn)熱加載類
參考 Flink 的 child-first 類加載機(jī)制
public final class ChildFirstClassLoader extends FlinkUserCodeClassLoader {
// 省略構(gòu)造函數(shù)等
@Override
protected synchronized Class<?> loadClassWithoutExceptionHandling(
String name,
boolean resolve) throws ClassNotFoundException {
// 首先, 檢查該類是否被加載
Class<?> c = findLoadedClass(name);
if (c == null) {
// 省略...
try {
// 根據(jù) URL 選擇類
c = findClass(name);
} catch (ClassNotFoundException e) {
// let URLClassLoader do it, which will eventually call the parent
c = super.loadClassWithoutExceptionHandling(name, resolve);
}
}
// 省略...
}
// 重寫獲取資源路徑, 避免使用父加載器的 getResource 方法
@Override
public URL getResource(String name) {
// 使用 URLClassloader 的 getResource
URL urlClassLoaderResource = findResource(name);
if (urlClassLoaderResource != null) {
return urlClassLoaderResource;
}
// delegate to super
return super.getResource(name);
}
// 重寫獲取資源路徑, 避免使用父加載器的 getResources 方法
@Override
public Enumeration<URL> getResources(String name) throws IOException {
// f使用 URLClassloader 的 getResources
Enumeration<URL> urlClassLoaderResources = findResources(name);
final List<URL> result = new ArrayList<>();
while (urlClassLoaderResources.hasMoreElements()) {
result.add(urlClassLoaderResources.nextElement());
}
// 省略...
}
}
ChildFirstClassLoader 的核心代碼是 loadClassWithoutExceptionHandling 方法,沒有采用父加載器 findClass(避免采用雙親委派模型),而是采用自定義加載器 URLClassLoader 的findClass,同時重寫 getResource 方法,即使用 URLClassLoader 獲取資源的 URL。
3.如何判斷一個類使無用的類
“無用的類”需要滿足一下條件:
① 該類所有的實例都已經(jīng)被回收,即 JVM Heap 里不存在該類的任何實例;
② 該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法;
③ 加載該類的 ClassLoader 已經(jīng)被回收。
注意:滿足上述條件,“可以”對該對象進(jìn)行回收,而不是“馬上”、“必然”回收。