Java와 JVM
Java라는 언어는 JVM 위에서 실행되는 언어이다. C, C++, Rust 등의 언어들은 네이티브 컴파일되어 바이트 코드를 거치지 않고 즉시 OS/CPU가 읽을 수 있는 바이너리(기계어)로 컴파일된다. 하지만 Java는 javac(JDK에 포함)라는 자바 컴파일러에 의해 바이트코드(.class 파일)로 컴파일되고 이 바이트코드가 JVM에 의해 네이티브로 변환해 실행되는 것이다.
여기서 자바의 중요한 장점이 하나 드러나는데, 바로 플랫폼 독립성이다. 앞서 말한 것처럼 바로 네이티브 컴파일되는 언어들은 컴파일된 결과물이 CPU/OS에 종속된다. 하지만 Java는 CPU/OS에 종속되지 않는 바이트코드를 거쳐 JVM에 의해 실행되기 때문에 한 번 빌드한 바이트코드를 여러 플랫폼에서 실행할 수 있다.(JVM이 아니더라도 비슷한 구조를 가져가는 다른 언어도 있음!)
용어정리
* 바이트코드: 가상 머신용 명령 집합(네이티브 바이너리같이 0과 1의 파일임) -> 런타임 필요
* 네이티브 바이너리: CPU/OS용 실제 기계어
* JRE(Java Runtime Environment): 실행 환경(런타임)
* JDK(Java Development Kit): 개발 도구 + 런타임 -> JRE + javac 등등 포함

왜 다른 언어는?
그럼 이러한 구조를 다른 언어는 왜 사용하지 않을까? Java는 이런 구조를 채택함으로써 플랫폼 독립성이라는 이점을 얻었다. 하지만 실행하기 위해 JVM이라는 환경을 거쳐 한 번 더 번역해야한다는 trade off도 따라온다. 이러한 오버헤드를 줄이고 하드웨어에 가까운 성능이 필요할 때가 많기 때문에 성능상의 이유로 모두가 이런 구조를 사용하진 않는다.
JVM의 구조
JVM의 메모리 구조와 OS 레벨의 메모리 구조는 비슷한 개념을 사용하지만 실제 의미와 사용 방식이 다르다.
OS 레벨에서의 메모리 구조
운영체제가 프로세스를 실행할 때 프로세스마다 독립적인 메모리 공간을 제공한다. 대부분의 프로세스는 아래의 메모리 모델 관례를 기본으로 메모리를 사용한다.(실제로는 훨씬 다양한 매핑들로 이루어짐)
1. 스택
- 함수 호출 시 지역 변수, 매개변수, 리턴 주소 등 저장하는 공간
- LIFO 구조
- 함수 호출이 끝나면 해당 프레임 자동 해제
- 보통 OS가 제공하는 하드웨어 스택 포인터 레지스터로 관리
2. 힙
- 동적 메모리 할당(new, malloc 등) 시 사용하는 공간
- 수동 관리되거나 GC(Garbage Collector) 지원 언어에서 자동으로 관리
- 크기와 해제 시점을 프로그래머가 결정하거나 런타임이 관리
3. 데이터 영역
- 전역 변수, static 변수 저장
- 초기화 여부에 따라 .data, .bss 영역 등으로 나뉨
4. 코드 영역
- 실행할 프로그램의 명령어(바이너리) 저장
- 읽기 전용
JVM 레벨에서의 메모리 구조
JVM도 OS 위에서 동작하는 하나의 프로세스이므로 OS에게 메모리를 할당받아서 사용하는데, JVM이 내부적으로 관리하는 메모리 모델을 사용한다. 위에서 설명한 메모리 모델을 기반으로 하지만 다른 프로세스보다 더 많은 층위와 최적화가 존재한다.
1. 스택
- 스레드별로 존재하는 공간
- 각 메서드 호출 시 스택 프레임 생성
- 지역 변수, 매개변수, 함수 호출 내역 등을 저장
- Operand Stack
- 메서드 리턴 주소 등
- 함수 호출이 끝나면 프레임 자동 해제
- OS 스택과 유사하지만 바이트코드 실행을 위한 구조가 추가됨
2. 힙
- 모든 스레드가 공유하는 공간
- new 키워드로 생성한 모든 인스턴스 객체, 배열, Wrapper/Util 객체, static 필드의 값 등을 저장
- GC의 관리 대상 (어떤 참조 변수도 힙 영역에 있는 인스턴스를 참조하지 않게 된다면 GC에 의해 청소)
- 세부적으로 Young Generation, Old Generation, Metaspace와 연결된 영역 등으로 나뉨
3. 메서드 영역
- 클래스 로딩 시 저장되는 메타데이터
- 클래스 정보(필드, 메서드)
- static 변수
- 런타임 상수 풀
- OS 메모리의 데이터 영역와 유사
4. PC 레지스터
- 각 스레드가 현재 실행 중인 바이트코드 명령어의 주소를 저장 (스레드별 존재)
- cpu 코어를 여러 스레드가 교대로 사용하는 방식으로 멀티 스레딩이 구현되기 때문에 특정 시각에 각 코어는 한 스레드의 명령어만 실행
- 스레드를 전환하고 이전에 실행하다 멈춘 지점을 정확하게 복원하려면 스레드 각각에는 고유한 PC(Program Counter)가 필요

* 왜 스택은 스레드마다 할당되는데 힙은 전부 공유할까?
스택은 스레드 실행 컨텍스트이기 때문에 스레드마다 독립적이다. 함수 호출 시 필요한 지역 변수, 매개변수, 리턴 주소 등을 저장하는 실행 컨텍스트로 스레드가 여러 개 있다면, 각 스레드는 자신만의 함수 호출 흐름을 가지고 있어야 한다.
만약 스택 영역을 공유한다면? -> 여러 스레드의 함수 호출 프레임이 뒤섞여서 로컬 변수나 리턴 주소가 꼬이는 문제가 발생한다!
반면에 힙은 객체 저장소이기 때문에 공유해서 사용한다. 프로그램 실행 중 동적으로 만들어진 데이터(객체, 배열 등)을 저장하는 공간으로 여러 스레드가 동시에 같은 데이터를 참조해야하는 상황에서 효율적으로 관리하기 위함이다.
만약 힙 영역이 독립적이라면? -> 다른 스레드와 객체를 공유하기 위해 매번 객체를 복사해서 가지고 있어야 한다.
* 메서드 영역도 모든 스레드가 공유하는데 그냥 메서드 영역이랑 합쳐서 힙에 몽땅 저장하면 안되나?
힙은 GC의 관리 대상인데, 여기에 메서드 영역의 메타데이터까지 합류하게 된다면 GC가 불필요하게 거대한 메타데이터까지 스캔해야하기 때문에 지연이 늘어나게 된다.
Java의 값 복사(pass-by-value)
Java는 항상 "값에 의한 전달"만 한다. 여기서 말하는 "값"이 기본형 값이냐, 참조값(객체 주소)이냐만 구분하면 된다.
1. 변수에 무엇이 들어있는가?
- 기본형(primitive): int, float, boolean 등 -> 변수에 실제 값이 들어있음
- 참조형(reference): String, 배열(int[] 등), 객체, Wrapper Type(Integer, Float 등) -> 변수 안에 객체를 가리키는 참조값(주소)가 들어있음
2. 대입, 메서드 호출은 어떻게 이루어지는가?
- 기본형: 숫자 값 자체가 복사되어서 넘어감
- 참조형: 참조값(주소)가 복사되어서 넘어감 -> 같은 객체를 가리키는 변수가 하나 더 생김!
- 메서드 호출: 새로운 스택 프레임의 매개변수 슬롯에 해당 값이 복사되어서 들어감
주의해야할 점
- 메서드 내부에서 매개변수 재할당 vs 내부 변경
- 재할당: a = new Foo() → 매개변수의 참조만 바뀜, 원본은 변화 X
- 내부 변경: a.field = 1 → 같은 객체의 내부를 수정했으므로 밖에서도 변화 확인
- String, Integer, Float 등 래퍼/문자열은 불변(immutable): 내부를 바꾸는 메서드가 없고, 변경처럼 보이는 연산은 새 객체를 만들어 참조를 바꿈
- final 매개변수: 매개변수 재할당만 금지, 객체 내부 변경은 가능(객체가 가변이면)
- 얕은/깊은 복사
- new ArrayList<>(old)나 Arrays.copyOf(...)는 얕은 복사(컨테이너만 새로, 요소 참조는 공유)
- 중첩 객체까지 새로 만들어야 하면 깊은 복사를 직접 구현해야 함(복사 생성자, 팩토리, 매퍼 등)
- clone()은 기본적으로 얕은 복사이고 사용상 주의점이 많아 권장되지 않는다
float a = 4.5f;
public void change(float b){
b = 2.0f;
}
chage(a);
System.out.println(a);
// 위 실행 결과는 4.5
// 메서드 안 지역변수 b에 a의 4.5라는 값을 복사해서 넘겼기 때문에 메서드 내부의 b는 a와 별개의 변수
참고
https://lima1016.tistory.com/124
[JVM 밑바닥] 1장. 자바, 2장 자동 메모리 관리
조아써! JVM 밑바닥까지 파헤치기 너로 정했다!읽어보자!1장JDK (Java Development Kit)Java 프로그램을 개발하기 위해 필요한 도구 모음 이다.컴파일러, 디버깅 도구, 자바 가상 머신(JVM) 등을 포함하고있
lima1016.tistory.com
'Computer Science > Java' 카테고리의 다른 글
| 객체지향과 Java (0) | 2025.10.13 |
|---|---|
| Java 21 Virtual Thread (0) | 2025.09.25 |
| Gradle 알아보기 (0) | 2025.09.13 |