자바 상수 본질 및 세 가지 상수 탱크(소결)탐구

그 전에 다른 사람의 박문,그리고 일부 책 에서 상수 가 상수 탱크 에 놓 여 있다 는 것 을 알 게 되 었 습 니 다.세부 적 인 내용 은 알 수 없 었 습 니 다.앞 에 있 는 것 이 거의 완전한 블랙박스 라 고 생각 하고 불편 함 을 느 꼈 습 니 다.그래서 을 읽 었 습 니 다.이 책 에서 상수 에 대한 소 개 는 바이트 파일 의 구조 에 중심 을 두 었 습 니 다.그리고 자동 메모리 관리 체제 에서 도 운행 상 당량 탱크 를 소 개 했 고 자 료 를 찾 아 본 후에 머 릿 속 에 어느 정도 인식 이 생 겼 다.
자바 의 상수 탱크 는 세 가지 형태 로 나 뉘 는데 그것 이 바로 정적 상수 탱크,문자열 상수 탱크 와 운행 시 상수 탱크 이다.
정적 상수 탱크
정적 상수 탱크,즉*.class 파일 의 상수 탱크 입 니 다.class 파일 의 상수 탱크 는 문자열(숫자)의 글자 만 포함 하 는 것 이 아니 라 클래스,방법 에 대한 정 보 를 포함 하여 class 파일 의 대부분 공간 을 차지 합 니 다.
이러한 상수 탱크 는 주로 두 가지 상수:글자 의 양(Literal)과 기호 인 용 량(Symbolic References)을 저장 하 는 데 사용 된다.글자 의 양 은 자바 언어 차원 의 상수 개념 에 해당 한다.예 를 들 어 텍스트 문자열,final 이 라 고 밝 힌 상수 값 등 이다.기호 인용 은 원래 의 이론 적 개념 을 컴 파일 하 는 데 다음 과 같은 세 가지 유형의 상수 에 속한다.
류 와 인터페이스의 전체 제한 명칭필드 이름과 설명자방법 명칭 과 설명자
그리고 실행 시 상 당량 탱크 는 jvm 가상 컴퓨터 가 클래스 로드 작업 을 마 친 후에 class 파일 의 상 당량 탱크 를 메모리 에 불 러 오고 방법 구역 에 저장 합 니 다.우리 가 흔히 말 하 는 상 당량 탱크 는 방법 구역 의 운행 시 상 당량 탱크 를 말 합 니 다.
런 타임 풀 은 Class 파일 상수 풀 에 비해 또 다른 중요 한 특징 은 동태 성 을 가 진 다 는 것 입 니 다.자바 언어 는 상수 가 반드시 컴 파일 기간 만 발생 하 는 것 을 요구 하지 않 습 니 다.즉,class 파일 에 미리 설 치 된 상수 풀 의 내용 이 방법 구역 에 들 어가 서 런 타임 풀 을 실행 할 수 있 는 것 이 아니 라 런 타임 에 새로운 상수 풀 에 넣 을 수도 있 습 니 다.이런 특성 을 개발 자 들 이 많이 이용 하 는 것 이 바로 String 류 의 intern()방법 이다.
String 의 intern()방법 은 상수 탱크 에 equal 같은 문자열 이 존재 하 는 지 찾 습 니 다.있 으 면 이 문자열 의 인용 을 되 돌려 주 고 없 으 면 자신의 문자열 을 상수 탱크 에 추가 합 니 다.
이렇게 보면 정적 상수 탱크,즉*.class 파일 의 상수 탱크 를 통 해 상수 의 의 미 를 더욱 탐구 할 수 있 습 니 다.
다음은 코드 를 보 겠 습 니 다.

public class Main {

  public static void main(String[] args) {
    System.out.println(Father.str);
  }
}

class Father{
  public static String str = "Hello,world";
  static {
    System.out.println("Father static block");
  }
}
출력 결과

또 다른 것 보기:

package com.company;

public class Main {

  public static void main(String[] args) {
    System.out.println(Father.str);
  }
}

class Father{
  public static final String str = "Hello,world";
  static {
    System.out.println("Father static block");
  }
}

결과:

단 하나
깜짝 놀 랐 죠?
우 리 는 두 번 째 프 리 젠 테 이 션 의 코드 블록 을 역 컴 파일 했다.

D:\CodePractise\untitled\out\production\untitled\com\company>javap -c Main.class
Compiled from "Main.java"
public class com.company.Main {
 public com.company.Main();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc      #4         // String Hello,world
    5: invokevirtual #5         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: return
}
여기 메 인(Main)이 있어 요.구조 방법 이 고 다음은 메 인 방법 이에 요.
0:getstatic\#2 는 System.out 에 대응 합 니 다.
3:ldc\#4 에 대응 하 는 값 은 Hello 입 니 다.World 에서 확 정 된 값 은 Father 클래스 에서 꺼 내지 않 았 습 니 다.
ldc 는 int,float 또는 String 형식의 상수 값 을 상수 탱크 에서 스 택 꼭대기 로 푸 시 하 는 것 을 표시 합 니 다.
없다 니!!!Father.class 파일 을 삭제 하 더 라 도 이 코드 를 실행 할 수 있 습 니 다.Father 류 와 는 아무런 관계 가 없습니다.
실제로 컴 파일 단계 에서 상수 가 이 상수 호출 방법 이 있 는 상수 탱크 에 저 장 됩 니 다.
이 예 에서 알 수 있 듯 이 여기 의 str 는 상수 로 이 상수 를 호출 하 는 방법 은 main 방법 main 방법 이 있 는 유형 은 Main 이다.즉,컴 파일 한 후에 str 는 이러한 상수 탱크 에 놓 여 있다.
본질 적 으로 호출 클래스 는 정 의 된 상수 클래스 에 직접 인용 되 지 않 았 기 때문에 정 의 된 상수 클래스 의 초기 화 를 촉발 하지 않 습 니 다.
클래스 의 초기 화 는 클래스 의 로드 메커니즘 과 관련 되 어 있 습 니 다.
문자열 상수 풀(string pool 에 도 string literal pool 이 라 고 함)
전역 문자열 풀 의 내용 은 클래스 로 딩 이 완료 되 었 습 니 다.검증 을 거 친 후 준비 단계 에서 문자열 대상 인 스 턴 스 를 생 성 한 다음 에 이 문자열 대상 인 스 턴 스 의 인용 값 을 string pool 에 저장 합 니 다.(기억:string pool 에 저 장 된 것 은 구체 적 인 인 인 인 스 턴 스 대상 이 아 닌 인용 값 입 니 다.구체 적 인 인 인 인 스 턴 스 대상 은 더미 에서 열 린 공간 에 저 장 됩 니 다.)
문자열 상수 탱크 의 위치 에 대한 설명 이 정확 하지 않 습 니 다.
JDK 6.0 및 이전 버 전에 서 문자열 상수 탱크 는 Perm Gen 구역(즉 방법 구역)에 놓 여 있 습 니 다.
JDK 7.0 버 전에 서 문자열 상수 탱크 가 더미 로 옮 겨 졌 습 니 다.
HotSpot VM 에서 실 현 된 string pool 기능 은 StringTable 류 입 니 다.해시 표 입 니 다.상주 문자열(즉,우리 가 흔히 말 하 는 두 따옴표 로 묶 은 것)의 인용(상주 문자열 인 스 턴 스 자체 가 아 닌)이 저 장 됩 니 다.즉,쌓 여 있 는 일부 문자열 의 인 스 턴 스 가 이 StringTable 에 인 용 된 후에'상주 문자열'의 신분 이 부 여 된 것 과 같 습 니 다.이 StringTable 은 모든 HotSpot VM 의 인 스 턴 스 가 하나 밖 에 없 으 며 모든 클래스 에 공 유 됩 니 다.
 실행 상수 풀 로 돌아 가기(runtime constant pool)
jvm 는 특정한 종 류 를 실행 할 때 반드시 로드,연결,초기 화 를 거 쳐 야 하 며 연결 은 검증,준비,해석 세 단 계 를 포함한다.
한편,클래스 가 메모리 에 불 러 오 면 jvm 은 정적 상수 탱크 의 내용 을 실행 시 상수 탱크 에 저장 합 니 다.이 를 통 해 알 수 있 듯 이 실행 시 상수 탱크 도 모든 종류 가 있 습 니 다.
정적 상수 탱크 에 저 장 된 것 은 글자 의 양 과 기호 참조 입 니 다.즉,대상 의 인 스 턴 스 가 아니 라 대상 의 기호 참조 값 입 니 다.그리고 해석(resolve)을 거 친 후에 기호 인용 을 직접 인용 으로 바 꾸 고 해석 하 는 과정 에서 문자열 상수 탱크 를 조회 합 니 다.즉,우리 가 위 에서 말 한 StringTable 은 상수 탱크 에서 인용 한 문자열 과 문자열 상수 탱크 에서 사용 하 는 문자열 이 일치 하도록 합 니 다.
예 를 하나 봅 시다.

import java.util.UUID;

public class Test {
  public static void main(String[] args) {
    System.out.println(TestValue.str);
  }
}

class TestValue{
  public static final String str = UUID.randomUUID().toString();

  static {
    System.out.println("TestValue static code");
  }
}

결과:

성명 자체 의 str 는 모두 상수 이다.관건 적 인 것 은 이 상수 의 값 이 컴 파일 시기 에 확 정 될 수 있 는 지 하 는 것 이다.분명히 이곳 의 예 는 컴 파일 기간 에 확실히 확정 되 지 않 는 다.운행 기간 에 만 확정 할 수 있 습 니 다.이 는 목표 류 를 초기 화 해 야 합 니 다.
상수 의 값 이 컴 파일 기간 에 확정 되 지 않 으 면 호출 클래스 의 상수 탱크 에 들 어가 지 않 습 니 다.
이 때 프로그램 이 실 행 될 때 이 상수 가 있 는 종 류 를 주동 적 으로 사용 하 게 되 고 이 종 류 를 초기 화 할 수 있 습 니 다.
(이것 은 클래스 의 로드 메커니즘 과 관련 되 어 있 으 며,뒤에 여기에 표 시 를 할 것 입 니 다)
역 컴 파일 탐구:

Compiled from "Test.java"
class com.leetcodePractise.tstudy.TestValue {
 public static final java.lang.String str;

 com.leetcodePractise.tstudy.TestValue();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 static {};
  Code:
    0: invokestatic #2         // Method java/util/UUID.randomUUID:()Ljava/util/UUID;
    3: invokevirtual #3         // Method java/util/UUID.toString:()Ljava/lang/String;
    6: putstatic   #4         // Field str:Ljava/lang/String;
    9: getstatic   #5         // Field java/lang/System.out:Ljava/io/PrintStream;
   12: ldc      #6         // String TestValue static code
   14: invokevirtual #7         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   17: return
}
TestValue 류 가 초기 화 될 것 이 분명 합 니 다.
상수 소개 가 끝 난 후에 여기에 반 컴 파일 및 보조 문자 의 필 기 를 기록 하 세 요.

package com.company;

public class Main {

  public static void main(String[] args) {
    System.out.println(Father.str);
    System.out.println(Father.s);
  }
}

class Father{
  public static final String str = "Hello,world";
  public static final short s = 6;
  static {
    System.out.println("Father static block");
  }
}
 


public class com.company.Main {
 public com.company.Main();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc      #4         // String Hello,world
    5: invokevirtual #5         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   11: bipush    6
   13: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   16: return
}
bipush 는 단일 바이트(-128-127)의 상수 치 를 스 택 꼭대기 로 미 루 는 것 을 표시 합 니 다.
더욱 가입 한다

package com.company;

public class Main {

  public static void main(String[] args) {
    System.out.println(Father.str);
    System.out.println(Father.s);
    System.out.println(Father.t);
  }
}

class Father{
  public static final String str = "Hello,world";
  public static final short s = 6;
  public static final int t = 128;
  static {
    System.out.println("Father static block");
  }
}
역 컴 파일 을 진행 하 다

public class com.company.Main {
 public com.company.Main();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc      #4         // String Hello,world
    5: invokevirtual #5         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   11: bipush    6
   13: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   16: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   19: sipush    128
   22: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   25: return
}
sipush 는 짧 은 정형 상수 치(-32768~32767)를 창고 꼭대기 로 밀어 넣 는 것 을 표시 합 니 다.
재 변경

package com.company;

public class Main {

  public static void main(String[] args) {
    System.out.println(Father.str);

    System.out.println(Father.t);
  }
}

class Father{
  public static final String str = "Hello,world";

  public static final int t = 1;
  static {
    System.out.println("Father static block");
  }
}

public class com.company.Main {
 public com.company.Main();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc      #4         // String Hello,world
    5: invokevirtual #5         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   11: bipush    6
   13: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   16: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   19: sipush    128
   22: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   25: return
}

D:\CodePractise\untitled\out\production\untitled\com\company>javap -c Main.class
Compiled from "Main.java"
public class com.company.Main {
 public com.company.Main();
  Code:
    0: aload_0
    1: invokespecial #1         // Method java/lang/Object."<init>":()V
    4: return

 public static void main(java.lang.String[]);
  Code:
    0: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
    3: ldc      #4         // String Hello,world
    5: invokevirtual #5         // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8: getstatic   #2         // Field java/lang/System.out:Ljava/io/PrintStream;
   11: iconst_1
   12: invokevirtual #6         // Method java/io/PrintStream.println:(I)V
   15: return
}
이곳 은 iconst 로 변 했다.1
iconst 1 은 int 형식 1 을 스 택 꼭대기 로 전송 하 는 것 을 표시 합 니 다(iconstm1-iconst_5)
5 이상 이면 bipush 로 바 뀌 어 요.
m1 대응 은-1
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기