JAVA 동작


자바는 어떤 운영체제든 실행이 가능하다. 이 기반이 JVM이고 OS에 독립적으로 구동 가능하다.

  • Java_Home: java가 설치된 directory이다.
    • Java.exe: 자바 파일 실행에 사용
    • Javac.exe: 컴파일하는 역할로 자바 소스파일 (.java)를 바이트코드 형식인 .class 파일로 컴파일하는 역할을 한다.
  • JDk: 자바 개발 환경- 컴파일러, 역어셈블러, 디버거, 의존관계 분석 등 개발에 필요한 도구 제공 Java Development Kit
  • JRE: 자바 실행 환경- 자바 실행 명령, 클래스 로더와 바이트 코드의 실행에 필요한 기본 라이브러리 제공 Java Runtime Enviroment
  • JVM: 자바 가상 머신- 바이트코드 인터프린터, JIT 컴파일러, 링커, 명령어 세트, GC, 런타임 데이터 영역(메모리) 등 OS에 독립적으로 실행 될 수 있는 추상 계층 제공한다. Java Virtual Machine

컴파일러에 의해서 자바가 클래스 파일로 바뀌고 클래스 파일들이 구동될수 있게 클래스 로더같은 JRE를 통해 실행된다. JRE는 JVM과 JAVA API를 구성한다.
자바 코드의 수행 과정이다.
JDK를 사용해 바이트 코드(class파일)를 만들고, JRE를 사용해서 바이트코드를 실행하면, JVM이 가동되어 바이트코드의 실질적인 실행(실제 OS에 메모리 할당 및 회수, 시스템 명령 호출 등)을 담당한다.
클래스 로더가 컴파일된 자바 바이트코드를 런타임 데이터 영역에 로드하고, 실행 엔진이 자바 바이트 코드를 실행한다.
자바의 바이트코드는 JRE위에서 동작한다. JRE에서의 가장 주요 요소는 바이트코드를 해석하고 실행하는 JVM이다. JRE는 JAVA API, JVM으로 구성되며, JVM의 역할은 자바 애플리케이션을 클래스 로더를 통해 읽어 자바 API와 함께 실행하는 것이다.

JVM, 가상머신? 가상화는 무엇인가

가상화

가상화란 물리적 자원을 논리적으로 분리하여 여러 개의 독립적 가상 환경으로 활용할 수 있게 만드는 기술이다.

가상 메모리

가상 메모리는 메인 메모리의 물리적 크기 제한을 극복하기 위해, 보다 저렴한 보조 기억 장치의 공간을 대신 사용하는 기법이다. ram과 hdd의 공간을 사용하는 것을 보통 가상 메모리라 한다.

현대 컴퓨터 시스템에서는 필요한 메모리 페이지만 로드 하여 메모리 효율을 높이는 페이징 기술을 통해 가상 메모리 주소의 개념을 동작시킨다.
가상 메모리 주소는 프로그램 실행에 있어 독점적 공간을 제공하며, 다른 프로그램과 주소가 겹치지 않도록 보장 한다.

가상 메모리는 가상 메모리 주소를 물리 메모리 주소로 매핑하며, MMU (Memory Management Unit)와 TLB (Translation Lookaside Buffer)를 사용하여 변환 오버헤드를 최소화한다.

가상 머신

가상 머신(Virtual Machine)은 하드웨어 대신 소프트웨어를 사용해 프로그램을 실행하고 애플리케이션을 배포하는 물리적 컴퓨터의 가상 표현 또는 에뮬레이션이다.
간단하게 독립적인 OS를 가지는 가상 컴퓨팅 환경이다.
가상 머신은 다음과 같은 구성 요소를 포함한다:

  • Guest OS: 가상 머신 내부에서 실행되는 운영체제.
  • Virtual Hardware: 물리적 하드웨어의 가상화 버전(CPU, 메모리, 디스크 등).
  • 하이퍼바이저(Hypervisor): VM의 실행 환경을 관리하고, 물리적 자원을 가상 머신에 분배. 가상화 계층을 구현해주는 소프트웨어이다.

가상화와 메모리 가상화

가상화는 가상 머신이 독립적으로 실행될 수 있도록 가상 메모리 관리 를 포함한 다양한 자원 가상화 기술을 활용한다.
하이퍼바이저는 Shadow Page Table 이나 Extended Page Table(EPT) 과 같은 기술을 통해 가상 머신이 효율적으로 메모리 주소를 변환하고 사용할 수 있도록 돕는다.

가상화에 대해서는 도커와 컨테이너, 클라우드와 함께 다룰 예정이다.


JVM(Java Virtual Machine)


Java 플랫폼은 Java 소프트웨어 개발 언어로 작성된 프로그램을 위한 실행 환경입니다. "한 번 작성하면 어디서나 실행할 수 있다"는 Java의 약속은 모든 Java 프로그램이 모든 Java 플랫폼에서 실행될 수 있다는 것을 의미하며, 이 때문에 Java 플랫폼에는 Java 가상 머신(JVM)이 포함되어 있습니다.

Java 프로그램에는 JVM을 위한 명령어의 한 형태인 바이트코드가 포함되어 있습니다. JVM은 이 바이트코드를 호스트 컴퓨터에서 사용하는 가장 낮은 수준의 언어인 머신 코드로 컴파일합니다. 한 컴퓨팅 플랫폼의 Java 플랫폼에 있는 JVM은 프로세서가 기대하는 머신 코드에 따라 다른 컴퓨팅 플랫폼의 JVM과 다른 머신 코드 명령어 세트를 생성합니다.

따라서 JVM은 전체 OS를 실행하지 않으며 다른 VM처럼 하이퍼바이저를 사용하지 않습니다. 대신 특정 하드웨어에서 실행되도록 애플리케이션 수준의 소프트웨어 프로그램을 변환합니다.

JVM

JVM(Java Virtual Machine)은 소프트웨어 기반의 가상 머신이으로 CPU랑 메모리(RAM, SSD 등)를 가상화해서, 프로그램이 실제 하드웨어에서 실행되는 것처럼 작업한다. 기본적으로 자바 바이트코드(클래스 파일)를 실행하는 실행기이다.
단순히 자바 코드를 실행하는 것만 하는 게 아니라, 운영 체제 기능의 일부도 포함해서 프로그램 실행 환경을 제공한다. ex), 메모리 관리(가비지 컬렉션), 스레드 관리, 로드 타임(dynamic class loading)


특징

  • 스택 기반의 가상 머신
  • 심볼릭 레퍼런스: 기본 자료형을 제외한 모든 타입(클래스, 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아닌 심볼릭 레퍼런스를 통해 참조한다.
  • 가비지 컬렉션: 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성, 가비지 컬렉션에 의해 자동으로 관리.
  • 플랫폼 독립성: 기본 자료형을 명확하게 정의하여 호환성을 유지하며 플랫폼 독립성을 보장(c/c++과 같이 환경에 따라 자료형의 크기가 다르지 않음)
  • 네트워크 바이트 오더: 자바 클래스 파일은 인텔 아키텍처가 사용하는 독립성 유지를 위해 고정된 바이트 오더를 유지하기 위해 네트워크 전송시 사용하는 네트워크 바이트 오더를 사용한다.

주요 기능

1. 바이트코드 실행

  • JVM은 컴파일된 Java 바이트코드(.class 파일)을 실행.
  • 자바 소스코드(.java)를 자바 컴파일러(javac)로 변환하면 플랫폼에 독립적인 바이트코드가 생성.
  • JVM은 이걸 인터프리팅하거나 JIT(Just-In-Time) 컴파일러로 네이티브 코드로 변환해서 실행.

2. 메모리 관리
JVM은 힙 메모리와 스택 메모리를 가상으로 관리.

  • 힙(Heap): 객체랑 클래스가 저장되는 영역.

  • 스택(Stack): 메서드 호출이랑 지역 변수가 저장되는 영역.

  • 가비지 컬렉션(Garbage Collection)을 통해 사용하지 않는 객체를 자동으로 정리.

3. 운영 체제 추상화
JVM은 자바 프로그램이 특정 운영 체제에 의존하지 않게, 운영 체제와의 상호작용을 추상화를 통해 독립적으로 사용.

4. 보안
JVM은 클래스 로더(Class Loader)바이트코드 검증 기능으로 프로그램 실행 시 보안을 유지한다. 그래서 악의적인 코드가 시스템에 접근하는 걸 방지.

실행 과정

  1. 소스 코드 작성
    .java 파일로 자바 소스 코드를 작성.

  2. 컴파일
    자바 컴파일러(javac)로 바이트코드로 변환해서 .class 파일을 생성.

  3. 클래스 로딩
    JVM이 클래스를 로드해서 메모리에 배치.

  4. 바이트코드 실행
    JIT 컴파일러로 바이트코드를 네이티브 코드로 변환하거나, 인터프리터 방식으로 실행.

예시

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
컴파일

bash
코드 복사
javac HelloWorld.java -> HelloWorld.class
실행

bash
코드 복사
java HelloWorld -> JVM이 HelloWorld.class 실행해서 결과 출력.
JVM 덕분에 자바 프로그램은 플랫폼 독립적으로 실행가능.
이외에도 성능 최적화, 보안 같은 다양한 기능을 제공한다.

정리

JDK는 Java 소스 코드를 바이트코드(.class)로 컴파일하고, JRE는 이 바이트코드를 실행한다.
JVM은 클래스 로더와 실행 엔진을 통해 바이트코드를 로딩 → 링크 → 초기화 → 실행한다.
실행 엔진은 인터프리터, JIT 컴파일러, GC 등을 활용해 바이트코드를 실행하고 메모리 관리를 수행합니다.

  • 컴파일은 대용량 즉, 책 한권을 번역하는 것에 유리하다.
  • 인터프린터는 책 한장을 번역하는 가벼운 것에 유리하다고 생각하면 된다.
  • 이는 JAVA의 경우 인터프린터와 컴파일러를 모두 사용하는 하이브리드에 속하는데 속도의 차이가 뭐가 더 우세하냐에 대해서 상황에 따라 다르다는 것이다. JAVA는 성능을 위해서 이를 채택한 것이다.

JVM 메모리 영역

jvm은 os로부터 메모리를 할당받는데 이 메모리를 자바 프로그램에 맞게 여러 영역으로 나누어 사용하게 된다. 크게 3가지 영역이다.
1. Heap 영역
2. Stack 영역
3. Static(Method) 영역

변수는 선언 위치에 따라 구분짓게 된다.
4가지 종류로 클래스 변수, 인스턴스 변수, 지역 변수, 매개 변수 

public class Variable {
	public static int age = 30; // 클래스 변수(전역 변수)
    
    int height = 175; // 인스턴스 변수 (전역 변수)
    
    public static void main(String[] args) { //매개 변수(파라미터)
		int size = 50; //지역 변수 
     }
}

Heap

인스턴스를 생성할 때 사용되는 메모리 영역

  • 참조형 데이터 객체의 실제 데이터가 저장되는 공간. Stack 영역에서 실제 데이터가 존재하는 Heap 영역의 참조값을 가진다.
  • new 키워드로 인스턴스를 생성 시, Heap 영역에는 생성된 객체가 저장, Stack 영역에서 생성된 객체에 대한 주소 값이 저장
Person person = new Person("HONG");

person은 stack 영역, new 객체 부분은 Heap 영역에 저장된다.
흐름
1. new를 사용해 객체를 생성할 때 저장한다.
2. 참조형 데이터 타입이 저장 (String, array, enum, class, interface), Object
3. Heap 영역의 데이터들을 가르키는 Reference(참조주소)는 Stack 영역에 적재되고 Reference를 통해서만 Heap 영역의 데이터에 접근, 관리 가능하다.
4. 호출이 종료되도 삭제되지 않는다. 가비지컬렉터 에 의해서 메모리에서 해제된다.
5. 쓰레드가 몇 개가 존재하든, 단 하나의 영역만 존재한다. stack 영역의 경우 쓰레드 별로 1개씩 생성

Stack

  1. 기본 자료형(원시 자료형, Primitive), 지역변수, 매개 변수가 저장되는 메모리 (int, double, boolean, byte)
  2. 메서드 내부의 기본 자료형에 해당하는 변수 적재
  3. Heap 영역에 생성된 데이터의 참조 값이 할당
  4. 메서드가 호출될 때 메모리에 할당, 메서드 종료 시 메모리에서 삭제
  5. 자료구조의 Stack의 구조로 LIFO의 특징
  6. 각 스레드마다 자신의 스택을 가짐.(Thread와 Stack은 1:1구조)
    • Thread는 내부적으로 Static, Heap, Stack 영역을 가진다. Thread는 다른 Thread에 접근할 수 없지만, static, Heap 영역을 공유하여 사용 가능하다.

Static(Method)

클래스 변수나, static으로 선언된 것들이 static 영역에 저장된다.

실행 흐름

  1. JVM이 실행될 때 Class가 로딩될 때 생성
  2. class의 정보, Static 변수(클래스 변수), 생성자, 메소드와 같은 것들을 저장
  3. Static 영역에 있는 것은 어디든 접근 가능
  4. JVM 종료 시(프로그램 종료를 뜻함) 메모리에서 해제된다. 프로그램이 돌아가는 동안 메모리상에 존재하기에 어디서든 접근 가능하고, 무분별하게 사용될 경우 메모리 부족 현상이 발생한다.

Garbage Collection

가비지 컬렉션은 가비지(유효하지 않는 메모리) 즉, 불필요한 메모리를 자동으로 관리해 준다.

  • 가비지 컬렉터: 가비지 컬렉션은 메모리 관리 기술중 하나로 가비지 컬렉터에 의해 수행되는 프로세스이고, 가비지 컬렉터는 메모리 관리를 담당하는 시스템 또는 프로그램 구성 요소이다. 메모리에 더이상 사용되지 않는 객체를 찾아 제거하여 메모리 회수 역할을 수행한다.
Person person = new Person();
person.add("hello")

person = null;

person = new Person();
person.add("world");

위와 같은 경우 hello를 가진 person 객체는 더 이상 참조를 하지 않고 사용되지 않아 가비지가 된다. 이런 메모리 누수를 방지하기 위해 가비지 컬렉터가 주기적으로 검사하여 메모리를 관리한다.
JVM의 Heap 영역은 처음 설계 당시 2가지 전제로 설계되었다.

  • 대부분의 객체는 접근 불가능 상태(Unreachable)가 된다.
    • 대부분의 객체는 일회성으로 생성되어 프로그램 실행 중 필요하지 않다.
  • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재.
    • 객체의 참조 방향이 새로운 객체에서 오래된 객체로 향하여 오래된 객체의 관리 비용을 줄인다.
      이는 객체 대부분이 일회성이 되며, 메모리에 오랫동안 남아있지 않는다는 전재로 객체의 생존 기간에 따라 나누어진다.
  • Young Generation(Young 영역)
    • 새롭게 생성된 객체가 할당되는 영역이다. 대부분 객체가 일회성으로 Unreachable 상태가 되기 때문에 많은 객체가 young에서 생성되었다 사라진다. 이 Young 영역에 대한 GC를 Minor GC라 한다.
  • Old Generation(Old 영역)
    • Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역이다. yound 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생한다.
    • Old 영역에 대한 GC 를 Major GC라 한다.
  • JVM 마다 영역이 다르다. 자바 8버전 이전에는 Perm 영역으로 Class의 메타 정보들이 저장되었지만 이 영역의 메모리 누수, OutofMemoryError등 클래스 로딩 및 언로딩 과정에서 메모리 할당 해제의 빈번한 발생으로 Metaspace 영역으로 대체되었다.
  • Metaspace 영역
    • Perm에서의 저장하던 Class의 메타 정보들은 이 영역에 저장된다.
    • Native Memory 영역에 위치하며, JVM이 아닌 OS레벨에서 관리한다. 이를 통해 JVM에서의 OutofMemoryError를 해결했다.
    • 클래스 메타데이터와, 리플렉션을 사용하는 애플리케이션에서 사용하는 일부 메모리를 저장
      • 리플렉션: 객체의 정보를 동적으로 탐색하고 수정하는 java의 기능중 하나로 런타임 시점에 객체를 다룰 수 있으며 제한된 캡슐화 해제 및 접근 가능하다.

Reachable과 Unreachable

Garbage Collection은 객체가 garbage인지 판별하기 위해 객체애 유효가 참조가 있다면 Reachable, 없으면 unreachable로 구별한다. Unreachable 객체를 가비지로 간주하여 gc를 수행한다.

위 사진과 같이 뿌리부터 root set으로 시작한 참조 사슬에 속한 객체들은 reachable 객체이고 나머지는 Unreachable 객체로 가비지 컬렉터의 대상이다.
가비지의 동작 과정에서 Stop the world

  • Stop the world: 가비지 컬렉터를 실행하기 위해 JVM 애플리케이션 실행을 멈추는 것으로 GC를 실행하는 스레드를 제외한 모든 작업을 멈추고 GC 작업 이후 중단된 작업을 다시 시작한다. GC 튜닝은 이런 stop the world 시간을 줄이는 것이다. 간단하게 유효 객체 판별 후 메모리 회수를 위해 작업이 더디기 대문에 일시적으로 멈추고 수행한다.

Minor GC

객체가 새롭게 생성되면 young 영역 중에서도 Eden 영역에 할당된다. 이 영역이 차게 되면 Minor GC가 발생하게 되는데

  • 사용되지 않는 메모리는 해제되고 Eden 영역에 존재하는 객체는 사용중인 Survivor 영역으로 옮겨진다.
  • Survivor 영역은 총 2개이지만 반드시 1개의 영역에만 데이터가 존재해야 한다. 총 3개의 영역 (Eden, Survivor2)
    각 영역의 절차
  • 새로 생성한 대부분의 객체는 Eden 영역에 위치한다.
  • Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동
  • Eden 영역에서 GC 발생 시 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 쌓인다.
  • 하나의 Survivor 영역이 가득 차면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동하고, 가득찬 영역은 아무 데이터도 없는 상태가 된다.
  • 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게 된다. 즉 이 절차대로 진행한다면, 위의 2개의 영역 중 하나의 영역에만 데이터가 존재하는 것에 만족한다. 만약 다른 상황일 경우에는 정상적인 상황이 아닌 것이다.

Major GC

Young 영역에서 오래 살아남은 객체는 Old 영역으로 Promotion 되었다. 그리고 Major GC는 객체들이 계속 Promotion 되어 Old 영역의 메모리 부족시 발생한다. young 영역은 할당 크기가 작기 때문에 짧은 시간에 GC가 끝난다. 그러나 Old 영역은 할당된 영역 크기가 크고, Young 영역을 참조 할 수 있기에 시간이 오래 걸린다.

Serial GC
Parallel GC
Parallel Old GC(Parallel Compacting GC)
Concurrent Mark & Sweep GC(이하 CMS)
G1(Garbage First) GC
등이 존재한다.

각 서비스의 WAS에서 생성하는 객체의 크기와 생존 주기가 모두 다르고, 장비의 종류도 다양하다. WAS의 스레드 개수와 장비당 WAS 인스턴스 개수, GC 옵션 등은 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다. - JavaOne에서 Oracle JVM을 만드는 엔지니어

참조: Naver D2

0개의 댓글