Java는 객체 지향 프로그래밍(OOP) 패러다임을 기반으로 하는 컴파일 및 인터프리터 언어이다. "Write Once, Run Anywhere (WORA)"라는 철학 아래, 작성된 소스 코드는 플랫폼에 독립적인 바이트코드(Bytecode)로 변환되어 JVM(Java Virtual Machine) 위에서 실행된다. Java의 기본 문법은 C/C++의 신택스(Syntax)를 계승하면서도, 포인터 제거와 자동 메모리 관리(Garbage Collection)를 통해 안정성을 극대화한 구조적 특징을 지닌다.
Java 애플리케이션을 구성하는 기본 문법과 그 내부 메커니즘은 다음과 같다.
Java의 모든 코드는 클래스(Class) 내부에 존재해야 한다. 프로그램이 실행되려면 반드시 구동 진입점인 main 메서드가 필요하다.
public class Application {
public static void main(String[] args) {
// 실행할 로직 규칙 정의
}
}
public: JVM이 어디서나 접근할 수 있도록 허용하는 접근 제어자이다.static: 인스턴스 생성 없이 메모리(Method Area)에 상주시켜 JVM이 바로 호출할 수 있게 한다.void: 반환 타입이 없음을 명시한다.Java는 메모리 효율성과 객체 지향의 일관성을 모두 충족하기 위해 데이터 타입을 두 가지로 엄격히 분류한다.
int, double, boolean, char 등 8가지가 존재한다. 비객체 타입으로, 실제 데이터 값이 스택(Stack) 메모리에 직접 저장되므로 연산 속도가 빠르다.조건문(if-else, switch)과 반복문(for, while)은 프로그램의 흐름을 제어한다. 제어문 블록({}) 내에서 선언된 변수는 해당 블록을 벗어나는 순간 스택 메모리에서 소멸하는 지역 변수(Local Variable)의 생명 주기를 따른다.
대규모 트래픽을 처리하는 엔터프리터 시스템에서 Java 기본 문법의 메커니즘을 오해했을 때 발생하는 대표적인 장애 사례와 해결 방안이다.
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)의 위험성도 내포한다.
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)을 의도적으로 사용하여 힙 메모리 할당 오버헤드를 원천 차단한다.
public static void main(String[] args) 메서드이다.