F-LAB JAVA · 2주차 · Phase 3 · 바이트코드와 상수 풀
🎯 2주차의 정점 —javap -c -v출력을 처음부터 끝까지 분석한다
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
javap -c -v ClassName.class 출력의 모든 줄을 해독할 수 있는가?dup 명령이 정확히 왜 필요한가? (커리큘럼 자기 점검 질문)invokespecial / invokevirtual / invokestatic / invokeinterface 의 결정적 차이는?Phase 3의 모든 학습이 이 한 줄에 응축된다:
javap -c -v -p ClassName.class이 출력을 박승제씨가 처음부터 끝까지 이해할 수 있다면, 2주차의 정점에 도달한 것이다.
Phase 1 메모리 구조, Phase 2 메서드 호출 메커니즘, Phase 3 바이트코드 + 상수 풀 + 심볼 참조 — 모든 것이 한 화면에서 만난다.
| 단계 | 비유 |
|---|---|
| 소스 코드 작성 | 환자와 대화로 증상 듣기 |
.class 생성 | X-Ray 촬영 |
javap -v 출력 | X-Ray 사진 |
| 이번 Unit의 능력 | X-Ray를 보고 정확히 진단 |
비전문가도 X-Ray 사진은 볼 수 있다. 읽을 수 있는가가 차이.
박승제씨도 Phase 3까지 와서 이제 읽을 수 있는 능력을 갖춘다.
1. Phase 3 종합 — 정점 진입
2. 분석할 실전 ILIC 코드
3. .class 파일 헤더 완전 해독
4. Constant Pool 완전 해독
5. 생성자 바이트코드 — 한 줄씩
6. 인스턴스 메서드 바이트코드 — 한 줄씩
7. ILIC 운영 사고 시뮬레이션 — 실제 디버깅
8. Java 버전별 차이 + 흔한 함정
9. 면접 질문 + Phase 3 졸업 시험
Unit 3.1 — 바이트코드란 무엇인가
↓ 스택 머신, .class 구조, javap 도구, 8개 명령 카테고리
Unit 3.2 — 상수 풀의 생성과 구조
↓ 14가지 항목, Methodref의 5개 참조, 디스크립터
Unit 3.3 — 심볼 참조
↓ Symbolic vs Direct, Resolution 5단계, Late Binding
Unit 3.4 — 바이트코드 실전 분석 (이번)
↓ 모든 것을 종합한 한 클래스 분석
이 Unit이 끝나면:
.class 파일을 즉시 분석 가능# 가장 자주 쓸 명령
javap -c -v -p ClassName.class
# 옵션 의미:
# -c : 바이트코드 표시
# -v : verbose (상수 풀, 메타데이터)
# -p : private 멤버 포함
대안:
박승제씨는 IntelliJ를 쓰니까 IDE 통합이 가장 편할 것.
하지만 운영 서버에선 javap 명령어가 필수.
ILIC의 실전 클래스 (단순화 버전):
package com.ilic.shipment;
import java.math.BigDecimal;
public class ShipmentCalculator {
private static final BigDecimal FUEL_RATE = new BigDecimal("0.15");
private int callCount = 0;
public BigDecimal calculate(int weight, BigDecimal baseRate) {
callCount++;
BigDecimal subtotal = baseRate.multiply(BigDecimal.valueOf(weight));
return subtotal.multiply(BigDecimal.ONE.add(FUEL_RATE));
}
public int getCallCount() {
return callCount;
}
}
이 한 클래스 안에 박승제씨가 배운 모든 메커니즘:
callCount) → Unit 1.5FUEL_RATE) → Unit 1.3calculate) → Unit 2.1BigDecimal.valueOf) → Unit 1.5의 invokestaticmultiply) → invokevirtualnew BigDecimal) → Unit 2.4javac ShipmentCalculator.java
javap -c -v -p ShipmentCalculator.class > analysis.txt
이제 출력을 한 줄씩 보자.
.class 파일 헤더 완전 해독Classfile /Users/seungje/ShipmentCalculator.class
Last modified May 15, 2026; size 567 bytes
SHA-256 checksum 8a3c2f1e...
Compiled from "ShipmentCalculator.java"
public class com.ilic.shipment.ShipmentCalculator
minor version: 0
major version: 65
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #21 // com/ilic/shipment/ShipmentCalculator
super_class: #2 // java/lang/Object
interfaces: 0, fields: 2, methods: 3, attributes: 1
minor version: 0
major version: 65
→ Java 21로 컴파일. (Unit 3.1의 버전표: 65 = Java 21)
→ Java 17 서버에서 실행 시 UnsupportedClassVersionError 가능.
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
→ 클래스 접근 플래그:
ACC_PUBLIC = 0x0001 (public)ACC_SUPER = 0x0020 (구식 호환 플래그, Java 1.0 호환)다른 플래그 값:
ACC_FINAL = 0x0010 (final class)ACC_INTERFACE = 0x0200 (인터페이스)ACC_ABSTRACT = 0x0400 (abstract)ACC_ENUM = 0x4000 (enum)this_class: #21
super_class: #2
→ 자기 클래스명 = Constant Pool의 #21
→ 부모 클래스명 = Constant Pool의 #2 (Object)
interfaces: 0, fields: 2, methods: 3, attributes: 1
→ 구현 인터페이스 0개
→ 필드 2개 (FUEL_RATE static, callCount instance)
→ 메서드 3개 (생성자 + calculate + getCallCount)
→ attribute 1개 (SourceFile)
hexdump -C ShipmentCalculator.class | head -2
00000000 ca fe ba be 00 00 00 41 00 2c 0a 00 02 00 03 ...
↑ Magic ↑ Ver=65 ↑ Constant Pool 항목 수=44
0xCAFEBABE: Magic Number (Unit 3.1)0x41 = 65 (Java 21)→ javap로 본 것과 일치.
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Class #8 // java/math/BigDecimal
#8 = Utf8 java/math/BigDecimal
#9 = String #10 // "0.15"
#10 = Utf8 "0.15"
#11 = Methodref #7.#12 // BigDecimal."<init>":(Ljava/lang/String;)V
#12 = NameAndType #5:#13 // "<init>":(Ljava/lang/String;)V
#13 = Utf8 (Ljava/lang/String;)V
#14 = Fieldref #21.#15 // ShipmentCalculator.FUEL_RATE:Ljava/math/BigDecimal;
#15 = NameAndType #16:#17 // FUEL_RATE:Ljava/math/BigDecimal;
#16 = Utf8 FUEL_RATE
#17 = Utf8 Ljava/math/BigDecimal;
#18 = Fieldref #21.#19 // ShipmentCalculator.callCount:I
#19 = NameAndType #20:#23 // callCount:I
#20 = Utf8 callCount
#21 = Class #22 // com/ilic/shipment/ShipmentCalculator
#22 = Utf8 com/ilic/shipment/ShipmentCalculator
#23 = Utf8 I
#24 = Methodref #7.#25 // BigDecimal.valueOf:(J)Ljava/math/BigDecimal;
#25 = NameAndType #26:#27 // valueOf:(J)Ljava/math/BigDecimal;
#26 = Utf8 valueOf
#27 = Utf8 (J)Ljava/math/BigDecimal;
#28 = Methodref #7.#29 // BigDecimal.multiply:(LBigDecimal;)LBigDecimal;
#29 = NameAndType #30:#31
#30 = Utf8 multiply
#31 = Utf8 (Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
#32 = Fieldref #7.#33 // BigDecimal.ONE:LBigDecimal;
#33 = NameAndType #34:#17
#34 = Utf8 ONE
#35 = Methodref #7.#36 // BigDecimal.add:(LBigDecimal;)LBigDecimal;
...
#4 = Utf8 java/lang/Object ← 클래스명
#5 = Utf8 <init> ← 생성자 이름
#6 = Utf8 ()V ← 시그니처: void 반환
#10 = Utf8 "0.15" ← String 값
#16 = Utf8 FUEL_RATE ← 필드 이름
#17 = Utf8 Ljava/math/BigDecimal; ← 타입 디스크립터
#20 = Utf8 callCount ← 필드 이름
#23 = Utf8 I ← int 타입
#26 = Utf8 valueOf ← 메서드 이름
#27 = Utf8 (J)Ljava/math/BigDecimal; ← long 받아 BigDecimal 반환
#30 = Utf8 multiply
#31 = Utf8 (Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
#34 = Utf8 ONE
→ 모든 텍스트의 진짜 저장소.
#2 = Class #4 // java/lang/Object
#7 = Class #8 // java/math/BigDecimal
#21 = Class #22 // com/ilic/shipment/ShipmentCalculator
→ 3개 클래스를 참조. Object(부모), BigDecimal(사용), 자기 자신.
#3 = NameAndType #5:#6 // <init>:()V (Object 생성자)
#12 = NameAndType #5:#13 // <init>:(String)V (BigDecimal 생성자)
#15 = NameAndType #16:#17 // FUEL_RATE:BigDecimal
#19 = NameAndType #20:#23 // callCount:I
#25 = NameAndType #26:#27 // valueOf:(J)BigDecimal
#29 = NameAndType #30:#31 // multiply:(BigDecimal)BigDecimal
→ 각 멤버 (메서드/필드)의 이름 + 시그니처 쌍.
#1 = Methodref #2.#3 // Object.<init>()
#11 = Methodref #7.#12 // BigDecimal.<init>(String)
#24 = Methodref #7.#25 // BigDecimal.valueOf(long)
#28 = Methodref #7.#29 // BigDecimal.multiply(BigDecimal)
#35 = Methodref #7.#36 // BigDecimal.add(BigDecimal)
→ 호출하는 메서드들. 각 항목이 Class + NameAndType 조합.
#14 = Fieldref #21.#15 // ShipmentCalculator.FUEL_RATE
#18 = Fieldref #21.#19 // ShipmentCalculator.callCount
#32 = Fieldref #7.#33 // BigDecimal.ONE
→ 접근하는 필드들.
#9 = String #10 // "0.15"
→ String 리터럴. Utf8을 한 단계 감쌌음.
java/math/BigDecimal 텍스트는 풀에 단 1번 (#8).
하지만 코드에서 BigDecimal을 여러 번 사용:
new BigDecimal("0.15") (생성자)BigDecimal.valueOf(weight) (static)baseRate.multiply(...) (인스턴스 메서드)BigDecimal.ONE (필드)BigDecimal.add(...) (인스턴스 메서드)모두 #7 (Class) 또는 #8 (Utf8) 을 공유.
→ 중복 제거 효과 확실.
ShipmentCalculator 클래스에 명시적 생성자 없음.
컴파일러가 자동으로 기본 생성자 생성:
public ShipmentCalculator() {
super();
this.callCount = 0; // 명시적이지만 0이라 생략 가능
}
추가로 static 초기화 블록:
static {
FUEL_RATE = new BigDecimal("0.15");
}
public com.ilic.shipment.ShipmentCalculator();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #18 // Field callCount:I
9: return
LineNumberTable:
line 9: 0
메타 정보:
descriptor: ()V ← 매개변수 없음, void 반환
flags: ACC_PUBLIC ← public
stack=2, locals=1, args_size=1
stack=2: Operand Stack 최대 2 슬롯 사용locals=1: LVA 1 슬롯 (this 1개)args_size=1: 매개변수 1개 (this 포함)바이트코드 한 줄씩:
0: aload_0
1: invokespecial #1 // Object.<init>()
super()#1은 Constant Pool의 Methodref → Object의 <init>4: aload_0
5: iconst_0
6: putfield #18 // callCount:I
#18 = Fieldref → ShipmentCalculator.callCountthis.callCount = 0 실행9: return
callCount = 0 가 왜 명시적?박승제씨가 작성한 코드:
private int callCount = 0;
만약:
private int callCount; // 명시적 = 0 없이
JVM은 어차피 0으로 초기화 (Unit 2.4). 그런데 컴파일러는 둘 다 putfield #18로 동일하게 처리.
→ 명시적이든 아니든 같은 바이트코드.
→ Unit 2.4에서 본 "컴파일러가 필드 초기값을 생성자에 자동 삽입"의 증거.
<clinit>)static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=3, locals=0, args_size=0
0: new #7 // class java/math/BigDecimal
3: dup
4: ldc #9 // String "0.15"
6: invokespecial #11 // BigDecimal.<init>(String)
9: putstatic #14 // FUEL_RATE
12: return
<clinit> = class initializer. 클래스 초기화 시 1번만 호출.
한 줄씩:
0: new #7 // new BigDecimal
3: dup // 복제
4: ldc #9 // "0.15"
6: invokespecial #11 // BigDecimal.<init>(String)
9: putstatic #14 // FUEL_RATE
#14 = ShipmentCalculator.FUEL_RATE12: return
→ Unit 2.4 6장 (new의 메모리 변화)을 직접 확인.
public BigDecimal calculate(int weight, BigDecimal baseRate) {
callCount++;
BigDecimal subtotal = baseRate.multiply(BigDecimal.valueOf(weight));
return subtotal.multiply(BigDecimal.ONE.add(FUEL_RATE));
}
public java.math.BigDecimal calculate(int, java.math.BigDecimal);
descriptor: (ILjava/math/BigDecimal;)Ljava/math/BigDecimal;
flags: (0x0001) ACC_PUBLIC
Code:
stack=4, locals=4, args_size=3
0: aload_0
1: dup
2: getfield #18 // Field callCount:I
5: iconst_1
6: iadd
7: putfield #18 // Field callCount:I
10: aload_2
11: iload_1
12: i2l
13: invokestatic #24 // Method BigDecimal.valueOf:(J)LBigDecimal;
16: invokevirtual #28 // Method BigDecimal.multiply:(LBigDecimal;)LBigDecimal;
19: astore_3
20: aload_3
21: getstatic #32 // Field BigDecimal.ONE:LBigDecimal;
24: getstatic #14 // Field FUEL_RATE:LBigDecimal;
27: invokevirtual #35 // Method BigDecimal.add:(LBigDecimal;)LBigDecimal;
30: invokevirtual #28 // Method BigDecimal.multiply:(LBigDecimal;)LBigDecimal;
33: areturn
LineNumberTable:
line 12: 0
line 13: 10
line 14: 20
descriptor: (ILjava/math/BigDecimal;)Ljava/math/BigDecimal;
→ (int, BigDecimal) → BigDecimal
stack=4, locals=4, args_size=3
LVA 레이아웃:
LVA[0] this
LVA[1] weight (int)
LVA[2] baseRate (BigDecimal)
LVA[3] subtotal (지역변수)
0: aload_0
1: dup
2: getfield #18 // callCount
5: iconst_1
6: iadd
7: putfield #18 // callCount
→ 5개 명령어로 callCount++ 표현.
→ Unit 2.3에서 본 패턴 정확히 재현.
baseRate.multiply(BigDecimal.valueOf(weight)) 분석 (PC 10~19)10: aload_2
11: iload_1
12: i2l
BigDecimal.valueOf(long) 이라 long 필요13: invokestatic #24 // BigDecimal.valueOf(J)
16: invokevirtual #28 // BigDecimal.multiply(BigDecimal)
baseRate.multiply(BigDecimal_from_weight) 실행19: astore_3
→ invokestatic + invokevirtual 차이 직접 확인.
BigDecimal.valueOfbaseRate.multiply(...)subtotal.multiply(BigDecimal.ONE.add(FUEL_RATE)) 분석 (PC 20~30)20: aload_3 // subtotal
21: getstatic #32 // BigDecimal.ONE (static 필드)
24: getstatic #14 // FUEL_RATE (static 필드)
27: invokevirtual #35 // BigDecimal.ONE.add(FUEL_RATE)
30: invokevirtual #28 // subtotal.multiply(결과)
33: areturn // 반환
Stack 변화:
PC 20: [subtotal]
PC 21: [subtotal, ONE]
PC 24: [subtotal, ONE, FUEL_RATE]
PC 27: [subtotal, ONE.add(FUEL_RATE)] ← invokevirtual이 ONE.add(...) 실행
PC 30: [subtotal.multiply(...)] ← invokevirtual이 subtotal.multiply(...) 실행
PC 33: [] ← areturn
public int getCallCount();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #18 // callCount:I
4: ireturn
3줄에 모든 게:
1. this 푸시
2. this의 callCount 읽기
3. int 반환
→ Java의 getter는 정말 단순.
→ JIT가 인라이닝 처리 → 거의 0 비용.
2026-05-15 14:30:00 ERROR ShipmentService - 운임 계산 실패
java.lang.NoSuchMethodError:
'java.math.BigDecimal java.math.BigDecimal.multiply(java.math.BigDecimal)'
at com.ilic.ShipmentCalculator.calculate(ShipmentCalculator.java:13)
박승제씨의 분석 시작.
NoSuchMethodError:
특히 BigDecimal.multiply(BigDecimal):
의심:
# 운영 서버에서 클래스 추출
ssh prod-server
cd /var/log/dump/
jcmd <PID> Thread.print > thread.dump
# 실행 중인 JAR의 ShipmentCalculator.class 추출
unzip -o app.jar com/ilic/ShipmentCalculator.class
javap -c -v com/ilic/ShipmentCalculator.class > local-analysis.txt
local-analysis.txt의 Constant Pool 확인:
#28 = Methodref #7.#29
#7 = Class #8 // java/math/BigDecimal
#29 = NameAndType #30:#31
#30 = Utf8 multiply
#31 = Utf8 (Ljava/math/BigDecimal;)Ljava/math/BigDecimal;
→ 우리 클래스는 정상. BigDecimal.multiply(BigDecimal) 호출.
# 어느 BigDecimal이 로딩됐나?
jcmd <PID> VM.classloader_stats
# 또는
jcmd <PID> GC.class_stats | grep BigDecimal
출력:
java.math.BigDecimal loaded by com.malicious.BadClassLoader
예상 외 결과: BigDecimal이 표준 JDK가 아닌 다른 ClassLoader에서 로딩됨!
# JAR 안에 BigDecimal이 있나?
unzip -l app.jar | grep BigDecimal
발견:
com/malicious/BigDecimal.class
→ 누군가가 BigDecimal 이름의 클래스를 JAR에 넣음.
# 어떤 의존성에서 왔나?
./gradlew dependencyInsight --dependency BigDecimal
→ 특정 라이브러리가 자체 BigDecimal 가지고 있음. 클래스 우선순위 문제.
즉시 조치:
1. 문제 라이브러리 제거
2. 또는 ClassLoader 우선순위 조정
3. JDK BigDecimal이 먼저 로딩되게 보장
장기 조치:
1. 빌드 시 클래스 충돌 검출 도구 추가 (Maven Enforcer, Gradle Conflict Resolution)
2. JAR 무결성 검증
이 시나리오를 30분 내에 진단할 수 있게 된 것이 Phase 3 학습의 가치.
시간: 15:00 (에러 발생)
↓ 1분
에러 메시지 확인
↓ 5분
바이트코드 추출 + javap 분석
↓ 10분
ClassLoader 추적
↓ 10분
원인 발견 + 해결책 도출
시간: 15:30 (해결)
이게 바이트코드 읽기 능력의 진짜 가치.
String s = "Hello, " + name + "!";
Java 8 컴파일:
new #1 // StringBuilder
dup
invokespecial #2 // StringBuilder.<init>()
ldc #3 // "Hello, "
invokevirtual #4 // StringBuilder.append(String)
aload_1 // name
invokevirtual #4 // StringBuilder.append(String)
ldc #5 // "!"
invokevirtual #4
invokevirtual #6 // StringBuilder.toString()
astore_2
Java 9+ 컴파일:
ldc #3 // "Hello, "
aload_1
ldc #5 // "!"
invokedynamic #7 // makeConcatWithConstants
astore_2
→ Java 9+ 는 invokedynamic + StringConcatFactory 사용 (JEP 280).
→ 런타임에 JVM이 최적 전략 선택.
Java 16 이전 — switch 표현식 등장 전:
switch (status) {
case DRAFT: ...; break;
case ACTIVE: ...; break;
}
lookupswitch { ... } // 일반적인 분기
Java 17+ — pattern matching switch (preview):
return switch (obj) {
case Integer i -> "int: " + i;
case String s -> "str: " + s;
default -> "other";
};
invokedynamic #N // SwitchBootstraps.typeSwitch
→ 점점 더 invokedynamic 활용 증가.
public record Shipment(String blNo, BigDecimal freight) {}
javap -c -v 결과:
blNo(), freight(), equals(), hashCode(), toString()invokedynamic 으로 ObjectMethods 부트스트랩 사용// 일반 클래스의 equals/hashCode/toString:
// 수십 줄의 바이트코드
// record:
0: aload_0
1: aload_1
2: invokedynamic #1 // ObjectMethods.bootstrap
7: areturn
→ 같은 동작을 훨씬 짧은 바이트코드로.
public sealed class Cargo
permits DryCargo, ReeferCargo, HazmatCargo {}
.class 파일에 새 attribute:
PermittedSubclasses:
DryCargo
ReeferCargo
HazmatCargo
→ JVM이 컴파일 시점에 자식 클래스 목록 검증.
javac App.java
javap -c App.class
LocalVariableTable이 없으면:
0: aload_0
1: aload_1 // 변수 이름 모름
-g 옵션으로 컴파일하면:
0: aload_0 // this
1: aload_1 // shipment
→ 운영 디버깅 위해 -g:lines,vars 옵션 권장.
// 호출 사이트
shipment.calculate(100); ← 항상 ShipmentImpl1만 옴
JIT가 인라인 캐시 → 매우 빠름.
만약 어느 순간 다른 타입 등장:
shipment.calculate(100); ← 이번엔 ShipmentImpl2
캐시 미스 → 재해소.
메가모픽(megamorphic) 호출 사이트가 되면 캐시 효과 사라짐.
→ 다형성을 너무 다양하게 쓰면 성능 손실. 보통 문제 안 됨.
Method m = clazz.getMethod("calculate", int.class);
m.invoke(obj, 100);
내부적으로:
→ Reflection 한 번 vs 매번 호출의 차이.
// ❌ 비효율
for (int i = 0; i < 100; i++) {
clazz.getMethod("calc", int.class).invoke(obj, i);
}
// ✓ Method 객체 캐시
Method m = clazz.getMethod("calc", int.class);
for (int i = 0; i < 100; i++) {
m.invoke(obj, i);
}
| Q | 핵심 답변 |
|---|---|
dup 명령은 왜 필요? | new 후 객체 참조를 invokespecial이 소비. astore도 써야 해서 복제 |
invokevirtual vs invokespecial vs invokestatic 차이? | 다형성 / 정적 확정 / 객체 무관 (Unit 1.5 4종) |
getstatic vs getfield? | static 필드 / 인스턴스 필드 |
| Java 9+ String concat 바이트코드 차이? | StringBuilder → invokedynamic (JEP 280) |
| LocalVariableTable의 역할? | 디버그용 변수명 매핑 |
i2l 명령? | int → long 타입 변환 |
ireturn vs areturn? | int 반환 / 참조 반환 |
| record의 equals/hashCode 구현? | ObjectMethods.bootstrap + invokedynamic |
| 클래스 파일 verify의 역할? | 바이트코드 안전성 검증 (스택 일치, 점프 유효성) |
| Constant Pool 인덱스 시작? | #1부터 (#0은 reserved) |
javap -c -v 출력을 모든 줄 해독 가능dup, getfield, putfield, invoke* 등을 안다다음 질문에 즉답할 수 있다면 Phase 3 졸업:
String s = a + b; 가 Java 8과 Java 11에서 어떻게 다르게 컴파일되는가?invokevirtual #20 에서 #20이 정확히 무엇을 가리키는가?dup 명령이 새 객체 생성 시 왜 필요한가?즉답 가능 = Phase 3 졸업 + 2주차 정점 달성.
1. javap -c -v -p 한 줄이 바이트코드 분석의 전부
2. 자바 코드 ↔ 바이트코드는 1:N 매핑
callCount++ = 5개 바이트코드 명령new Shipment("BL") = new + dup + ldc + invokespecial+ 연산 = Java 9+에선 invokedynamic3. 운영 사고 디버깅의 강력한 무기
🎯 Phase 3 — 바이트코드와 상수 풀
✅ Unit 3.1 바이트코드란 무엇인가
✅ Unit 3.2 상수 풀의 생성과 구조
✅ Unit 3.3 심볼 참조
✅ Unit 3.4 바이트코드 실전 분석 ← 여기, Phase 3 완주
→ 박승제씨는 이제 .class 파일 분석 전문가
javap -c -v -p 출력을 처음부터 끝까지 해석이번 Phase에서 클래스 파일과 메서드 호출의 모든 것을 봤다면, 다음은 운영 환경의 메모리 관리.
1주차에서 GC 기본을 봤지만, 2주차 Phase 4는:
→ 박승제씨가 운영 서버의 OOM 트러블슈팅을 마스터하게 됨.
✅ Phase 1 — 자바 변수 ↔ 메모리 매핑 (1.1 ~ 1.6 완주)
✅ Phase 2 — JVM 메서드 실행 메커니즘 (2.1 ~ 2.4 완주)
✅ Phase 3 — 바이트코드와 상수 풀 (3.1 ~ 3.4 완주, 정점 달성)
🚀 Phase 4 — G1 GC 심화 (다음)
⏭ Phase 5 — 컬렉션 내부 구조
⏭ Phase 6 — Reflection & Iterator
⏭ Phase 7 — Buffer
Phase 1: 6개 Unit (1.1 ~ 1.6)
Phase 2: 4개 Unit (2.1 ~ 2.4)
Phase 3: 4개 Unit (3.1 ~ 3.4) ★ 정점
─────────────────────────────
누적: 14개 Unit