본문 바로가기

Backend/Java

(12)
[Java] Generic 1편: Type Erasure와 javac의 동작 원리 Java 개발을 하다 보면 List과 같은 제네릭(Generic)을 숨 쉬듯이 사용하게 됩니다. 하지만 “왜 런타임에는 제네릭 타입 정보가 남아있지 않을까?” 혹은 “왜 instanceof List은 컴파일 에러가 날까?”라는 의문을 가져본 적이 있으신가요? 이 글에서는 Java가 제네릭을 어떻게 처리하는지,그리고 컴파일러와 JVM의 역할이 어떻게 나뉘는지 심도 있게 파헤쳐 봅니다.제네릭이 없던 시절의 Java와 JSR-14Java 5에 제네릭이 도입되기 전, 컬렉션을 다루는 코드는 런타임 에러의 지뢰밭이었습니다. 제네릭 도입을 제안했던 JSR-14 Proposal의 문구를 보면 당시의 고민이 잘 드러납니다. "Many programs and libraries written in Java are intr..
[Inside Java] 08. 마무리.. Java 실행 구조 핵심 위키! 앞선 1장부터 7장까지 다룬 내용을 다시 찾기 쉽도록 핵심 개념만 두줄로 압축했습니다.자세한 동작 원리와 예시는 각 챕터에서 확인하고, 이 글에서는 개념의 연결 관계만 빠르게 정리합니다. 이번 시리즈를 정리하면서 Java는 단순히 컴파일만 하는 언어가 아니라, 인터프리터와 JIT 컴파일러를 함께 활용해 실행 성능을 높이는 구조라는 점이 특히 인상 깊었습니다.또한 클래스 로딩부터 메모리 구조, 동적 디스패치, JIT 최적화까지 각각의 개념이 독립적으로 존재하는 것이 아니라 하나의 실행 흐름으로 연결되어 있다는 점도 새롭게 이해할 수 있었습니다. 예전에 Young Generation, Old Generation, Minor GC, Major GC 등 GC의 동작 방식도 한 번 공부했던 기억이 있는데, 이번..
[Inside Java] 07. interface는 JVM 메모리에서 어떻게 관리될까? — default 메서드와 enum 객체까지!! 이전 [Inside Java] 시리즈에서는 class로 만든 객체와 메서드 호출이 JVM 안에서 어떻게 연결되는지 살펴봤습니다.그런데 Java에는 class만 있는 것이 아니죠.....interface Animal { void speak();}enum Direction { LEFT, RIGHT}@interface Marker {} 셋은 문법적으로 서로 완전히 달라 보입니다. 그런데 컴파일하고 나면 모두 .class 파일이 만들어집니다.Animal.java → Animal.classDirection.java → Direction.classMarker.java → Marker.class 여기서 한 가지 궁금한 점이 생겼습니다.이름은 모두 .class인데, 그렇다면 전부 일반 ..
[Inside Java] 06. 같은 Java 코드가 실행될수록 빨라지는 이유 — JIT 컴파일과 HotSpot JVM 최적화 #Inline #Hot Code와 JIT Compilation #Runtime Profiling [Inside Java] 1~5편 까지는 JVM이 메모리를 어떻게 관리하는지를 중심으로 살펴보았습니다. 그런데 지금까지 살펴본 것처럼 JVM이 런타임마다 바이트코드를 하나씩 해석하고, Operand Stack을 이용해 연산을 수행하며, 메서드 호출 대상까지 계속 찾아야 한다면 Java는 계속 느린 것 아닐까?.. 하는 의문증이 생길수도..왜냐면은 실제로 Java는 AOT(Ahead-Of-Time) 방식처럼 실행 전에 모든 코드를 기계어로 컴파일하지 않고, 우선 Interpreter를 통해 바이트코드를 실행합니다.그렇다면 같은 코드가 반복 실행될 때도 매번 바이트코드를 해석해야 하는 걸까요? Java는 처음부터 모든 코드를 기계어로 바꾸지 않습니다. 우선 빠르게 실행을 시작하고, 실제 실행 정보를 관찰한 ..
[Inside Java] 05. JVM은 main()을 바로 실행하지 않는다 #2 — class metadata는 언제 초기화 될까? #Active Use, #메모리 누수?! 앞선 4장에서는 .class 파일이 JVM 내부에서 Loading, Linking, Initialization을 거쳐 실행 가능한 타입이 되는 과정을 살펴봤습니다.하지만 여기서 한 가지 궁금증이 생깁니다. “클래스가 메모리에 로드되었다면, 항상 그 즉시 초기화되는 것일까요?” 정답은 아닙니다. JVM은 클래스가 실제로 필요한 순간에만 초기화를 수행합니다.이번 글에서는 JVM 명세에서 말하는 Active Use를 시작으로, 클래스의 정체성을 결정하는 ClassLoader 그리고 클래스가 메모리에서 사라지는 과정까지 살펴보겠습니다.메서드 영역에 클래스가 로딩·초기화되는 시점은 언제일까? (Active Use)클래스가 메서드 영역에 로드되었다고 반드시 즉시 초기화되는 것은 아닙니다. 이전 포스트에서 한번 언..
[Inside Java] 04. JVM은 main()을 바로 실행하지 않는다?! #1 -ClassLoader, 클래스 생명주기 #Loading #Linking #Initialization 1편에서는 Java 코드가 바이트코드로 변환되고 JVM 위에서 실행되는 전체 흐름을 살펴봤습니다. 2편에서는 컴파일러가 정적 타입을 기준으로 무엇을 검사하고, 런타임의 JVM이 실제 객체를 기준으로 무엇을 결정하는지 정리했습니다.3편에서는 Heap의 객체에서 Klass Pointer와 Class Metadata를 따라가며 실제 메서드가 선택되는 흐름까지 살펴봤습니다. 그렇다면 이제 실행의 시작점으로 돌아가 보겠습니다.질문!! JVM은 .class 파일을 발견한 순간부터 바로 main()을 실행할 수 있을까요? JVM은 필요한 클래스를 로드하고, 검증하고, 실행에 필요한 상태를 준비한 뒤 초기화를 수행합니다.이번 글에서는 .class 파일이 JVM 안에서 실행 가능한 타입이 되는 과정만 집중해서 살펴보겠..
[Inside Java] 03. Heap 객체에서 method Area까지! - Klass Pointer와 Dynamic vs Static Dispatch 살펴보기 이전 포스트(누르면 바로가져요) 에서는 다음 코드가 왜 문법적으로 가능한지 살펴봤습니다.Parent p = new Child(); 컴파일러는 표현식 p의 정적 타입을 Parent로 보고, 실제 생성되는 객체는 Child입니다.그런데 여기서 한 단계 더 들어가면 새로운 질문이 생깁니다.p.introduce(); 바이트코드에는 Parent.introduce()에 대한 참조가 남는데, 실행 결과는 왜 Child.introduce()일까요? Heap의 객체에는 필드 값만 있을 텐데, JVM은 어떻게 이 객체가 Child라는 사실을 알고 실제 메서드 구현까지 찾아갈까요?이번 글은 이 질문을 해결하는 데 집중합니다. 기준 코드class Parent { int parentAge = 50; String na..
[Inside Java] 02. 업캐스팅?! Java 컴파일러는 무엇을 알고, JVM은 무엇을 결정할까? — Dynamic Dispatch로 이해하는 다형성 Parent p = new Child(); Java를 공부하다 보면 이 한 줄을 아주 자주 만나게 됩니다.보통은 이렇게 설명합니다.부모 타입 변수에 자식 객체를 담는 업캐스팅이다. 문법적으로는 맞는 설명입니다.그런데 저는 여기서 조금 더 궁금해졌습니다.Parent와 Child 객체가 둘 다 만들어지는 걸까?p는 부모 객체를 가리키는 걸까, 자식 객체를 가리키는 걸까?필드는 왜 부모 기준으로 보이는데, 메서드는 자식 기준으로 실행될까?이번 글에서는 객체지향 문법을 길게 다시 설명하기보다, 뒤에서 JVM 실행 원리를 살펴보기 위해 꼭 필요한 내용만 정리해보려고 합니다.이 글에서는 Parent p = new Child(); 한 줄을 기준으로 업캐스팅이 가능한 이유와 실제로 생성되는 객체가 무엇인지 살펴봅니다...