2주차 Unit 7.1 — Buffer의 본질

Psj·2026년 5월 18일

F-lab

목록 보기
75/230

Unit 7.1 — Buffer의 본질

F-LAB JAVA · 2주차 · Phase 7 · Buffer
🏁 2주차의 마지막 Unit — 졸업 직전


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • Buffer 가 자바에서 의미하는 것은?
  • Buffer의 4가지 속성 (capacity, position, limit, mark) 의 정확한 의미는?
  • flip(), clear(), rewind(), compact() 의 차이는?
  • Heap Buffer vs Direct Buffer 의 결정적 차이는?
  • Memory-Mapped File 의 동작은?
  • ILIC 운영에서 Direct Buffer 를 언제 써야 하나?
  • 2주차 졸업 — 박승제씨는 이제 무엇을 할 수 있는가?

🎯 핵심 한 문장

Buffer는 "고정된 크기의 메모리 블록 + 읽기/쓰기 위치 추적"이다.
1주차 NIO Unit에서 본 ByteBuffer가 가장 흔한 예이며,
이번 Unit에서는 메모리 관점에서 정밀하게 다시 본다.
Heap Buffer는 JVM 안, Direct Buffer는 JVM 밖 — 이 차이가 운영 성능을 좌우한다.

비유 — 박물관의 전시 공간

시스템비유
capacity전체 전시 공간 크기
position현재 작업 중인 위치
limit작업 가능한 끝 위치
mark북마크 (나중에 돌아갈 지점)
flip"쓰기 끝, 이제 읽을 차례" 전환
clear모든 것 리셋
Heap Buffer박물관 본관 (JVM Heap)
Direct Buffer별관 (Native Memory)

🧭 9개 섹션 로드맵

1. Buffer의 본질 — 1주차 복습 + 깊이
2. 4가지 속성 정밀 추적
3. 6가지 핵심 메서드
4. ByteBuffer와 타입별 Buffer
5. Heap Buffer vs Direct Buffer
6. Memory-Mapped File
7. ILIC 실무 — Buffer 활용
8. 흔한 실수 + 디버깅
9. 면접 + 🏆 2주차 졸업 시험

1️⃣ Buffer의 본질 — 1주차 복습 + 깊이

1.1 1주차 복습

박승제씨가 1주차 Unit 7.3에서 본 것:

  • ByteBuffer의 기본 사용
  • position, limit, capacity 개념
  • flip()의 의미
  • Channel과의 연동

API의 큰 그림.

1.2 2주차 Phase 7의 깊이

Phase 7의 추가:
  - 메모리 관점에서 Buffer 정밀
  - Heap vs Direct의 메모리 메커니즘
  - Memory-Mapped File의 OS 레벨 동작
  - 운영 시 Direct Buffer 모니터링
  - GC와 Buffer의 관계

1.3 Buffer의 메모리적 정의

Buffer = 두 가지의 결합:
  1. 메모리 블록 (Heap 또는 Native)
  2. 4가지 정수 속성 (위치 추적)
// 단순화한 Buffer 모델
abstract class Buffer {
    private int capacity;   // 총 크기
    private int limit;       // 작업 한계
    private int position;    // 현재 위치
    private int mark = -1;   // 북마크
}

→ 메모리 블록 + 4개 정수.
→ 이 단순한 구조가 모든 NIO의 기반.

1.4 자기 점검 — Buffer 모델 그려보기

Buffer의 4가지 속성을 종이에 그려보세요.

capacity = 10 인 Buffer:

  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │  │  │  │  │  │  │  │  │  │  │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
   0  1  2  3  4  5  6  7  8  9
                                   capacity = 10

position = 3, limit = 7:
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │XX│XX│XX│  │  │  │  │  │  │  │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
                ▲                ▲
            position           limit
            
의미:
  - 0~2: 이미 처리됨
  - 3~6: 현재 작업 영역
  - 7~9: 사용 불가 (limit 너머)

1.5 Buffer 클래스 계층

Buffer (abstract)
  ├── ByteBuffer
  │   ├── HeapByteBuffer (Heap에 byte[])
  │   ├── DirectByteBuffer (Native memory)
  │   ├── MappedByteBuffer (memory-mapped file)
  │   └── ReadOnlyByteBuffer
  ├── CharBuffer
  ├── ShortBuffer
  ├── IntBuffer
  ├── LongBuffer
  ├── FloatBuffer
  └── DoubleBuffer

→ primitive 타입별로 별도 Buffer.
→ ByteBuffer가 가장 기본 (다른 타입은 ByteBuffer로 변환 가능).


2️⃣ 4가지 속성 정밀 추적

2.1 capacity — 총 크기

ByteBuffer buf = ByteBuffer.allocate(10);
buf.capacity();   // 10
  • Buffer 생성 시 결정
  • 이후 변경 불가
  • 메모리 블록의 총 크기

2.2 position — 현재 위치

ByteBuffer buf = ByteBuffer.allocate(10);
buf.position();   // 0 (초기)

buf.put((byte) 1);
buf.position();   // 1

buf.put((byte) 2);
buf.position();   // 2
  • 다음 작업 (put/get) 이 일어날 위치
  • 자동 증가
  • 명시적 변경 가능: buf.position(5)

2.3 limit — 작업 한계

ByteBuffer buf = ByteBuffer.allocate(10);
buf.limit();   // 10 (초기 = capacity)

buf.limit(7);
buf.limit();   // 7
  • 작업 가능한 끝 (이 위치 직전까지)
  • position이 limit 도달 시 더 진행 불가
  • 변경 가능

2.4 mark — 북마크

ByteBuffer buf = ByteBuffer.allocate(10);
buf.position(3);
buf.mark();        // 현재 position(3) 기록
buf.position(7);
buf.reset();       // position을 mark(3)로 복원
  • 현재 position을 기록
  • reset()으로 그 위치로 복원
  • 초기값 -1 (mark 안 됨)

2.5 4가지 속성의 불변식

규칙: 0 ≤ mark ≤ position ≤ limit ≤ capacity

위반 시:
  - limit > capacity: IllegalArgumentException
  - position > limit: 자동 조정
  - mark > position: 자동 -1 (무효화)

2.6 ILIC 시나리오로 추적

// 1만 bytes 데이터 쓰기 → 읽기 시나리오
ByteBuffer buf = ByteBuffer.allocate(1000);

// 상태 1: 초기
// position=0, limit=1000, capacity=1000

buf.put("BL-001".getBytes());
// 상태 2: position=6, limit=1000, capacity=1000

buf.put(",10".getBytes());
// 상태 3: position=9, limit=1000, capacity=1000

buf.flip();
// 상태 4: position=0, limit=9, capacity=1000
//          ↑ 0~8 영역이 읽기 가능

byte[] result = new byte[9];
buf.get(result);
// 상태 5: position=9, limit=9, capacity=1000

buf.clear();
// 상태 6: position=0, limit=1000, capacity=1000
//          다시 쓰기 모드

→ 4가지 속성이 상태 머신처럼 작동.

2.7 자기 점검 답변

Buffer의 position과 limit이 같으면 무슨 의미?

:

  • 작업 더 못 함 (position이 limit 도달)
  • 읽기 모드면 → 모두 읽음
  • 쓰기 모드면 → 가득 참

hasRemaining():

boolean hasRemaining() {
    return position < limit;
}

3️⃣ 6가지 핵심 메서드

3.1 flip() — 쓰기 → 읽기

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

동작:

  • limit = 현재 position
  • position = 0
  • mark 무효화
Before flip (쓰기 모드, 9 bytes 썼음):
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │A │B │C │D │E │F │G │H │I │  │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
                                ▲                ▲
                            position=9       limit=10

After flip:
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │A │B │C │D │E │F │G │H │I │  │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
  ▲                            ▲
  position=0               limit=9
  
  → 0~8을 읽기 가능

flip = "방향 전환".

3.2 clear() — 모두 리셋

public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
}
  • position = 0
  • limit = capacity
  • 데이터는 그대로 (덮어쓰기 대상)
After clear:
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │A │B │C │D │E │F │G │H │I │  │   ← 데이터는 남아있음
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
  ▲                                ▲
  position=0                  limit=capacity

→ "처음부터 다시 쓰기" 상태.

3.3 rewind() — 다시 읽기

public final Buffer rewind() {
    position = 0;
    mark = -1;
    return this;
}
  • position = 0
  • limit 유지
After rewind (이전: 다 읽음):
  position=0, limit=9 (그대로)
  → 0~8을 다시 읽기 가능

→ "처음부터 다시 읽기".

3.4 compact() — 부분 처리 후 정리

public ByteBuffer compact() {
    System.arraycopy(hb, position, hb, 0, remaining());
    position(remaining());
    limit(capacity);
    discardMark();
    return this;
}

동작:

  • position부터 limit까지 (남은 데이터)를 앞으로 복사
  • position = 남은 데이터 수
  • limit = capacity
Before compact (일부 읽었음, 5~9는 안 읽음):
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │ ?│ ?│ ?│ ?│ ?│E │F │G │H │I │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
                       ▲              ▲
                   position=5     limit=10

After compact:
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
  │E │F │G │H │I │ ?│ ?│ ?│ ?│ ?│   ← 5~9를 0~4로 복사
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
                ▲                     ▲
            position=5            limit=10
            (다시 쓰기 시작 위치)

읽다 만 데이터 유지 + 추가 쓰기 가능 모드.
→ 스트림 처리에 유용.

3.5 mark() / reset()

buf.position(5);
buf.mark();       // mark=5 저장

buf.position(8);  // 어디론가 이동

buf.reset();      // position=5 복원

→ 임시 북마크.

3.6 메서드 비교 표

메서드positionlimitmark데이터
flip()0이전 position-1유지
clear()0capacity-1유지 (덮어쓰기 가능)
rewind()0유지-1유지
compact()남은 수capacity-1압축
mark()유지유지현재 position유지
reset()mark 값유지유지유지

3.7 일반 사용 패턴

ByteBuffer buf = ByteBuffer.allocate(1024);

// 1. 쓰기
buf.put(data);

// 2. flip — 읽기 모드로
buf.flip();

// 3. 읽기
while (buf.hasRemaining()) {
    process(buf.get());
}

// 4. clear — 다시 쓰기 모드로
buf.clear();

flip - 읽기 - clear 사이클.


4️⃣ ByteBuffer와 타입별 Buffer

4.1 ByteBuffer — 기본

ByteBuffer buf = ByteBuffer.allocate(1024);

// 다양한 타입 쓰기
buf.putInt(100);          // 4 bytes
buf.putLong(1000L);       // 8 bytes
buf.putDouble(3.14);      // 8 bytes
buf.put((byte) 1);        // 1 byte

buf.flip();

// 다양한 타입 읽기
int i = buf.getInt();
long l = buf.getLong();
double d = buf.getDouble();
byte b = buf.get();

byte 단위지만 다양한 primitive 타입 입출력 가능.

4.2 다른 타입 Buffer

IntBuffer ib = IntBuffer.allocate(100);
ib.put(1);
ib.put(2);
ib.put(3);

ib.flip();
int val = ib.get();   // 1

CharBuffer cb = CharBuffer.allocate(100);
cb.put('A');
cb.put('B');

→ 각 primitive 타입마다 Buffer 존재.

4.3 ByteBuffer를 다른 View로

ByteBuffer bb = ByteBuffer.allocate(1024);

// View Buffer 생성
IntBuffer ib = bb.asIntBuffer();
CharBuffer cb = bb.asCharBuffer();
DoubleBuffer db = bb.asDoubleBuffer();

같은 메모리를 다른 타입으로 해석.
메모리 효율적.
→ ByteBuffer 변경 시 View Buffer에도 반영.

4.4 Byte Order (Endianness)

ByteBuffer buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.BIG_ENDIAN);     // 네트워크 표준
// 또는
buf.order(ByteOrder.LITTLE_ENDIAN);  // x86

buf.putInt(0x12345678);
  • BIG_ENDIAN: 바이트 0x12, 0x34, 0x56, 0x78 순서
  • LITTLE_ENDIAN: 바이트 0x78, 0x56, 0x34, 0x12 순서

ByteBuffer 기본은 BIG_ENDIAN.
→ 네트워크 통신엔 BIG_ENDIAN.
→ 파일 포맷에 따라 다름.

4.5 wrap — 기존 배열 사용

byte[] data = "Hello".getBytes();
ByteBuffer buf = ByteBuffer.wrap(data);
// data 배열을 그대로 Buffer로

// 또는
ByteBuffer buf2 = ByteBuffer.wrap(data, 0, 3);
// 일부만

→ 메모리 복사 없이 Buffer 만듦.
→ Buffer 변경 = 원본 배열 변경.


5️⃣ Heap Buffer vs Direct Buffer

5.1 두 가지 메모리 위치

// Heap Buffer — JVM Heap 안
ByteBuffer heap = ByteBuffer.allocate(1024);

// Direct Buffer — JVM Heap 밖 (Native memory)
ByteBuffer direct = ByteBuffer.allocateDirect(1024);

5.2 Heap Buffer의 구조

JVM Heap:
  ┌──────────────────────────┐
  │ HeapByteBuffer 객체        │
  │   - byte[] hb (실제 메모리) │
  │   - position, limit, ...   │
  └──────────────────────────┘

→ GC 대상
→ 일반 자바 객체와 동일한 메모리 관리

5.3 Direct Buffer의 구조

JVM Heap:
  ┌──────────────────────────┐
  │ DirectByteBuffer 객체      │
  │   - address (Native 주소)  │ ──┐
  │   - position, limit, ...   │   │
  └──────────────────────────┘   │
                                   │
Native Memory (JVM Heap 밖):       │
  ┌──────────────────────────┐   │
  │ 실제 byte 데이터            │ ◄─┘
  └──────────────────────────┘

→ GC 대상 아님 (자동 회수 X)
→ malloc / free 방식으로 관리

5.4 차이점 비교

항목Heap BufferDirect Buffer
위치JVM HeapNative Memory
GC자동객체만 GC, 메모리는 finalizer
할당 비용낮음높음
I/O 성능보통 (복사 필요)빠름 (직접 OS 호출)
메모리 한계-XmxOS 메모리
디버깅쉬움어려움

5.5 I/O 성능 차이

파일 / 네트워크 I/O 시:

Heap Buffer 사용:
  1. JVM이 일시적으로 Direct Buffer 만듦
  2. Heap Buffer → Direct Buffer 복사
  3. Direct Buffer를 OS에 전달
  4. OS가 I/O 수행
  → 추가 복사 = 비용

Direct Buffer 사용:
  1. OS에 직접 전달
  2. OS가 I/O 수행
  → 복사 없음 = 빠름

I/O 빈번한 환경에선 Direct Buffer.

5.6 Direct Buffer의 함정

// ❌ 매번 새 Direct Buffer
for (int i = 0; i < 10000; i++) {
    ByteBuffer buf = ByteBuffer.allocateDirect(1024);
    // ... 사용
}

문제:

  • malloc 비용 ↑ (Heap allocation의 수 배)
  • finalize 시 free (지연됨)
  • 결국 OutOfMemoryError: Direct buffer memory

해결:

// ✓ 풀링
private static final ByteBuffer[] POOL = new ByteBuffer[16];
// 또는 Netty의 PooledByteBufAllocator 같은 라이브러리

5.7 Direct Buffer 모니터링

# Direct Buffer 사용량 확인
jcmd <PID> VM.native_memory summary

# 출력:
#   Internal (reserved=8MB, committed=8MB)
#     (malloc=8MB)
#   ...
#   ByteBuffer (reserved=256MB, committed=256MB)
#     (malloc=256MB)

또는 옵션:

-XX:NativeMemoryTracking=summary

ILIC 운영에서 Native Memory 모니터링 권장.

5.8 ILIC 권장

일반 데이터 처리:
  → HeapByteBuffer 충분
  
I/O 빈번 (Netty, NIO 서버):
  → DirectByteBuffer 풀링
  
큰 파일 처리:
  → MappedByteBuffer (다음 섹션)

6️⃣ Memory-Mapped File

6.1 개념

RandomAccessFile file = new RandomAccessFile("large.dat", "rw");
FileChannel channel = file.getChannel();

MappedByteBuffer mapped = channel.map(
    FileChannel.MapMode.READ_WRITE, 0, channel.size());

// 이제 mapped는 파일 자체를 가리킴!
mapped.put(0, (byte) 'A');   // 파일에 즉시 반영
byte b = mapped.get(100);     // 파일에서 직접 읽음

6.2 메모리적 의미

OS의 Virtual Memory:
  ┌──────────────────────────┐
  │ 프로세스 가상 주소 공간      │
  │                            │
  │   일반 메모리                │
  │   Heap                     │
  │   ...                      │
  │   ┌──────────────────────┐│
  │   │ Memory-Mapped Region ││ ◄── 이 영역이 파일과 매핑
  │   └──────────────────────┘│
  └──────────────────────────┘
              ▲
              │
              ▼ (OS가 자동으로)
  ┌──────────────────────────┐
  │ 디스크의 파일               │
  └──────────────────────────┘

→ 파일을 메모리처럼 접근.
→ 읽기/쓰기 = 가상 메모리 접근.
→ OS가 페이지 단위로 자동 로드/저장.

6.3 장점

1. 매우 큰 파일 처리
   - 전체를 메모리에 로딩 안 함
   - 필요한 부분만 OS가 로드

2. 빠름
   - OS 캐시 활용
   - 시스템 콜 감소

3. 동시 접근
   - 여러 프로세스가 같은 파일 매핑 가능
   - 공유 메모리처럼 사용

6.4 단점

1. 운영 체제 종속
2. 매핑 비용 (작은 파일엔 비효율)
3. 매핑 해제 어려움 (Java에선 GC 의존)
4. 32-bit 환경 제한 (4GB)
5. 디버깅 어려움

6.5 ILIC 사용 시나리오

적합:
  - 큰 CSV 파일 (수 GB) 부분 분석
  - 큰 로그 파일 검색
  - 데이터베이스 파일 직접 접근

부적합:
  - 작은 파일 (1MB 이하)
  - 자주 열고 닫는 파일
  - 일반 비즈니스 로직

6.6 사용 예 — 큰 파일 검색

public boolean searchInFile(Path path, byte[] pattern) throws IOException {
    try (RandomAccessFile file = new RandomAccessFile(path.toFile(), "r");
         FileChannel channel = file.getChannel()) {
        
        MappedByteBuffer mapped = channel.map(
            FileChannel.MapMode.READ_ONLY, 0, channel.size());
        
        // 매핑된 영역에서 검색
        for (long i = 0; i < mapped.capacity() - pattern.length; i++) {
            boolean match = true;
            for (int j = 0; j < pattern.length; j++) {
                if (mapped.get((int) (i + j)) != pattern[j]) {
                    match = false;
                    break;
                }
            }
            if (match) return true;
        }
        
        return false;
    }
}

→ 10GB 파일도 빠르게 검색.
→ 메모리에 전체 로딩 안 함.


7️⃣ ILIC 실무 — Buffer 활용

7.1 자주 만나는 상황

ILIC에서 Buffer 사용:
  - 파일 입출력 (배치)
  - 네트워크 통신 (HTTP, Socket)
  - PDF/Excel 생성
  - JSON 직렬화
  - 압축/암호화 처리

→ 대부분 라이브러리가 내부적으로 처리.

7.2 박승제씨가 직접 다룰 일

// 1. 큰 파일 청크 단위 읽기
try (FileChannel channel = FileChannel.open(path)) {
    ByteBuffer buf = ByteBuffer.allocate(8192);
    while (channel.read(buf) > 0) {
        buf.flip();
        processChunk(buf);
        buf.clear();
    }
}

// 2. HTTP Response 스트리밍
HttpClient client = HttpClient.newHttpClient();
HttpResponse<InputStream> response = client.send(
    request, HttpResponse.BodyHandlers.ofInputStream());

byte[] chunk = new byte[4096];
try (InputStream is = response.body()) {
    int n;
    while ((n = is.read(chunk)) > 0) {
        process(chunk, n);
    }
}

// 3. Netty 같은 프레임워크
// 내부적으로 ByteBuf (Netty 자체 Buffer) 사용

7.3 Spring Boot의 Buffer 활용

Spring Boot 앱 내부:
  - DispatcherServlet의 요청/응답 Buffer
  - Jackson의 직렬화 Buffer
  - Tomcat/Undertow의 네트워크 Buffer
  - JPA의 데이터베이스 입출력
  - SLF4J의 로그 Buffer

→ 박승제씨는 거의 의식 안 함.

7.4 메모리 누수 디버깅

# Native Memory 누수 의심 시
jcmd <PID> VM.native_memory summary

# 변화 추적
jcmd <PID> VM.native_memory baseline
# ... 시간 경과 후
jcmd <PID> VM.native_memory summary.diff

Direct Buffer 누수 패턴:

  • 매번 새 Direct Buffer 생성
  • 풀링 없음
  • finalize 의존
  • → OOM: Direct buffer memory

7.5 운영 옵션

# Direct Buffer 최대 크기 제한
-XX:MaxDirectMemorySize=256m

# 기본값은 Heap 크기와 비슷
# 명시적 설정 권장 (특히 큰 힙)

7.6 ILIC 권장 패턴

// 작은 데이터: HeapBuffer
ByteBuffer small = ByteBuffer.allocate(1024);

// I/O 위주: DirectBuffer 풀링
private static final ThreadLocal<ByteBuffer> IO_BUFFER = 
    ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(8192));

// 큰 파일: MappedByteBuffer
try (FileChannel ch = ...) {
    MappedByteBuffer mapped = ch.map(READ_ONLY, 0, ch.size());
}

8️⃣ 흔한 실수 + 디버깅

실수 1 — flip 빼먹기

// ❌
buf.put(data);
// flip 없이
buf.get();   // 0번 위치부터 읽기 시작, 잘못된 데이터

해결:

buf.put(data);
buf.flip();   // 반드시!
buf.get();

실수 2 — clear vs compact 혼동

// ❌ 부분 읽기 후 clear
buf.get(part1);   // 일부만 읽음
buf.clear();      // 안 읽은 데이터 손실!

해결:

buf.get(part1);
buf.compact();   // 안 읽은 데이터 보존

실수 3 — Direct Buffer 누수

// ❌
public void process() {
    ByteBuffer buf = ByteBuffer.allocateDirect(1024);
    // ... 사용
    // buf 해제 안 함 → Native memory 누적
}

해결:

  • 풀링 사용
  • 또는 사용 후 명시적 해제 (sun.misc.Cleaner — 비공개 API)
  • Netty의 ByteBuf 라이브러리

실수 4 — Byte Order 무시

// 다른 시스템과 통신 시
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(1234);

// 받는 쪽이 LITTLE_ENDIAN 가정하면 깨짐

해결:

buf.order(ByteOrder.LITTLE_ENDIAN);   // 명시적

실수 5 — capacity 변경 시도

ByteBuffer buf = ByteBuffer.allocate(10);
// buf의 capacity 늘리고 싶음
// ❌ Buffer는 immutable capacity

해결:

  • 새 Buffer 만들기
  • 또는 큰 capacity로 시작

실수 6 — 멀티스레드 Buffer 공유

private final ByteBuffer shared = ByteBuffer.allocate(1024);

// Thread A
shared.put(...);

// Thread B (동시)
shared.put(...);
// → 데이터 깨짐

→ Buffer는 thread-unsafe.
→ ThreadLocal 또는 동기화.

실수 7 — 작은 데이터에 Direct Buffer

// ❌ 작은 데이터에 Direct
ByteBuffer buf = ByteBuffer.allocateDirect(64);

Direct 할당은 비쌈 (수 μs).
작은 데이터엔 HeapBuffer가 빠름.

→ Direct는 재사용 + 큰 I/O 시.

디버깅 도구

# Direct Buffer 사용량
jcmd <PID> VM.native_memory summary

# Heap dump (Buffer는 객체로 보임)
jmap -dump:format=b,file=heap.hprof <PID>

# 운영 옵션
-XX:NativeMemoryTracking=summary
-XX:MaxDirectMemorySize=256m

9️⃣ 면접 + 🏆 2주차 졸업 시험

9.1 면접 단골 질문 매핑

Q핵심 답변
Buffer의 4가지 속성?capacity, position, limit, mark
flip()의 동작?limit=position, position=0
Heap vs Direct?JVM Heap 안 vs Native Memory
Direct Buffer 빠른 이유?OS와 직접 통신, 복사 없음
Direct Buffer 위험?할당 비싸고, GC 지연으로 누수
Memory-Mapped File?파일을 가상 메모리로 매핑
Byte Order 의미?BIG/LITTLE ENDIAN. 바이트 저장 순서
flip vs rewind?flip은 limit도 변경, rewind는 position만
clear vs compact?전체 리셋 vs 안 읽은 데이터 보존
MaxDirectMemorySize?Direct Buffer 최대. Heap과 별개

9.2 자기 점검 체크리스트

기본 이해

  • Buffer의 4가지 속성을 안다
  • flip/clear/compact/rewind 차이를 안다
  • Heap vs Direct Buffer 차이를 안다
  • Memory-Mapped File을 이해한다
  • Byte Order의 의미를 안다

실전 적용

  • 적절한 Buffer 선택 가능
  • Direct Buffer 누수 방지
  • 멀티스레드 Buffer 안전 사용
  • Native Memory 모니터링
  • 큰 파일 처리 시 MappedByteBuffer

면접 대비 — 5분 답변

  • Buffer의 본질과 4가지 속성
  • Heap vs Direct 트레이드오프
  • flip/compact 사용 시점
  • Memory-Mapped File 사례
  • 운영 시 Direct Memory 관리

🎯 핵심 요약 — 3줄 정리

1. Buffer = 메모리 블록 + 4가지 속성

  • capacity, position, limit, mark
  • flip/clear/compact/rewind 가 핵심 메서드
  • ByteBuffer 가 가장 기본

2. Heap vs Direct

  • Heap: JVM 안, GC 대상, 일반 사용
  • Direct: JVM 밖, GC X, I/O 빠름, 풀링 필수
  • MappedByteBuffer: 파일을 가상 메모리로

3. ILIC 실무

  • 일반 코드: 라이브러리가 알아서
  • 박승제씨 직접 사용: 큰 파일, 청크 처리, 스트리밍
  • Direct Buffer 모니터링: jcmd VM.native_memory

🏆 2주차 졸업 — 마스터 달성

2주차 전체 진행:

✅ Phase 1 — 자바 변수 ↔ 메모리 매핑 (1.1 ~ 1.6 완주)
✅ Phase 2 — JVM 메서드 실행 메커니즘 (2.1 ~ 2.4 완주)
✅ Phase 3 — 바이트코드와 상수 풀 (3.1 ~ 3.4 완주, 정점)
✅ Phase 4 — G1 GC 심화 (4.1 ~ 4.5 완주, 운영 마스터)
✅ Phase 5 — 컬렉션 내부 구조 (5.1 ~ 5.4 완주, 자료구조 마스터)
✅ Phase 6 — Reflection & Iterator (6.1 ~ 6.2 완주, 동적 마스터)
✅ Phase 7 — Buffer (7.1 완주, 메모리 I/O 마스터) ← 여기

🏆 2주차 완주 — 모든 Phase 마스터

2주차 전체 학습 통계

총 Unit 수: 26개

Phase별:
  Phase 1: 6 Unit (메모리)
  Phase 2: 4 Unit (메서드 실행)
  Phase 3: 4 Unit (바이트코드 정점)
  Phase 4: 5 Unit (GC 마스터)
  Phase 5: 4 Unit (컬렉션 마스터)
  Phase 6: 2 Unit (동적 메커니즘)
  Phase 7: 1 Unit (Buffer)
  
총 페이지: 추정 1000+

2주차 졸업 종합 시험

다음 질문에 즉답할 수 있다면 2주차 졸업:

  1. new Shipment("BL-001").calculate(100) 한 줄이 JVM에서 일어나는 모든 일은?
  2. javap -c -v Shipment.class 출력의 모든 줄을 해석할 수 있는가?
  3. 운영 서버에서 Full GC 5초씩 발생할 때 디버깅 순서는?
  4. ArrayList vs LinkedList vs ArrayDeque의 결정 기준은?
  5. Spring @Autowired 가 private 필드에 어떻게 주입하는가?
  6. Direct Buffer를 매번 새로 만들면 어떤 문제가 발생하는가?

모두 답할 수 있다면, 박승제씨는 자바 메모리와 JVM 마스터.


📚 다음으로...

2주차 학습 자료 종합 정리

박승제씨가 작성한 모든 자료:

1주차:
  Unit 6.4 HashMap과 LoadFactor (MD + PPT)
  Unit 7.1 try-with-resources
  Unit 7.2 NIO2 Files/Path
  Unit 7.3 NIO Channel/Buffer
  Unit 7.4 Serializable/transient

2주차:
  Phase 1 (6개): 자바 변수 ↔ 메모리 매핑
  Phase 2 (4개): JVM 메서드 실행
  Phase 3 (4개): 바이트코드와 상수 풀 (정점)
  Phase 4 (5개): G1 GC 심화
  Phase 5 (4개): 컬렉션 내부 구조
  Phase 6 (2개): Reflection & Iterator
  Phase 7 (1개): Buffer
  
총: 31개 학습 자료 (markdown) + 1 PPT (HashMap)
profile
Software Developer

0개의 댓글