특징
-
객체지향 언어
실제 세계를 모델링하여 개발하는 방법으로, 데이터(프로퍼티)와 절차(메서드)가 하나로 묶인 객체를 중심으로 한 상호작용(메세지)으로 서술하는 방식이다.
원칙
- 단일책임 : 하나의 클래스는 하나의 기능만 가진다.
- 개방폐쇄 : 기존의 코드를 수정하지 않고 동작을 추가하거나 변경할 수 있다.
- 리스코브 치환 : 하위 타입은 언제나 상위 타입을 대체 가능하다.
- 인터페이스 분리 : 불필요 기능에 접근하지 않도록 인터페이스를 작게 나눈다.
- 의존성 역전 : 구체화를 담당하는 저수준 모듈이 아닌, 추상화 된 고수준 모듈에 의존한다.
특징
- 추상화 : 불필요한 정보는 제외하고 중요한 정보만을 표현.
- 캡슐화 : 속성과 동작을 하나의 객체로 묶고, 필요 시 내부를 은닉한다.
- 상속성 : 상속을 통해 다른 객체의 속성 및 동작을 이용할 수 있다.
- 다형성 : 하나의 변수명, 함수명이 상황에 따라 다양하게 해석될 수 있다.
-
플랫폼 독립적
자바는 가상 머신에 의해 실행되기 때문에, 운영체제의 종류에 구애받지 않고 동일한 코드로 실행이 가능하다.
-
자동 메모리 관리
포인터 개념이 없어 직접 메모리에 접근할 없으며, 모든 메모리 접근을 자바 시스템이 관리하며 GC의 존재로 메모리 누출을 프로그래머가 고려할 필요가 없다.
-
멀티 쓰레딩 지원
-
동적 로딩
모든 클래스의 정보를 불러오지 않고 필요한 부분만 런타임에 불러와 메모리의 낭비를 방지한다.
자바 코드의 실행
자바 코드는 다음과 같은 순서로 실행된다.
- 자바 컴파일러가 자바 소스파일을 컴파일한다.
이 때의 파일은 자바 바이트 코드 파일(.class)로, 컴퓨터는 읽을 수 없고 자바 가상 머신(JVM)이 읽을 수 있는 코드이다.
- 컴파일 된 바이트 코드가 JVM의 클래스로더에게 전달된다.
- 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 JVM의 메모리인 런타임 영역에 올린다.
- 실행엔진은 JVM 메모리에 올라온 바이트 코드를 명령어 단위로 가져와 실행한다.
자바 가상머신(JVM)
- JDK
Java Development Kit은 개발자가 사용하기 위해 JRE와 Development Tools이 합쳐진 모음으로, development Tools에는 컴파일(javac), 테스트, 디버깅 등의 기능이 포함되어 있다.
- JRE
Java Runtime Environment는 프로그램이 JVM 상에서 실행되기 위해 필요한 런타임 환경과 라이브러리를 제공한다. JRE가 JVM을 포함하고 있으며, 사용자가 자바 프로그램을 수행하기 위해서는 JDK가 아닌 JRE가 필수이다.
- JVM
Java Virture Machine은 JRE와 JDK에 포함되는 중요 부분으로, 자바 코드가 컴파일 된 바이트 코드(.class)를 실행하는 역할을 한다.
클래스 로더
자바는 컴파일 시점이 아닌 런타임에 처음으로 참조되는 클래스를 로드하고 링크하는 특징이 있다. 이를 동적 로드라고 하며, 이것을 담당하는 부분이 JRE, JVM의 클래스 로더이다.
특징
- 계층 구조 & 위임 모델
클래스 로더는 계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 형태로 동작한다. 클래스 로더가 클래스 로드를 요청받으면, 우선 캐시를 확인하여 이전에 로드 된 클래스인지 확인한 뒤, 없다면 상위 클래스 로더로 거슬러 올라가 확인한다.
클래스 로더는 총 4가지 존재한다.
- 부트스트랩 클래스 로더
JVM의 구동에 필수적인 라이브러리들의 클래스가 탑재된다.
- 확장 클래스 로더
기본 자바 API를 제외한 확장 클래스들을 로드한다.
다양한 보안 확장 기능 등을 여기에서 로드하게 된다.
- 시스템 클래스 로더
사용자가 지정한 환경변수 Classpath의 클래스를 탑재한다.
사용자가 작성한 클래스를 로드하는 로더이기도 하다.
- 사용자 지정 클래스 로더
사용자가 직접 코드 상에서 생성해서 사용하는 클래스 로더이다.
- 가시성 제한
하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 반대는 불가능하다.
- 언로드 불가
클래스 로더는 클래스를 로드할 수는 있지만 언로드는 불가능하다.
로드 과정
- 로드
.class 파일을 읽어 바이트 코드를 메소드 영역에 저장한다.
- 연결
- 검증
Bytecode Verifier가 .class 파일이 유효하고 사용 가능한 형태인지 검사한다.
만약 유효하지 않다면, VerifyError가 throw 되고 연결 프로세스가 중지된다.
- 준비
클래스 또는 인터페이스의 모든 static 변수에 메모리가 할당되고 초기값으로 초기화된다.
- 분석
모든 Symbolic Reference가 실제 물리적 메모리 주소로 대체된다. 이를 위해선 메소드 영역의 런타임 상수 풀 메모리에 있는 Symbol table이 사용된다.
- 초기화
static block(static initializer)가 실행되고, static 변수에 값이 할당된다.
런타임 데이터 영역
JVM이 운영체제로부터 할당받는 메모리 영역이다.
5개 영역으로 나눌 수 있으며, 이 중 힙과 메모리 영역은 모든 스레드가 공유하고 나머지는 스레드 별로 생성된다.
-
메서드 영역
JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, static 변수, 메서드의 바이트코드 등을 보관한다.
- 런타임 상수 풀
각 클래스와 인터페이스의 상수 뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스를 담고 있는 테이블이다. JVM이 어떤 메서도 또는 필드를 참고할 때 런타임 상수 풀을 통해 실제 메모리 주소를 찾아 참조한다.
-
힙
JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다. 인스턴스 또는 객체가 저장되며 GC의 주 대상이다.
-
PC 레지스터
현재 수행 중인 JVM 명령 주소가 저장된다.
-
JVM 스택
메소드를 호출할 때마다 스택 프레임이 추가되고 종료되면 제거되는 영역이다.
- 스택 프레임
각 스택 프레임은 지역 변수 배열, 피연산자 스택, 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀 참조를 가진다.
-
네이티브 메서드 스택
Java가 아닌 C, C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 생성된다.
실행 엔진
클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 실행하는 것이 실행 엔진이다. 실행 엔진은 자바 바이트 코드를 명령어 단위로 실행하며, 두 가지 방식을 통해 바이트 코드를 실제 컴퓨터가 실행할 수 있는 바이너리 코드로 변환한다.
- 인터프리터
바이트코드 명령어를 하나씩 읽고 해석하여 실행한다.
컴파일러에 비해 메모리 효율이 좋으나, 실행 시간이 느리다.
- JIT 컴파일러
인터프리터의 단점을 보완하기 위해 도입되었다.
인터프리터 방식으로 수행하며 특정 코드가 반복되는 횟수를 카운팅하여, 횟수가 일정 수치를 넘어가면 해당 코드 전체를 컴파일하여 캐시한다. 이후 동일한 코드가 호출되면 인터프리팅 없이 바로 실행된다.
GC
전제
GC는 두 가지 전제에 기반하여 동작한다.
- 대다수의 객체는 금방 접근 불가능한 상태가 된다.
- 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
동작 방식
- Stop The World
GC 수행은 JVM으로 하여금 애플리케이션 실행을 멈추게 한다.
GC를 실행하는 쓰레들를 제외한 모든 쓰레드의 작업을 중단하고, GC가 완료되면 재개한다.
일반적으로 GC의 성능 개선은 이 애플리케이션이 멈추는 Stop the world의 시간을 줄이는 작업을 말한다.
- Mark and Sweep
GC는 스택의 모든 변수 또는 접근 가능한 객체를 스캔하며 사용되고 있는 메모리를 식별(Mark)하고, 사용되지 않는 객체들을 메모리에서 제거(Sweep)한다.
Minor GC
Heap 메모리에 최근 생성된 객체가 할당되는 Young 메모리 영역은 1개의 Eden 영역과 2개의 Survivor 영역으로 나뉜다. 새로 생성된 객체는 Eden 영역에 할당되며, 이 Eden 영역이 꽉 차면 다음과 같은 프로세스로 Minor GC가 실행된다.
- 새로 생성된 객체가 Eden에 할당되어 영역이 꽉 찬다.
- Minor GC가 실행된다.
2-1. Eden 영역에서 사용되지 않는 객체의 메모리가 해제된다.
2-2. 해제되지 않은 객체는 Survivor 영역으로 옮겨진다.
- 위 과정이 반복되어 Survivor 영역이 가득 차면, GC가 실행되고 살아남은 객체가 다른 빈 Survivor 영역으로 이동된다.
- 위 과정이 반복되어 계속해서 살아남은 객체는 Old 영역으로 이동된다.
Major GC
Young 영역에서 살아남은 객체들은 Old 영역으로 이동된다. 객체가 계속하여 이동되어 Old 영역의 메모리가 부족해지면 Major GC가 실행된다. Old 영역은 Young 영역보다 크며, Young 영역에 있는 객체를 참조하는 경우도 있기 때문에 일반적으로 Minor GC보다 실행 시간이 매우 길다.
참고
자바의 특징 - 점프 투 자바
객체지향 프로그래밍 원칙 - 망나니개발자
자바의 동적 로딩 - mincho920
Just in Time Compilation Explained
JVM Internal - Naver D2
Difference between JDK, JRE and JVM explained Java - getKT
Java Virtual Machine - Class Loader
자바 메모리 구조 - Hoonmaro
컴파일러(compiler)와 인터프리터(interpreter)의 차이 - jhur98
[Java] Garbage Collection(가비지 컬렉션)의 개념 및 동작 원리 - MangKyu