본문 바로가기

기술스택/ASM - [Bytecode]

[ASM] static과 인스턴스 필드 생성자 호출 - <clinit>, <init>

<clinit> : 정적 초기화 생성자 블록, <init> : 생성자 호출 블록

 

변수를 만드는 것과, 생성자를 불러서 초기 값을 지정하는 것은 다른 프로세스이다.
쉽게 말해서 Obj obj; 와 Obj obj = new Obj(); 은 JVM 동작 자체가 다르다. 

 

asm으로 변수를 생성하는 것과는 별개로, clinit과 init 호출을 통해 이것의 생성자를 mv으로 호출하고, 이 생성자들을 통해 값을 넣어줄 수 있다

 

clinit과 init은 Java 바이트코드에서 특별한 의미를 가지는 메서드 이름이다.

이들은 클래스 단위로 작동하며, 특정 객체 인스턴스에 대한 것이 아니다. 이 메서드를 통해 정의된 코드는 클래스가 JVM에 의해 로딩되는 시점에 한 번만 실행된다.

 

<clinit> : static에서의 생성자 호출

 

<clinit>는 Java 클래스의 정적 초기화 블록(static initializer block)에 해당하는 특별한 메서드이다.

Java에서 정적 초기화 블록은 클래스가 로딩되어 JVM(Java Virtual Machine)에 의해 처음으로 참조될 때 정확히 한 번 실행된다.

 

이 블록은 주로 정적 변수의 복잡한 초기화에 사용되며, 모든 정적 변수 할당과 정적 초기화 블록 내의 명령어들은 <clinit> 메서드로 컴파일된다.

 

    public void example() {

        //2. 정적 변수(static)에 값 넣기
        //먼저 Java 클래스의 정적 초기화 블록을 호출한다.
        MethodVisitor mv = cv.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
        mv.visitCode();
        mv.visitLdcInsn(123); //stack에 상수 123 입력
        
        //이 static 변수에 123을 넣자.
        mv.visitFieldInsn(PUTSTATIC, "com/dummy/jdbcserver/example_asm/chap3/BasicExample" , "staticVal", "I");
        
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

 

<init> : 일반 인스턴스 필드의 생성자 호출

반대로 <init>은 인스턴스 생성자를 나타낸다.

이는 객체가 생성될 때마다 호출되며, 인스턴스 필드의 초기화나 생성자에서 정의된 다른 초기화 코드를 실행한다. 각각의 객체 인스턴스에 대해 <init> 메서드가 호출되며, 클래스 내에 여러 생성자가 정의될 수 있다. 각 생성자는 서로 다른 시그니처(매개변수 목록)를 가질 수 있다.

 

 

다른 생성자 생성 예시

    public void example() {
        //1. 일반 필드 추가 및 값 넣기 (변수가 있다면 visitField 수행, 아니라면 새로 생성)
        FieldVisitor fv = cv.visitField(ACC_PUBLIC, "makeNewField", "I", null, null);
        if (fv != null) {
            //1-1. 커스텀 생성자 만들기

            //1-2. 기본 생성자 예시
//            MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); //생성자 만들기
            //하지만 위처럼 실행하면 ClassFormatErr 발생, 이미 기본 생성자가 java에서는 알아서 생성되기 때문(똑같은 생성자 만들면 중복 에러)

            //1-3. 커스텀 생성자
            //그렇기에 매개변수를 다르게 하여, 다른 생성자를 추가하겠음. : 이제 Int형 변수를 받는다.
            MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null); //생성자 만들기
            mv.visitCode(); //방문 시작

            //1-4. Object(상위)의 기본 생성자 호출 : java에서는 하위 클래스 생성자 호출 시 상위도 같이 호출하기 때문.
            // 이는 우리가 암묵적으로 사용하던 부모 클래스 생성자 호출 super()와 동일하다고 이해하면 된다.
            mv.visitVarInsn(ALOAD, 0); // this 참조
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);

            //1-5. 커스텀 생성자를 통한 매개변수 값 할당
            mv.visitVarInsn(ALOAD, 0); //this(자기자신 - 커스텀 생성자) stack에 넣기
            mv.visitLdcInsn(1000); //stack에 값 넣기
            mv.visitFieldInsn(PUTFIELD, "com/dummy/jdbcserver/example_asm/chap3/BasicExample", "makeNewField", "I");
            mv.visitInsn(RETURN);
            mv.visitMaxs(0,0);
            mv.visitEnd();
            fv.visitEnd();
        }

 

 

결과 : 

 

public class BasicExample {
    public int val;
    public static int staticVal = 123;
    public int vFive;
    public int vSix;
    public int makeNewField;

    public BasicExample() {
        this.vFive = 5;
        this.vSix = 6;
    }

    public void main() throws Exception {
        System.out.println("기초 연습용");
    }

    //이 커스텀 생성자가 추가되었음
    public BasicExample(int var1) {
        this.makeNewField = 1000;
    }

 

Class A 안에 a, b, c의 인스턴스가 있다면?

public class A {
    int a, b, c; // 인스턴스 필드

    public A() { // 생성자
        a = 1;
        b = 2;
        c = 3;
    }
}

 

1. 클래스 A 안에 a, b, c 인스턴스 필드가 있다고 할 때, <clinit>를 통해 정의된 값은 이 필드들을 직접 초기화하지 않는다.

 

2. 대신, 클래스 레벨의 정적 필드 초기화에 사용된다. 예를 들어, A 클래스의 정적 필드를 초기화하는 데 사용된다.

 

3. 인스턴스 필드 a, b, c를 초기화하려면, <init> 메서드(즉, 생성자) 내에서 초기화 코드를 작성해야 한다. 각 객체 인스턴스에 대해 생성자 <init>가 호출되며, 이 때 객체의 인스턴스 필드를 초기화할 수 있다.

 

ASM에서는 다음과 같이 나타낼 수 있다.

 

MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
// 객체 자신에 대한 참조와 필드 초기화 코드
mv.visitVarInsn(ALOAD, 0); // 객체 자신(this)을 로드
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); // 슈퍼클래스의 생성자 호출
mv.visitVarInsn(ALOAD, 0);
mv.visitLdcInsn(1);
mv.visitFieldInsn(PUTFIELD, "A", "a", "I");
// b, c 필드에 대한 초기화 코드 생략
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1); // COMPUTE_MAXS 옵션 사용 시 실제 값은 자동 계산됨
mv.visitEnd();