1️⃣ 자바의 메모리 영역과 컴파일
a. 메모리 영역
-
자바 프로그램이 실행되면 JVM(자바 가상 머신)은 OS로부터 메모리를 할당받고, 그 메모리를 용도에 따라서 여러 영역으로 나누어 관리를 한다.
JVM의메모리 공간(Runtime Data Area)은 크게 Method(Static) 영역, Stack 영역, Heap 영역으로 구분되고 데이터 타입(자료형)에 따라 각 영역에 나눠서 할당 되게 된다.

- 컴퓨터의 메모리는 사용할 수 있는 공간이 한정되어 있기 때문에 어떻게 관리하느냐에 따라서 프로그램의 성능(속도 등)이 좌우된다. 따라서 Java 어플리케이션에서 메모리를 효율적으로 사용하기 위해서는 메모리 구조와 특징에 대해 이해할 필요가 있다.
-
변수는 크게 네 종류로 변수의 선언된 위치에 따라서 클래스변수, 인스턴스변수, 지역변수, 매개변수로 나뉜다.
- 클래스 변수: 객체 사이에 공유하는 변수로 클래스 영역에서 static으로 선언 → 클래스가 메모리에 올라갈 때 Method 영역에서 할당된다.
- 인스턴스 변수: 객체/인스턴스마다 다른 값 저장하는 변수로 클래스 영역에서 선언 → Heap 영역에서 할당되고 참조 변수가 없는 경우 가비지 컬렉터에 의해 자동 제거됨
- 지역변수: 메서드 영역에서 선언되는 변수로 메서드 끝나면 소멸된다.
- 매개변수: 메서드 호출시 전달되는 값을 저장하는 변수로 메서드 끝나면 소멸된다.
영역 |
역할 |
저장 데이터 |
공유 여부 |
생명주기 |
Method Area |
클래스 구조 및 메타데이터 저장 |
클래스 메타데이터, 메서드 메타데이터, 상수, 정적(static)변수 |
모든 스레드가 공유 |
JVM이 종료될 때까지 유지 |
Heap |
객체와 배열 저장 |
객체, 배열, 객체 내부의 멤버 변수 |
모든 스레드가 공유 |
Garbage Collector가 관리 |
Stack |
메서드 호출과 지역 변수 관리 |
지역 변수, 메서드 호출 정보, 참조 변수 |
스레드별로 독립 |
메서드 종료 시 자동 해제 |
b. 컴파일 과정 순서
- 개발자가 자바 소스코드(.java)를 작성한다.
- 자바 컴파일러(Java Compiler)가 자바 소스파일을 컴파일한다. 이때 나오는 파일은 자바 바이트 코드(.class)파일로 아직 컴퓨터가 읽을 수 없는 자바 가상 머신이 이해할 수 있는 코드이다.
- 컴파일된 바이트 코드를 JVM의 클래스로더(Class Loader)에게 전달한다.
- 클래스 로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data area), 즉 JVM의 메모리에 올린다.
- 클래스 로더 세부 동작
- 로드 : 클래스 파일을 가져와서 JVM의 메모리에 로드한다.
- 검증 : 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사한다.
- 준비 : 클래스가 필요로 하는 메모리를 할당한다. (필드, 메서드, 인터페이스 등등)
- 분석 : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.
- 초기화 : 클래스 변수들을 적절한 값으로 초기화한다. (static 필드)
- 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다. 이때, 실행 엔진은 두가지 방식으로 변경한다.
- 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나하나의 실행은 빠르나, 전체적인 실행 속도가 느리다는 단점을 가진다.
- JIT 컴파일러(Just-In-Time Compiler) : 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고 이후에는 해당 메서드를 더이상 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식이다. 하나씩 인터프리팅하여 실행하는 것이 아니라 바이트 코드 전체가 컴파일된 바이너리 코드를 실행하는 것이기 때문에 전체적인 실행속도는 인터프리팅 방식보다 빠르다.
2️⃣ 가비지 컬렉션(Garbage Collection)
- 정의와 동작 방식
-
가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스를 말한다.
-
가비지 컬렉션의 동작 방식은 Mark-Sweep이란 다양한 GC에서 사용되는 객체를 솎아내는 내부 알고리즘이다. Mark-Sweep의 원리는 가비지 컬렉션이 될 대상 객체를 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행하게 된다.
-
Heap영역은 GC의 효율적인 관리를 위해 크게 두 가지 영역으로 나누게 된다.
-
Young 영역(Young Generation)
-
새롭게 생성된 객체가 할당(Allocation)되는 영역
-
대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
-
Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부른다.
-
Young 영역을3가지 영역(Eden, survivor 0, survivor 1) 으로 나눈다.
-
Old 영역(Old Generation)
-
Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
-
Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
-
Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC 또는 Full GC라고 부른다.
⭐️ Yung 영역의 Eden부터 채워지면서 가득차게 되면 GC(Mark)를 통해 참조되지 않는 객체를 제거하고 살아 남은 객체들의 age가 1개 증가허고 survivor 0 또는 survivor 1 로 넘어가게된다. survivor 0 또는 survivor 1로 넘어간 객체들이 Eden이 다시 가득찰때마다 GC의 Mark 검사를 받게 되어 식제 되거나 survivor 0 또는 survivor 1 사이에 age값이 계속 증가하면서 이동하게 된다. 하지만 age값이 임계값을 도달하게 되면 그 객체가 Old 영역에 넘어가게된다.
→ Major GC는 Old 영역은 데이터가 가득 차면 GC를 실행하는 단순한 방식이다.
Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 Major GC가 실행되게 된다.하지만 Old Generation은 Young Generation에 비해 상대적으로 큰 공간을 가지고 있어, 이 공간에서 메모리 상의 객체 제거에 많은 시간이 걸리게 된다.
예를들어 Young 영역은 일반적으로 Old 영역보다 크키가 작기 때문에 GC가 보통 0.5초에서 1초 사이에 끝난다. 그렇기 때문에 Minor GC는 애플리케이션에 크게 영향을 주지 않는다. 하지만 Old 영역의 Major GC는 일반적으로 Minor GC보다 시간이 오래걸리며, 10배 이상의 시간을 사용한다.
바로 여기서 모든 스레드를 멈추고 GC 스레드만 남기는 현상인 Stop-The-World 문제가 발생하게 된다. Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 현상이 일어난다.
-
Python의 Garbage Collection 동작 원리 (다른 프로그래밍 언어의 GC 비교)
- GC 동작 원리:
- Python은 참조 카운팅(Reference Counting)과 사이클 감지(Cycle Detection) 기반으로 GC 동작
- 참조 카운팅(Reference Counting): 객체가 생성되면 참조 카운트는 1로 설정 → 다른 변수가 해당 객체를 참조할 경우, 참조 카운트가 1씩 증가 → 객체를 참조하던 변수가 삭제되거나 새로운 값을 할당받으면 참조 카운트가 1씩 감소 → 카운트가 0되면 메모리를 즉시 해제
but 순환 참조(Circular Reference) 문제를 해결하지 못한다.
- 사이클 감지(Cycle Detection): GC 모듈은 순환 참조(circular references)를 탐지하고 이를 처리합니다. but 추가적인 탐색 비용이 발생하며, 성능이 저하될 수 있습니다.
→ Python은 객체의 생성과 소멸 시점을 명확히 알 수 있지만, 순환 참조가 있을 경우 별도의 감지가 필요하다. 성능은 자바보다 낮을 수 있습니다.
-
Java GC의 여러 종류
-
Serial GC
- 서버의 CPU 코어가 1개일 때 사용하기 위해 개발된 가장 단순한 GC
- GC를 처리하는 쓰레드가 1개 (싱글 쓰레드) 이어서 가장 stop-the-world 시간이 길다
- Minor GC 에는 Mark-Sweep을 사용하고, Major GC에는 Mark-Sweep-Compact를 사용한다.
- 보통 실무에서 사용하는 경우는 없다 (디바이스 성능이 안좋아서 CPU 코어가 1개인 경우에만 사용)
- Parallel GD
- Java 8의 디폴트 GC
- Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 쓰레드로 수행 (Old 영역은 여전히 싱글 쓰레드)
- Serial GC에 비해 stop-the-world 시간 감소
- Parallel Old GC
- Parallel GC를 개선한 버전
- Young 영역 뿐만 아니라, Old 영역에서도 멀티 쓰레드로 GC 수행
- 새로운 가비지 컬렉션 청소 방식인 Mark-Summary-Compact 방식을 이용 (Old 영역도 멀티 쓰레드로 처리)
- CMS GC
- 어플리케이션의 쓰레드와 GC 쓰레드가 동시에 실행되어 stop-the-world 시간을 최대한 줄이기 위해 고안된 GC
- 단, GC 과정이 매우 복잡해짐.
- GC 대상을 파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다
- 메모리 파편화 문제
- CMS GC는 Java9 버젼부터 deprecated 되었고 결국 Java14에서는 사용이 중지
- G1 GC
- CMS GC를 대체하기 위해 jdk 7 버전에서 최초로 release된 GC
- Java 9+ 버전의 디폴트 GC로 지정
- 4GB 이상의 힙 메모리, Stop the World 시간이 0.5초 정도 필요한 상황에 사용 (Heap이 너무작을경우 미사용 권장)
- 기존의 GC 알고리즘에서는 Heap 영역을 물리적으로 고정된 Young / Old 영역으로 나누어 사용하였지만, G1 gc는 아예 이러한 개념을 뒤엎는 Region이라는 개념을 새로 도입하여 사용.전체 Heap 영역을 Region이라는 영역으로 체스같이 분할하여 상황에 따라 Eden, Survivor, Old 등 역할을 고정이 아닌 동적으로 부여
- Garbage로 가득찬 영역을 빠르게 회수하여 빈 공간을 확보하므로, 결국 GC 빈도가 줄어드는 효과를 얻게 되는 원리
- Shenandoah GC
- Java 12에 release
- 레드 햇에서 개발한 GC
- 기존 CMS가 가진 단편화, G1이 가진 pause의 이슈를 해결
- 강력한 Concurrency와 가벼운 GC 로직으로 heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요가 특징
- ZGC (Z Garbage Collector)
- Java 15에 release
- 대량의 메모리(8MB ~ 16TB)를 low-latency로 잘 처리하기 위해 디자인 된 GC
- G1의 Region 처럼, ZGC는 ZPage라는 영역을 사용하며, G1의 Region은 크기가 고정인데 비해, ZPage는 2mb 배수로 동적으로 운영됨.
- ZGC가 내세우는 최대 장점 중 하나는 힙 크기가 증가하더도 'stop-the-world'의 시간이 절대 10ms를 넘지 않는다는 것
Q) 그럼 많은 자바 GC 중에서 선택할 때 어떤 것을 고려해야할까?
- 실시간 처리(낮은 지연 시간 중요) → ZGC, Shenandoah GC, G1 GC
- 대규모 데이터 처리(높은 처리량 중요) → Parallel GC, Parallel Old GC
- 소규모 애플리케이션 → Serial GC, G1 GC
- 대규모 메모리(4GB 이상) → G1 GC, ZGC
- 클라우드 기반의 대규모 서비스 → ZGC, Shenandoah GC
- 일반적인 엔터프라이즈 애플리케이션 → G1 GC
3️⃣ JVM관점에 static을 왜 조심해서 써야하는지 서술하시오
- static 변수는 Runtime Data Area의 Method 영역에서 저장된다. 하지만 Method 영역에서 특정 클래스는 JVM이 실행 중일 때 최초로 참조될 때 한 번만 로드되며, 이후에는 Method Area에 저장된 내용을 사용. → 즉, GC의 영역이 아니기 때문에 사용하지 않는 static 변수가 자동으로 제거가 되지 않고 프로그램이 샐행되는 동안 계속 메모리에 남아 있다.
4️⃣ 자바의 OOP, 추상화, 캡슐화
- OOP (Object Oriented Programming)
자바에서 객체지향 프로그래밍(OOP)은 객체(책임/역할)를 중심으로 설계하고 구현하는 프로그래밍 방법이다. 객체지향프로그래밍의 중요한 특성은으로 크게 캡슐화, 추상화, 다형성, 상속성이 있다.
또한, 자바 OOP에서 유지보수 가능, 확장 가능, 가독성 높이기 위해 SOLID 원칙이라는 5가지 주요 원칙이 있다.
SOLID 원칙 5가지
- Single Responsibility Principle (단일 책임 원칙) : 클래스는 단 하나의 책임(역할)만 가져야 한다.
- Open/Closed Principle (개방-폐쇄 원칙) : 소프트웨어 요소는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
- Liskov Substitution Principle (리스코프 치환 원칙) : 상위 클래스의 객체는 하위 클래스의 객체로 대체할 수 있어야 한다.
- Interface Segregation Principle (인터페이스 분리 원칙): 특정 클라이언트에 맞는 인터페이스만 제공해야 하며, 사용하지 않는 메서드는 포함하지 말아야 한다.
- Dependency Inversion Principle (의존 역전 원칙) : 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화(인터페이스)에 의존해야 한다.
- 추상화 (Abstraction)
추상의 사전적인 의미는 "여러가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용"이다. 그러므로 추상화는 여러 개체들을 분해해서 찾을 수 있는 공통되는 특성을 관심 영역에 따라 재조합하는 것이라고 이해할 수 있다.
예를 들어
- 관심 영역(애플리케이션의 컨텍스트)를 병원으로 설정한다면 사람은 환자이고 환자의 공통적인 행위는 먹다, 자다 된다.
- 은행으로 설정한다면 사람은 고객이되고 고객의 공통적인 행위는 입금하다, 출금하다, 대출하다 등이 된다.
이러한 행위들은 클래스 안의 메서드로 추상화시킬 수 있다.
마찬가지로 환자(사람)의 다양한 속성 시력, 몸무게, 고객의 직업, 연봉 등도 클래스 안의 속성, 즉 필드로 추상
→ 따라서 추상화는 모델링이며, 클래스는 추상화의 결과라고 말할 수 있다.
→ 이렇게 추상적으로 끄집어 낸 개념들을 큰 틀에서 클래스로 만든것이 바로 추상클래스이다.