Java 클래스 로드 전 과정 깊이 이해

8396 단어 Java클래스 로드
Java 클래스 로드 전 과정
자바 파일이 불러오기부터 제거되기까지 총 4단계를 거쳐야 합니다.
로드 -> 링크 (검증 + 준비 + 확인) -> 초기화 (사용 전 준비) -> 사용 -> 마운트 해제
그 중에서 불러오기(사용자 정의 불러오기 제외) + 링크의 과정은 완전히 jvm가 책임지고 클래스를 초기화하는 작업(불러오기 + 링크는 그 전에 이미 완성), jvm는 엄격한 규정(4가지 상황)이 있습니다.
1. new, getstatic,putstatic,invokestatic 네 바이트 코드 명령을 만났을 때 클래스를 초기화하지 않으면 바로 초기화 작업을 합니다.사실은 세 가지 상황이다. new로 하나의 클래스를 실례화할 때, 클래스의 정적 필드를 읽거나 설정할 때, (final에 의해 수식된 정적 필드를 포함하지 않는다. 왜냐하면 그들은 이미 상량 탱크에 들어갔기 때문이다), 그리고 정적 방법을 실행할 때.
2. 자바를 사용한다.lang.reflect.*클래스에 대한 반사 호출을 할 때 클래스가 초기화되지 않았다면 바로 클래스를 진행합니다.
3. 한 종류를 초기화할 때 아버지가 초기화되지 않으면 먼저 아버지를 초기화한다.
4. jvm가 시작될 때 사용자가 실행할 메인 클래스 (staticvoidmain (String []args) 를 포함하는 클래스) 를 지정해야 하면 jvm가 먼저 이 클래스를 초기화합니다.
상기 4가지 예처리는 한 클래스에 대한 주동적인 인용이라고 하고 나머지 다른 상황은 수동적인 인용이라고 하며 클래스의 초기화를 촉발하지 않는다.다음은 수동 참조의 예입니다.

/** 
 *  1 
 *  ,  
 * @author volador 
 * 
 */ 
class SuperClass{ 
  static{ 
    System.out.println("super class init."); 
  } 
  public static int value=123; 
} 
 
class SubClass extends SuperClass{ 
  static{ 
    System.out.println("sub class init."); 
  } 
} 
 
public class test{ 
  public static void main(String[]args){ 
    System.out.println(SubClass.value); 
  } 
   
} 
출력 결과: 슈퍼 클래스 init.

/** 
 *  2 
 *  ,  
 * @author volador 
 * 
 */ 
public class test{ 
  public static void main(String[] args){ 
    SuperClass s_list=new SuperClass[10]; 
  } 
} 
출력 결과: 출력 없음

/** 
 *  3 
 *  , ,  
 * @author root 
 * 
 */ 
class ConstClass{ 
  static{ 
    System.out.println("ConstClass init."); 
  } 
  public final static String value="hello"; 
} 
 
public class test{ 
  public static void main(String[] args){ 
    System.out.println(ConstClass.value); 
  } 
} 
출력 결과:hello(tip:컴파일할 때,ConstClass.value는hello 상수로 바뀌어test류의 상수 탱크에 넣었습니다)
이상은 클래스의 초기화입니다. 인터페이스도 초기화해야 합니다. 인터페이스의 초기화는 클래스의 초기화와 약간 다릅니다.
위의 코드는 모두 static {}로 초기화 정보를 출력합니다. 인터페이스는 할 수 없지만 인터페이스가 초기화될 때 컴파일러는 인터페이스에 ()의 클래스 구조기를 생성하여 인터페이스의 구성원 변수를 초기화하는 데 사용됩니다. 이 점은 클래스의 초기화에도 있습니다.진정으로 다른 점은 세 번째이다. 클래스의 초기화가 실행되기 전에 부류가 모두 초기화되도록 요구하지만 인터페이스의 초기화는 부인터페이스의 초기화에 별로 걸리지 않는 것 같다. 즉, 부인터페이스가 초기화될 때 부인터페이스도 초기화되도록 요구하지 않고 부인터페이스를 실제로 사용할 때만 초기화된다(예를 들어 인터페이스의 상량을 인용할 때).
다음 클래스의 불러오는 전 과정을 분해합니다: 불러오기->검증->준비->해석->초기화
먼저 로드:
이 가상 시스템은 다음과 같은 3가지 작업을 수행합니다.
        1.클래스의 모든 한정된 이름을 통해 이러한 바이트 흐름을 정의합니다.
        2.이 바이트 흐름을 대표하는 정적 저장 구조를 방법 구역의 운행 시 데이터 구조로 전환합니다.
        3.자바 더미에서 이 종류를 대표하는 자바를 생성합니다.이 데이터에 대한 접근 입구로 lang.Class 대상입니다.
첫 번째는 매우 유연하다. 많은 기술들이 이곳에서 삽입된다. 왜냐하면 이진 흐름이 어디에서 오는지 한정하지 않기 때문이다.
class 파일에서 -> 일반 파일 불러오기
zip 패키지에서 ->jar의 클래스 불러오기
네트워크에서 -> Applet
..........
탑재 과정의 다른 몇 가지 단계에 비해 탑재 단계의 제어성이 가장 강하다. 왜냐하면 유형의 탑재기는 시스템적으로도 쓸 수 있고 스스로 쓸 수도 있기 때문이다. 프로그램원은 자신의 방식으로 탑재기를 써서 바이트 흐름의 획득을 제어할 수 있기 때문이다.
2진 흐름을 가져오면 jvm에 필요한 방식으로 방법 구역에 저장되며, 자바 더미에 자바를 실례화합니다.lang. Class 대상은 무더기의 데이터와 연결됩니다.
로드가 완료되면 바이트 흐름을 검사하기 시작합니다. (사실은 파일 형식 검증과 같은 여러 절차가 교차되어 진행됩니다.)
검사의 목적:class 파일의 바이트 흐름 정보가 jvm의 입맛에 부합되고 jvm가 불편하지 않도록 확보합니다.만약class 파일이 순수한java 코드로 컴파일된다면 수조가 경계를 넘어 존재하지 않는 코드 블록으로 이동하는 것과 같은 불건전한 문제가 발생하지 않을 것이다. 왜냐하면 이런 현상이 발생하면 컴파일러가 컴파일을 거절하기 때문이다.그러나 앞서 말한 바와 같이 Class 파일 흐름은 반드시 자바 원본에서 컴파일된 것이 아니라 인터넷이나 다른 곳에서 왔을 수도 있다. 심지어 16진법으로 작성할 수도 있다. 만약에 jvm가 이 데이터를 검사하지 않는다면 유해한 바이트 흐름이 jvm를 완전히 붕괴시킬 수도 있다.
검증은 주로 몇 가지 절차를 거친다. 파일 형식 검증 -> 메타데이터 검증 -> 바이트 코드 검증 -> 기호 인용 검증
파일 형식 검증: 바이트 흐름이 Class 파일 형식의 규범에 부합되는지 확인하고 현재 jvm 버전에서 처리될 수 있는지 검증합니다.ok 문제가 없으면 바이트 흐름은 메모리의 방법 구역에 들어가서 저장할 수 있습니다.뒤의 세 가지 검사는 모두 방법 구역에서 진행되었다.
메타데이터 검증: 바이트 코드 묘사 정보에 대해 의미화 분석을 하고 그 묘사 내용이 자바 언어의 문법 규범에 부합하도록 확보한다.
바이트 코드 검사: 가장 복잡하고 상대방 법체의 내용을 검사하여 운행할 때 격식에 어긋나는 일을 하지 않도록 보증한다.
기호 인용 검증: 일부 인용의 진실성과 타당성을 검증한다. 예를 들어 코드에서 다른 종류를 인용했다. 여기서 그것이 도대체 존재하는지 확인해야 한다.또는 코드에서 다른 종류의 일부 속성에 접근했다면, 여기서 그 속성에 접근할 수 있는 줄을 검증했다.(이 단계는 뒤의 해석 작업에 기반을 다질 것이다)
검증 단계는 매우 중요하지만 필요한 것도 아니다. 만약에 일부 코드가 반복적으로 사용되고 신뢰성을 검증했다면 실시 단계는 - Xverify:none 매개 변수로 대부분의 클래스 검증 조치를 닫고 클래스 불러오는 시간을 짧게 할 수 있다.
이어서 위의 절차를 완성하면 준비 단계에 들어갈 것이다.
이 단계는 클래스 변수(정적 변수를 가리키는 말)에 메모리를 분배하고 클래스의 초기 값을 설정하는 단계로 이 안에 존재하는 방법 구역에서 분배됩니다.여기서 설명하자면, 이 단계는 정적 변수에 초기 값만 설정하고, 그 실례 변수는 실례화 대상에서 분배된다.여기에 주어진 클래스 변수의 초기 값은 클래스 변수의 부여 값과 약간 다르다. 예를 들어 다음과 같다.

public static int value=123;
이 단계에서value의 값은 123이 아니라 0이 될 것입니다. 이때는 자바 코드를 실행하기 시작하지 않았기 때문에 123은 보이지 않습니다. 123을 value에 부여하는putstatic 명령은 프로그램이 컴파일된 후에 () 에 존재하기 때문에value에 123을 부여하는 것은 초기화할 때 실행됩니다.
여기에도 예외가 있다.

public static final int value=123;
여기 준비 단계에서value의 값은 123으로 초기화됩니다.이것은 컴파일러에서javac는 이 특수한value를 위해ConstantValue 속성을 생성하고 준비 단계에서 jm는 이ConstantValue의 값에 따라value에 값을 부여한다는 것이다.
상단을 완성한 후에 분석을 진행해야 한다.해석은 클래스의 필드, 방법 등을 바꾸는 것 같지만 클래스 파일의 형식 내용과 구체적으로 관련되어 깊이 이해하지 못했다.
초기화 프로세스는 클래스 로딩 프로세스의 마지막 단계입니다.
앞의 클래스 마운트 과정에서 마운트 단계에서 사용자가 사용자 정의 클래스 마운트를 통해 참여할 수 있는 것을 제외하고 다른 동작은 jvm가 주도하고 이 부분을 초기화해야 자바 안의 코드를 진정으로 실행하기 시작한다.
이 단계는 일부 사전 조작을 실행할 것입니다. 준비 단계를 구분하고 클래스 변수에 대해 시스템에 값을 부여했습니다.
사실 말하자면, 이 단계는 실행 프로그램의 () 이다.방법의 과정.다음은 () 방법을 연구해 보겠습니다.
() 방법은 클래스 구조기 방법이라고 하는데 컴파일러 자동 핸드폰 클래스의 모든 클래스 변수의 값 부여 동작과 정적 문장 블록의 문장을 합쳐서 만든 것으로 그들의 순서는 원본 파일에 배열된 것과 같다.
();방법은 클래스 구조 방법과 다르다. 그는 부모 클래스를 호출하는 ()를 표시할 필요가 없다.방법, 가상 기회 보증 하위 클래스의 ();방법은 실행 전 부류의 이 방법이 이미 실행되었다. 즉, 가상 기기에서 처음으로 실행된 ().방법은 틀림없이 자바일 거야.lang.Object류의.
다음은 예를 들어 설명합니다.

static class Parent{ 
  public static int A=1; 
  static{ 
    A=2; 
  } 
} 
static class Sub extends Parent{ 
  public static int B=A; 
} 
public static void main(String[] args){ 
  System.out.println(Sub.B); 
} 
우선 Sub.B에서 정적 데이터를 참조하고 Sub 클래스를 초기화합니다.또한 상위 Parent는 초기화 작업을 먼저 수행합니다.Parent 초기화 후, A=2, 그래서 B=2;이전 절차는 다음과 같습니다.

static class Parent{ 
  <clinit>(){ 
    public static int A=1; 
    static{ 
      A=2; 
    } 
  } 
} 
static class Sub extends Parent{ 
  <clinit>(){ //jvm  
  public static int B=A; 
  } 
} 
public static void main(String[] args){ 
  System.out.println(Sub.B); 
} 
();방법은 클래스와 인터페이스에 필수적이지 않습니다. 클래스나 인터페이스에 클래스 변수에 값을 부여하지 않고 정적 코드 블록이 없으면 () 방법은 컴파일러에 생성되지 않습니다.
인터페이스 안에 static {} 이런 정적 코드 블록이 존재할 수 없지만 변수가 초기화될 때의 변수 부여 작업이 존재할 수 있기 때문에 인터페이스 안에도 () 구조기가 생성됩니다.그러나 클래스와 다른 것은 하위 인터페이스를 실행하는 ();방법 전에 부모 인터페이스의 () 를 실행할 필요가 없습니다.방법, 부모 인터페이스에 정의된 변수가 사용되면 부모 인터페이스가 초기화됩니다.
또한 인터페이스의 실현 클래스는 초기화할 때도 인터페이스의 ()를 실행하지 않습니다.방법
또한 jvm는 하나의 종류의 ()를 보장합니다.방법은 다중 스레드 환경에서 동기화를 정확하게 잠글 수 있습니다.<초기화는 한 번만 수행되므로 >
다음은 예를 들어 설명합니다.

public class DeadLoopClass { 
 
  static{ 
    if(true){ 
    System.out.println("  ["+Thread.currentThread()+"]  , "); 
    while(treu){}   
    } 
  } 
   
  /** 
   * @param args 
   */ 
  public static void main(String[] args) { 
    // TODO Auto-generated method stub 
    System.out.println("toplaile"); 
    Runnable run=new Runnable(){ 
 
      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        System.out.println("["+Thread.currentThread()+"]  "); 
        DeadLoopClass d=new DeadLoopClass(); 
        System.out.println("["+Thread.currentThread()+"]  "); 
         
      }}; 
       
      new Thread(run).start(); 
      new Thread(run).start(); 
  } 
 
} 
이 안에서 운행할 때 막히는 현상을 볼 수 있다.
읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기