[LG U+ 유레카 4기] WEEK 01 - Java 기초 객체지향언어 (1)

Soohwan Lim·6일 전

유레카부트캠프

목록 보기
3/8
post-thumbnail

Java 기초 문법부터 JVM 메모리 구조까지


1. 오늘의 학습 흐름

  • Java / Eclipse 개발 환경 설정 (Java 21.0.10, Eclipse STS 5.1.1)
  • Java 언어 특성: 탄생 배경, JVM, Write Once Run Anywhere
  • 기본 자료형(Primitive) vs 참조형(Reference) 타입
  • 형변환 (자동 형변환 / 명시적 캐스팅)
  • 연산자 (비트 연산, 논리 연산, printf 포맷)
  • 제어문: if / switch / for / while / break / continue
  • 메서드 기초
  • 배열: 1차원, 2차원, 배열 복사 (arraycopy / Arrays.copyOf)
  • Pass By Value (기본형 vs 참조형 인자 전달 방식)
  • JVM Heap 메모리 구조 및 GC 알고리즘 (Java8 전/후)

2. Deep Dive: JVM은 메모리를 어떻게 관리하는가

Java가 왜 등장했는가

Java는 원래 임베디드 기기 용으로 설계됐다. 당시 문제는 하드웨어 아키텍처가 제각각이라 플랫폼마다 코드를 따로 짜야 했다는 것. 해결책이 바로 JVM이다. 임베디드 기기 안에 JVM을 넣고, 자바 코드는 바이트코드로 컴파일한 뒤 JVM 위에서 실행하면 하드웨어에 종속되지 않는다. Write Once, Run Anywhere. 지금은 서버사이드 개발의 주력 언어가 됐지만, 탄생 배경을 알면 설계 철학이 훨씬 납득이 간다.

Java가 엔터프라이즈 시장에서 오래 살아남은 이유는 세 가지라고 생각한다.
1. 객체지향 강제: 수백 명이 협업하는 대규모 서비스에서 코드 구조가 자연스럽게 잡힌다.
2. 풍부한 오픈소스 생태계: Spring, Hibernate 등 검증된 라이브러리가 넘친다.
3. GC 자동화: 메모리 관리를 JVM이 해주니까 개발 생산성이 올라간다.

요즘 시대에 하드웨어 성능은 계속 올라가고, 클라우드 인프라 비용보다 개발자 인건비가 훨씬 비싸다. Java/Spring Boot의 생산성과 유지보수성이 그 비용을 충분히 상쇄한다.


Primitive vs Reference: 스택과 힙의 차이

자바 타입 시스템의 핵심은 값이 어디에 저장되느냐다.

구분종류저장 위치특징
Primitiveint, double, boolean, char 등 8가지Stack실제 값 직접 저장, 빠른 접근
ReferenceClass, Interface, Array, EnumHeap (객체) + Stack (주소값)객체는 힙에, 스택엔 hashcode(주소)만

C 포인터와 비교하면 Java 참조는 주소 연산이 안 되고 JVM이 관리하는 논리적 주소만 보유한다. 덕분에 NullPointerException 말고는 메모리 관련 사고가 거의 안 난다. C는 free() 직접 호출해야 하고 잘못 건드리면 세그멘테이션 폴트가 난다. 안정성 측면에서 Java가 훨씬 낫다.


JVM Heap 메모리 구조 (Java 8 이전)

객체가 생성되면 Heap 안에서 이렇게 이동한다.

[Eden] → [Survivor1 or Survivor2] → (왔다갔다) → [Old] → GC로 회수
                                                      ↑
                                               오래 살아남은 객체

New/Young 영역

  • 객체 생성 시 Eden에 먼저 배치
  • Eden 꽉 차면 참조되고 있는 객체만 Survivor1 또는 Survivor2로 이동 (Minor GC)
  • Survivor1/2 중 하나는 항상 비어 있어야 한다 (이게 포인트)

Old 영역

  • Survivor를 왔다 갔다 하면서 살아남은 객체가 이동
  • 얼마나 오래됐는지 기준: age bit (Minor GC 생존할 때마다 +1)
  • MaxTenuringThreshold 초과 시 Old로 승격
  • 예외: 객체 크기가 Survivor보다 크면 Eden에서 바로 Old로 점프

Major GC (Full GC)

  • Old 영역에서 발생
  • GC 스레드 제외 모든 스레드 정지 → Stop-The-World
  • 어떤 GC 알고리즘을 써도 STW는 피할 수 없다
  • GC 튜닝의 목표 = STW 시간 최소화

Java 8 이후: Permanent → Metaspace

Java 8 이전에는 Permanent 영역에 클래스 메타데이터, static 객체, 상수 등이 저장됐다. 문제는 Perm 영역 크기가 고정이라 OutOfMemoryError: PermGen space가 자주 터졌다.

// 이런 코드가 문제였음
static List<Object> list = new ArrayList<>();  // 계속 추가되면 Perm 터짐

Java 8부터 Metaspace로 교체됐다. Metaspace는 Native 메모리 영역(OS 관리)이라 JVM 힙 제한을 안 받는다. static 객체는 Heap으로 이동시켜 GC 대상이 되도록 개선했다.


GC 알고리즘 비교

GC특징단점
Serial GC싱글스레드, JDK 5/6STW 길다, 실무 비권장
Parallel GCMinor GC 멀티스레드Full GC는 여전히 느림
Parallel Old GCFull GC도 병렬 처리Mark-Summary-Compaction
CMS GCSTW 최소화 목표, 4단계로 정밀하게Compaction 미지원 → 메모리 단편화
G1 GCRegion 단위 관리, STW 예측 가능현재 Java 기본 GC

G1 GC가 핵심인 이유: 힙을 Region이라는 논리 단위로 잘게 나눠서 Eden/Survivor/Old 역할을 동적으로 부여한다. CMS와 달리 Compaction도 하고 STW 시간도 예측할 수 있다. JVM 힙은 최대 2048개 Region, 각 Region은 1MB~32MB.


3. 코드 실습 복기

형변환 - 조용히 일어나는 데이터 손실

long l1 = 2222222222L;
float f1 = l1;           // long → float 자동 형변환
System.out.println("f1:" + f1);  // f1:2.22222221E9 (정밀도 손실!)

long l2 = (long) f1;
System.out.println("l2:" + l2);  // l2:2222222208 (원래 값과 다름)

long → float 변환에서 정밀도가 손실된다. 자동 형변환이라고 다 안전한 게 아니다. 실제로 금융 계산에서 float/double 쓰면 큰일 난다. 이럴 때 BigDecimal을 써야 한다는 걸 나중에 배울 것 같다.

비트 연산 - >> vs >>>

int n2 = -8;
System.out.println("-8 >> 1 : "  + (n2 >> 1));   // -4 (부호 유지)
System.out.println("-8 >>> 1 : " + (n2 >>> 1));   // 2147483644 (부호 비트도 0으로)

>>는 부호 비트를 유지하면서 이동 (산술 우측 시프트), >>>는 그냥 0으로 채운다 (논리 우측 시프트). 음수에 >>>를 쓰면 엄청 큰 양수가 나온다.

&& vs & 단락 평가

boolean result = i++ > 10 && j++ > 5;

&&는 앞이 false면 뒤를 평가 안 한다 (단락 평가). 그래서 i++만 실행되고 j++는 실행 안 됨. &는 무조건 둘 다 평가한다. 조건문에서 &&를 쓰는 이유가 성능뿐 아니라 부작용(side effect) 방지이기도 하다.

Pass By Value - 자바의 핵심 오해

public static void passReference(int[] data) {
    data[0] = 100;  // 원본 배열이 바뀜!
}

자바는 "항상 Pass By Value"다. 근데 참조형의 경우 참조값(주소)이 복사되기 때문에 메서드 안에서 배열 내용을 바꾸면 원본도 바뀐다. 이게 Pass By Reference처럼 보이지만 엄밀히는 아니다. 배열 자체를 새로 할당(data = new int[5])하면 원본에 영향 없음.


4. 비판적 사고 (Critical Thinking)

오늘 수업에서 생긴 의문:

AI 코딩 시대에 성능 최적화를 위해 더 로우레벨 언어가 필요해지지 않을까?

LLM에게 답을 물었다.

  1. C는 이미 하드웨어의 물리적 수준에 가장 가까운 언어다. "더 로우레벨"로 가는 건 현실적으로 어렵다.
  2. 오히려 흐름은 반대다. 안전하면서 C만큼 빠른 언어가 뜨고 있다. Rust가 대표적. 메모리 안전성을 컴파일 타임에 강제해서 C/C++의 성능을 유지하면서 버그를 줄인다.
  3. AI 연산(GPU/NPU) 영역에서는 DSL(Domain-Specific Language) 이 성능 최적화의 주역이 될 것 같다. 범용 로우레벨보다 특정 하드웨어에 특화된 언어가 훨씬 효율적이다.

결론적으로, 인간이 더 고수준의 설계 의도를 정의하고 AI가 코드를 작성하는 방향으로 가는 것 같다. 이미 지금도 그렇게 되고 있다. 그러면 개발자에게 중요한 능력은 AI가 작성한 코드를 읽고, 위험한 지점을 감지하고, 설계 의도에 맞는지 판단하는 것이다.
최근에 실리콘밸리 개발자님의 유투브에서 오히려 인간이 하는 코드리뷰 단계가 병목이 된단 영상을 보았다. 코드 생산성이 말도 안되게 올라간만큼 설계할때가 더 중요해진것 같다.


5. 내일의 목표

  • 6장: 캡슐화 / 접근 제한자 / setter-getter 개념 정리
  • 7장: 상속 (단일상속, extends, 메서드 오버라이딩 vs 오버로딩)
  • 정처기 실기 - 네트워크 파트 1회독
  • GSAT 파랭이 수리영역
  • 하네스에 대해 공부
profile
developer

0개의 댓글