자바 기술 업계에서 가장 신비 클래스 로더는, 철저하게이 일을보고 충분히 이해

클래스 로더는 많은 사람들이, 문간입니다 알아낼 수없는 두통 된 Java 기술 세션의 가장 신비 중 하나입니다. 이 논문에서 나는 독자가 완전히, 완전히 클래스 로더 이해 기타 관련 기사 후에 당신이 볼 필요가 없습니다 수 있습니다 걸릴.

클래스 로더 무엇을 할까?

이름에서 알 수 있듯이,이 클래스를로드하는 데 사용됩니다. 이 클래스 Class 객체의 형태로 메모리에 바이트 코드를 변환 할 책임이 있습니다. 바이트 코드도 *의 .class 항아리 백 될 수 *의 .class 디스크 파일에서 올 수있는 원격 서버로부터 바이트 스트림이 될 수 있으며, 바이트 코드의 특성이 바이트 배열은 [] 바이트, 그것을 보유 특정 복잡한 내부 포맷.


많은 바이트 코드 암호화 기술은 달성하기 위해 사용자 정의 클래스 로더에 의존하는 것입니다 수 있습니다. 를 해독하는 바이트 코드를로드하기 전에 파일 내용을 해독 사용자 런타임 클래스 로더를 사용하여 암호화 된 파일 바이트 코드 도구를 처음 사용. 각 클래스 객체 내에서 클래스 로더가로드 무엇으로 자신을 식별 할 수있는 클래스 로더 필드가 있습니다. 클래스 로더는 클래스의 많은 이미로드 된 객체 설치된 컨테이너, 같다.

class Class {
...
private final ClassLoader classLoader;
...}
复制代码

게으른 로딩

JVM 런타임 부하가 게으른로드 수요에로드 된 모든 클래스에 필요하지 않습니다. 점차 많은 새로운 클래스가 작동하는 과정에서 모르는 발생합니다이 프로그램은, 이번에는 그 클래스를로드하는 클래스 로더를 호출합니다. 클래스 로더는 내부의 존재가 클래스 객체 후, 당신이 다음 번에 다시로드하지 않아도됩니다로드.

당신은 클래스의 정적 메서드를 호출 할 때 예를 들어, 첫 번째 클래스는 확실히로드해야하지만, 클래스의 인스턴스 필드를 터치하지 않고, 클래스 클래스의 인스턴스 필드 일시적으로 부하가없는 수 있지만, 그것은있을 수 있습니다 정적 메소드는 정적 필드를 액세스 할 수 있기 때문에 정적 필드를 관련 카테고리를로드. 클래스 인스턴스 필드는로드 될 가능성이 때 객체를 인스턴스화 할 때까지 기다릴 필요가있다.

그들의 임무

클래스 로더는 다른 장소에서 여러 JVM 인스턴스, 다른 클래스 로더를로드 바이트 코드 파일을 실행합니다. 그것은 또한 다른 jar 파일에서로드 할 수있는 다른 파일 디렉토리에서로드 할 수 있습니다, 바이트 코드는 인터넷 다른 정적 파일 서버를 다시로드에서 다운로드 할 수 있습니다.

JVM은 세 가지 중요한 클래스 로더, 각각 BootstrapClassLoader, ExtensionClassLoader 및 AppClassLoader을 만들었습니다.

$ JAVA_HOME / lib 디렉토리 /의 rt.jar 파일에 위치하는 JVM 런타임 코어 클래스를로드에 대한 책임 BootstrapClassLoader, 우리는 도서관 java.xxx에 내장 사용한. * 같은 java.util의 클래스로, 내부에 있습니다. ,는 java.io , java.nio의를 . , 인 java.lang. 등등. 클래스 로더는,이 C 코드에서 오히려 특별한 구현, 우리는 "루트 로더."호출

같은 라이브러리 이름은 일반적으로 자신의 병 패키지가 $ JAVA_HOME / lib 디렉토리 / 내선 / *. 항아리에 위치하고 있으며, javax의 시작 등 스윙 시리즈, 내장 JS 엔진, XML 파서, 같은 JVM 확장 클래스를로드하는 ExtensionClassLoader 책임, 많은 항아리가있다 패키지.

AppClassLoader 직접 부하 항아리 패키지와 디렉토리 경로 클래스 경로 환경 변수에 정의 된 사용자들 로더 대상으로합니다. 이 코드는 우리가 작성한과로드에 의해 타사 항아리 패키지는 일반적으로 사용됩니다.

정적 파일을 제공하기 위해 네트워크 서버에있는 항아리 패키지와 클래스 파일이, JDK가 URLClassLoader를 내장, 사용자는 생성자에 표준화 된 네트워크 경로를 전달해야, URLClassLoader의 원격 라이브러리를로드하는 데 사용할 수 있습니다. 원격으로 만 URLClassLoader의 라이브러리를로드 할 수 없음, 라이브러리는 또한 서로 다른 주소 생성자의 형태에 따라 로컬 경로를로드 할 수 있습니다. ExtensionClassLoader 및 AppClassLoader는 로컬 파일 시스템 라이브러리에서로드 URLClassLoader의 서브 클래스입니다.

AppClassLoader이 클래스 로더 클래스의 정적 방법 getSystemClassLoader에 의해 제공 될 수있다 () 우리는 "시스템 클래스 로더"라고 부릅니다하는 얻을, 우리는 일반적으로 쓰기 사용자 클래스 코드는 보통에 의해로드됩니다. 경우 제 1 사용자 클래스 로더 실행 우리의 주요 방법은 AppClassLoader이다.

클래스 로더 이적

운전 중 프로그램이 알 수없는 클래스가 발생, 그것이 어떻게로드 할 클래스 로더 선택할 것인가? 전략 가상 머신은 이전에 알려지지 않은 클래스의로드를 호출 측의 클래스 로더 클래스 객체를 사용하는 것입니다. 발신자 Class 객체는 무엇인가? 즉,이 알 수없는 클래스의 얼굴에, 반드시 가상 머신이 위의 메서드 호출 (정적 메서드 또는 인스턴스 메서드),이 방법이 걸려를 실행 할 수있는 클래스에서,이 클래스가 호출자 클래스 개체입니다. 우리는 누구에 의해로드 된 현재 클래스의 속성 클래스 로더 기록을 가지고 이전의 각 클래스의 객체를 언급했다.

클래스 로더의 전송, 지연로드 된 모든 클래스는 모든이에 대한 책임 클래스 로더의 초기 주요 방법으로 호출됩니다 때문에 AppClassLoader입니다.

위임 부모

우리는이 "우리가 자주 말하는 것입니다 그들은, 시스템 라이브러리의 AppClassLoader의로드 조작을 할 BootstrapClassLoader 및 ExtensionClassLoader에 인계해야하는 방법을 도서관 시스템을로드하지 않은 경우에만 다음 클래스 경로 클래스 라이브러리를로드에 대한 책임 AppClassLoader 언급 부모는 위임합니다. "

                        

알 수없는 클래스 이름을로드 할 때 AppClassLoader, 그것은 바로 클래스 경로를 검색하지 않도록, 그것은 ExtensionClassLoader로드 할 수있는 경우, 다음 AppClassLoader 문제가없는 것, 부하에 ExtensionClassLoader에 첫 번째 클래스의 이름이됩니다. 그렇지 않으면, 클래스 경로를 검색합니다.

알 수없는 클래스 이름을로드 할 때 ExtensionClassLoader, 그것은 바로 내선 경로를 검색 할 것이 아니라, 먼저 BootstrapClassLoader로드 할 수있는 경우, 다음 ExtensionClassLoader 문제가되지 않습니다, 부하에 BootstrapClassLoader 클래스 이름에 인계됩니다. 그렇지 않으면, 항아리 패키지 내선 경로에서 검색합니다.

세 ClassLoader와의 사이에 부모 - 자식 관계의 폭포를 형성, 클래스 로더는 각각의 게으른 아버지에 작업하려고, 그의 아버지는 자신이 건조하지 않을 것입니다. 객체 내부의 각 클래스 로더는 부모 로더에 점을 그 부모의 속성을 갖게됩니다.

클래스 클래스 로더 {

...

민간 최종 클래스 로더의 부모;

...

}

그것은 그것의 값이 null 부모이기 때문에 부모 필드가 null의 경우, 그것은 부모 로더는 말한다, 점선 그림 그 부모 포인터 ExtensionClassLoader 그림 주목할 만하다 "로더의 루트." 속성 classLoader가 Class 객체의 값이 null 인 경우, 클래스가 "루트 로더"로드 것을 의미한다. 부모가 단지 클래스 로더 필드 내에서, 부모 클래스는 슈퍼하지 않음을 유의하십시오.

Class.forName을

우리는 종종 Class.forName을 방법을 사용하여, JDBC 드라이버를 사용하면 동적으로 드라이버 클래스를로드합니다.

Class.forName("com.mysql.cj.jdbc.Driver");复制代码

원리는 MySQL을 구동 드라이버 클래스 드라이버 클래스가로드 될 때, 그것을 실행하는 코드의 정적 블록을 가지고있다. MySQL 인스턴스를 구동 할이 정적 블록은 글로벌 JDBC 드라이버 관리자로 등록됩니다.

class Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
...
}复制代码

forName을의 방법은 대상 클래스를로드 할 호출자의 클래스 로더 클래스 객체를 사용하고 있습니다. 그러나 forName을 또한 다중 매개 변수 버전을 제공합니다, 당신은 클래스 로더는 부하에 사용할 지정할 수 있습니다

클래스의 forName (문자열 이름, 부울 초기화, 클래스 로더의 CL)

그것은 내장의 forName 방법이 양식에 의해 로더,로드 라이브러리에 사용자 정의 클래스 로더를 사용하여 우리가 다른 소스를 확보 할 수 있도록 탈옥 수 있습니다. 클래스 로더는 다른 참조 라이브러리에 전달 된 대상 라이브러리의 전송은 사용자 정의 로더로드를 사용합니다.

사용자 정의 로더

세 가지 중요한 클래스 로더의 loadClass 메소드 (), (때문에) findClass ()와 defineClass는 ()가있다.

로 loadClass () 메소드는 대상 클래스를로드 할 수있는 항목은, 먼저 현재의 클래스 로더를 발견하고 부모가 부모가로드되지 않은 경우, 그것은 (때문에) findClass을 (호출,로드하려고 할 것이다 발견되지 않는 경우는 그 부모는 이미 대상 클래스 내부에로드 ) 대상 클래스를로드하기 위해 자신의 사용자 정의 로더를합니다. (때문에) findClass () 메소드의 클래스 로더는 서브 클래스를 커버하는 데 필요한, 다른로드 대상 클래스 바이트 코드를 검색하기 위해 다른 로직을 사용한다. 그런 다음 바이트 코드 클래스 객체로 바이트 코드를 변환하는 다음과 같은 방법을 얻을 수있는 defineClass는 ()를 호출합니다. 지금은 기본 과정에 대해 의사 코드 표현을 사용

class ClassLoader {
// 加载入口,定义了双亲委派规则
Class loadClass(String name) {
// 是否已经加载了
Class t = this.findFromLoaded(name);
if(t == null) {
// 交给双亲
t = this.parent.loadClass(name)
}
if(t == null) {
// 双亲都不行,只能靠自己了
t = this.findClass(name);
}
return t;
}
// 交给子类自己去实现
Class findClass(String name) {
throw ClassNotFoundException();
}
// 组装Class对象
Class defineClass(byte[] code, String name) {
return buildClassFromCode(code, name);
}
}
class CustomClassLoader extends ClassLoader {
Class findClass(String name) {
// 寻找字节码
byte[] code = findCodeFromSomewhere(name);
// 组装Class对象
return this.defineClass(code, name);
}
}复制代码
사용자 정의 클래스 로더는 쉽게 loadClass 메소드를 포함하지 않는 부모 위임 규칙을 파괴하는 것은 쉽지 않다. 이렇게하면 로더는 사용자 정의 내장 된 코어 라이브러리를로드 할 수 없습니다 원인이 될 수 있습니다. 부모 로더를 취소하는 것이 좋다 사용자 지정 로더를 사용하는 경우 수신의 하위 클래스를 구성하여 부모 로더입니다. 부모 클래스 로더가 null의 경우, 부모 부하가 의미 "로더의 루트."

// ClassLoader 构造器
protected ClassLoader(String name, ClassLoader parent);复制代码

부모의 위임 규칙이 될 수 있습니다 세 프로 임명 네 프로 임명 당신이 부모 로더를 사용하는 사람, 그것이 재귀 로더 루트에 위임 된 것에 따라.

ClassLoader.loadClass 대 Class.forName을

두 가지 방법이 대상 클래스를로드 할 수 있으며, Class.forName을 () 메소드는 클래스의 원시 형식을 가져옵니다이지만, ClassLoader.loadClass ()는 오류가 발생하지 그들 사이에 약간의 차이가있다.

Class x = Class.forName("[I");
System.out.println(x);
x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);
---------------------
class [I
Exception in thread "main" java.lang.ClassNotFoundException: [I
...复制代码

다이아몬드 의존

잘 알려진 개념은 프로젝트 관리의 "다이아몬드가 달려있다"가 소리 쳤다, 그것은 충돌보다는 공존 동일한 패키지 필요의 두 가지 버전으로 리드를 의존하는 소프트웨어를 말한다.


우리는 일반적으로 서로 다른 버전과의 호환성이 아주 나쁜 경우 다이아몬드 의존, 그 다음 프로그램을 컴파일 실행되지 않습니다, 사용에 충돌의 여러 버전을 선택할 것이다 받는다는 사용하여 해결된다. 메이븐이 양식은 "평면"의존성 관리라고합니다. 클래스 로더는 다이아몬드 의존도를 사용하여 해결할 수 있습니다. 소프트웨어 패키지의 다른 버전은, 부하에 다른 클래스 로더를 사용하여 다른 클래스의 이름은 실제로 같은 클래스에서 다른 클래스 로더에 위치해 있습니다 . 우리는 간단한 예제를 시도 URLClassLoader의를 사용하자, 그것은 기본 부모 로더 AppClassLoader입니다

$ cat ~/source/jcl/v1/Dep.java
public class Dep {
public void print() {
System.out.println("v1");
}
}
$ cat ~/source/jcl/v2/Dep.java
public class Dep {
public void print() {
System.out.println("v1");
}
}
$ cat ~/source/jcl/Test.java
public class Test {
public static void main(String[] args) throws Exception {
String v1dir = "file:///Users/qianwp/source/jcl/v1/";
String v2dir = "file:///Users/qianwp/source/jcl/v2/";
URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)});
URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)});
Class depv1Class = v1.loadClass("Dep");
Object depv1 = depv1Class.getConstructor().newInstance();
depv1Class.getMethod("print").invoke(depv1);
Class depv2Class = v2.loadClass("Dep");
Object depv2 = depv2Class.getConstructor().newInstance();
depv2Class.getMethod("print").invoke(depv2);
System.out.println(depv1Class.equals(depv2Class));
}
}复制代码
작동하기 전에, 우리는 컴파일 된 라이브러리에 의존해야

$ cd ~/source/jcl/v1
$ javac Dep.java
$ cd ~/source/jcl/v2
$ javac Dep.java
$ cd ~/source/jcl
$ javac Test.java
$ java Test
v1
v2
false复制代码

경로 URLClassLoader의 두 지점이 동일한 경우 다른 로딩 클래스 로더 클래스와도 같은 코드 바이트 밖으로 같은 클래스로 간주 될 수 없기 때문에, 다음과 같은 표현은,이 경우 거짓

depv1Class.equals (depv2Class)

우리는 또한 같은 출발지 클래스 구현의 두 가지 다른 버전의 인터페이스, 메소드 내부 출발지 클래스를 호출 반사를 사용하지 않도록하는 방법을 만들 수 있습니다.

Class depv1Class = v1.loadClass("Dep");
IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance();
depv1.print()复制代码

클래스 로더는 충돌을 의존 해결할 수 있지만, 그것은 또한 다른 패키지 반드시 사용 반사 또는 동적 호출 인터페이스의 사용자 인터페이스를 제한합니다. 메이븐은 사용자 정의 클래스 로더를 사용하지 않는 경우는 클래스가로드 된 다른 클래스 로더를 사용해야하기 때문에 AppClassLoader, 같은 이름의 다른 버전을 사용하므로 처음부터 끝까지, 작동 중에, 가상 머신의 기본 게으른 로딩 전략에 의존하고, 그러한 제한이 없다 그래서 Maven은 다이아몬드 완벽한 솔루션을 의존 할 수 없습니다. 당신은 오픈 소스 패키지 관리 도구 다이아몬드 의존, 난 당신이 분리 프레임과 같은 오픈 소스 경량 개미 골드 드레스있는 소파 방주,보고 추천 해결할 수 있는지 여부를 알고 싶은 경우.

노동과 협력 부문

여기에 우리가 클래스의 네임 스페이스에 해당 클래스 로더의 중요성에 대한 새로운 이해를 가지고, 클래스는 분리의 역할을했다. 같은 클래스 로더 클래스 이름이 고유 내부에 위치하고 있으며, 다른 클래스 로더는 같은 이름의 클래스를 보유 할 수 있습니다. 클래스 로더는 컨테이너 클래스 이름이 샌드 박스의 클래스입니다.


그들 사이의 협력이 완료 될 때까지 부모의 재산과 부모 메커니즘에 의해 임명되고, 다른 클래스 로더의 협력이있을 것입니다. 부모는 더 높은 부하의 우선 순위를 가지고있다. 또한, 부모는 또한 공유 관계, 복수의 서브 클래스 로더 공유 같은 부모, 모든 클래스를 들어 부모가 고려 될 수 하위 클래스 로더 공유를 나타냈다. 이 BootstrapClassLoader 모든 클래스 로더 로더의 조상으로 간주하는 이유, JVM의 핵심 라이브러리가 자연스럽게 공유 할 수 있어야한다. Thread.contextClassLoader

조금 스레드에게 소스 코드를 읽는다면, 당신은 매우 특별한 필드가 그 분야에서 예를 찾을 수

class Thread {
...
private ClassLoader contextClassLoader;
public ClassLoader getContextClassLoader() {
return contextClassLoader;
}
public void setContextClassLoader(ClassLoader cl) {
this.contextClassLoader = cl;
}
...
}复制代码

정확히 무엇 contextClassLoader "쓰레드 컨텍스트 클래스 로더"?

당신이 표시를 사용하지 않을 경우 먼저 contextClassLoader 필요가 어디를 사용하지 않습니다, 클래스 로더에 의해 사용되는 종류를 표시합니다. 당신은 그것을 보여주기 위해이 방법을 사용하려면 다음을 사용할 수 있습니다

.는 Thread.currentThread () getContextClassLoader ()로 loadClass (이름).;

대상 클래스를로드하기 위해 forName을 (캐릭터 이름) 방법을 사용하는 경우이 방법은 자동 contextClassLoader를 사용하지 않습니다. 코드 지연로드에 대한 종속성이 자동으로 사용되지 않도록 그 클래스는 contextClassLoader을로드합니다.

두 번째 스레드 contextClassLoader 기본이 저기 부모 스레드에서 상속, 소위 부모 thread가 현재의 thread를 생성하는 스레드입니다. 프로그램이 시작되면 contextClassLoader는 AppClassLoader 메인 스레드입니다. 설정할 수있는 매뉴얼이없는 경우이 방법은 다음 contextClassLoader 모든 스레드 AppClassLoader 있습니다.

어떤이 contextClassLoader두고 할까? 우리는 그 목적을 설명하기 위해 클래스 로더 사이의 노동과 협력의 분단의 이전에 언급 한 원리를 사용하고 싶습니다.

그것은만큼 그들은 같은 contextClassLoader을 공유하는 스레드를 공유 클래스를 통해 수행 할 수 있습니다. ContextClassLoader는 자동으로 자동화됩니다 공유, 아버지와 아들 스레드 사이에 전달.

다른 스레드가 다른 contextClassLoader를 사용하는 경우, 그래서 다른 스레드는 분리 클래스에 사용할 수 있습니다.

우리가 사업을 분할하는 경우, 스레드 풀을 사용하여 다양한 비즈니스가, 내부 스레드 풀은 다른 contextClassLoader 같은 contextClassLoader, 스레드 풀 사이에 공유, 그들은 피할 클래스 버전 충돌에 격리 보호에 좋은 역할을 할 수있다.

우리가 contextClassLoader을 사용자 정의하지 않으면 모든 스레드가 사용 AppClassLoader의 기본값이됩니다, 모든 클래스가 공유됩니다.

위의 논리 모호한 필요에 대한 걱정도되지 않을 경우, contextClassLoader 드문 경우 스레드.

JDK9는 수정 어느 정도의 작업을 수행 할 구조 설계 모듈 기능 클래스 로더를 증가하지만, 후 분리와 같은 역할뿐만 아니라 메커니즘을 위임하는 부모에 의존 할 필요가 선박의 종류, 같은 클래스 로더 또는 유사의 원리, 다른 클래스 로더 간의 협력을 설정합니다.

위 정정 해줘 환영을 공유 할 수있는 내 자신의 생각의 일부입니다, 그런데는 관심의 물결을 찾을 수


저자 : 자바 마이크로 서비스
링크 : HTTPS : //www.jianshu.com/p/14f196afeeef
제인 책 : 소스


추천

출처juejin.im/post/5e4ccf296fb9a07c8b5bab9c