
자바 컴파일러는 new 키워드를 발견하면 두 개의 바이트 코드 명령어로 변환한다.
1. new
2. invokespecial
new는 JVM 관점에서 객체를 생성한다 (메모리 할당)
invokespecial은 자바 관점에서 객체를 생성한다 (생성자 실행)
1. new
1.1 확인
new 명령의 매개변수가 런타임 상수 풀의 클래스를 가리키는 심벌참조인지 확인한다.
즉, 메타스페이스에 있는 클래스정보를 가리키는지 확인한다.
그 다음 해당 클래스가 로딩, 해석, 초기화 되었는지 확인한다.
그 다음 새 객체를 담을 메모리를 할당한다.
1.2 메모리 할당
메모리 할당 방식에는 두가지가 존재하며 어떤 방식을 택하는지는 가비지 컬렉터의 종류에 따라 다르다.
1. 포인터 밀치기 방식
2. 프리리스트 방식
포인터 밀치기 방식은 자바 힙이 규칙적이라고 가정하고 사용하는 방식이다.
사용 중인 메모리 공간이 왼쪽에 있고 여유 메모리가 오른쪽에 있으면 사용 중인 메모리 공간의 끝을 표시하는 포인터를 새 객체의 크기만큼 오른쪽으로 옮긴다.
가비지 컬렉터가 컴팩트를 할 수 있으면 포인트 밀치기 방식이 사용된다. (시리얼 GC)
그러나 자바 힙은 규칙적이지 않다. 사용 중인 메모리와 여유 메모리가 뒤섞여있다. 이럴 때는 프리리스트 방식을 사용한다.
프리리스트 방식은 가용 메모리 블록을 목록으로 따로 관리하고 객체 인스턴스를 담기에 충분한 공간을 찾아 할당한 후 갱신한다.
대부분의 경우 프리리스트 방식을 사용한다.
멀티 스레드 환경에서 여러 스레드가 동시에 객체를 생성하려할 때 포인터에 관한 레이스 컨디션 문제가 발생할 수 있다.
그렇기 때문에 TLAB를 사용한다.
TLAB는 스레드 로컬 할당 버퍼를 뜻하며 스레드마다 다른 메모리 공간을 할당받는다. 각 스레드는 로컬 버퍼에서 메모리를 할당 받아 사용하다가 버퍼가 부족해지면 동기화를 통해 새로운 버퍼를 할당 받는다.
1.3 초기화
메모리 할당이 끝나면 객체 헤더를 제외하고 할당받은 메모리 공간 전체를 0으로 초기화 한다.
TLAB를 사용한다면 TLAB할당 시 즉시 수행한다. 덕분에 자바에서 객체의 인스턴스 필드를 초기화하지 않아도 사용할 수 있다. 모든 필드가 자연스럽게 각 데이터 타입에 해당하는 0값을 가지게 된다.
1.4 설정
객체 헤더에 각 객체에 필요한 설정 정보를 추가한다.
1. 어느 클래스의 인스턴스인가
2. 클래스의 메타정보는 어떻게 찾는가
3. 해시코드는 무엇인가
4. GC 나이는 어떻게 되는가
2. invokespecial
invokespecial은 <init>()라는 특별한 메서드를 실행한다.
부모클래스의 <init>()을 호출하여 Object까지 올라가며 호출체인을 실행한다.
private int x = 10; 처럼 초기화를 준 부분과 인스턴스 초기화 블록에 따른 초기화 과정이 실행되며 개발자가 작성한 생성자가 실행된다.