[034] Java: 기본 문법

아투·2025년 1월 12일

Java

목록 보기
10/16
post-thumbnail

개요

Java는 객체 지향 프로그래밍(OOP) 패러다임을 기반으로 하는 컴파일 및 인터프리터 언어이다. "Write Once, Run Anywhere (WORA)"라는 철학 아래, 작성된 소스 코드는 플랫폼에 독립적인 바이트코드(Bytecode)로 변환되어 JVM(Java Virtual Machine) 위에서 실행된다. Java의 기본 문법은 C/C++의 신택스(Syntax)를 계승하면서도, 포인터 제거와 자동 메모리 관리(Garbage Collection)를 통해 안정성을 극대화한 구조적 특징을 지닌다.


핵심 원리 및 문법 구조

Java 애플리케이션을 구성하는 기본 문법과 그 내부 메커니즘은 다음과 같다.

1. 소스 코드의 기본 단위와 진입점 (Entry Point)

Java의 모든 코드는 클래스(Class) 내부에 존재해야 한다. 프로그램이 실행되려면 반드시 구동 진입점인 main 메서드가 필요하다.

public class Application {
    public static void main(String[] args) {
        // 실행할 로직 규칙 정의
    }
}
  • public: JVM이 어디서나 접근할 수 있도록 허용하는 접근 제어자이다.
  • static: 인스턴스 생성 없이 메모리(Method Area)에 상주시켜 JVM이 바로 호출할 수 있게 한다.
  • void: 반환 타입이 없음을 명시한다.

2. 데이터 타입의 이원화 (Primitive vs Reference)

Java는 메모리 효율성과 객체 지향의 일관성을 모두 충족하기 위해 데이터 타입을 두 가지로 엄격히 분류한다.

  • 원시 타입 (Primitive Type): int, double, boolean, char 등 8가지가 존재한다. 비객체 타입으로, 실제 데이터 값이 스택(Stack) 메모리에 직접 저장되므로 연산 속도가 빠르다.
  • 참조 타입 (Reference Type): 배열, 클래스, 인터페이스 및 래퍼 클래스(Wrapper Class) 등이 해당한다. 실제 객체는 힙(Heap) 메모리에 할당되고, 스택 메모리에는 그 힙 영역의 주소값(Reference)만 저장된다.

3. 제어문과 메모리 스코프

조건문(if-else, switch)과 반복문(for, while)은 프로그램의 흐름을 제어한다. 제어문 블록({}) 내에서 선언된 변수는 해당 블록을 벗어나는 순간 스택 메모리에서 소멸하는 지역 변수(Local Variable)의 생명 주기를 따른다.


실무 적용 및 트러블슈팅

대규모 트래픽을 처리하는 엔터프리터 시스템에서 Java 기본 문법의 메커니즘을 오해했을 때 발생하는 대표적인 장애 사례와 해결 방안이다.

오토박싱(Auto-boxing)으로 인한 성능 저하 및 NPE

Java는 원시 타입과 참조 타입 간의 변환을 자동으로 처리하는 오토박싱/언박싱 기능을 제공한다. 그러나 반복문 내부에서 이를 무분별하게 사용하면 심각한 메모리 오버헤드와 연산 지연이 발생한다.

비효율적인 코드 예시 (장애 유발 패턴)

public class PerformanceIssue {
    public void calculate() {
        Long sum = 0L; // 래퍼 클래스(Reference Type) 선언
        
        for (int i = 0; i < 1_000_000; i++) {
            sum += i; // 내부적으로 매번 새로운 Long 객체가 생성됨 (Auto-boxing)
        }
        System.out.println(sum);
    }
}

원인 분석: sum += i가 실행될 때마다 기존 Long 객체의 값을 꺼내어 더한 뒤, 새로운 Long 객체를 힙 메모리에 할당한다. 100만 개의 불필요한 객체가 생성되어 가비지 컬렉터(GC)의 부하를 가중시키고 시스템 멈춤 현상(Stop-The-World)을 유발한다. 또한 래퍼 클래스는 null을 가질 수 있으므로 연산 중 NullPointerException (NPE)의 위험성도 내포한다.

최적화된 리팩토링 코드 (Troubleshooting)

public class PerformanceFixed {
    public void calculate() {
        long sum = 0L; // 원시 타입(Primitive Type) 사용
        
        for (int i = 0; i < 1_000_000; i++) {
            sum += i; // 스택 메모리 내에서 값만 변경되므로 객체 생성이 없음
        }
        System.out.println(sum);
    }
}

해결책: 연산이 빈번하게 일어나는 로직에서는 래퍼 클래스가 아닌 원시 타입(long)을 의도적으로 사용하여 힙 메모리 할당 오버헤드를 원천 차단한다.


핵심 요약

  • Java는 클래스 기반의 객체 지향 언어이며, 모든 실행의 시작점은 public static void main(String[] args) 메서드이다.
  • 메모리 구조는 데이터 값 자체를 가지는 스택 영역(원시 타입)과 실제 객체가 할당되는 힙 영역(참조 타입)으로 분리되어 관리된다.
  • 실무 코드 작성 시, 편리성을 제공하는 오토박싱 등의 문법 요소가 내부적으로 객체를 양산하지 않는지 확인해야 하며, 연산 집약적인 로직에서는 원시 타입을 우선 사용하여 GC 부하를 최소화해야 한다.

0개의 댓글