menuhwang

[Java] JVM, 자바 실행부터 메모리 구조까지 본문

자바

[Java] JVM, 자바 실행부터 메모리 구조까지

menuhwang 2022. 12. 11. 17:25

개요


자바 파일을 작성하고 실행되기까지의 과정과 JVM안은 어떤 구조로 되어있는지 공부하고 Heap 영역, Method 영역 등 메모리 구조는 어떻게 되는지 어떤 값이 어느 위치에 저장되는지 정리해보았다.

 

자바 파일 실행 과정


*.java


우리가 작성하는 Java의 소스코드 파일이다. *.java파일은 JVM에서 바로 읽을 수 없다. 따라서 바이트 코드로 컴파일하는 과정이 필요하다.

 

*.class


Java의 소스코드를 컴파일하면 Class 파일이 생성된다. 바이트 코드라고도 불리며 JVM이 읽을 수 있다.

 

JVM


JVM이란, Java Virtual Machine 자바 가상 기계 즉, 자바 파일을 컴파일하고 생성된 클래스 파일을 실행시키는 프로그램이다.

이 JVM이 설치되어 있는 환경이라면 어떤 OS에서든 자바 파일을 실행시킬 수 있다.

 

요약


1. 자바 소스 파일 작성.

2. 클래스 파일(바이트코드)로 컴파일

3. JVM에서 실행.

 

JVM


JVM이 OS 마다 만들어져있는 덕분에 OS에 종속되지 않고 자바파일을 실행시킬 수 있다. 이러한 JVM이 어떤 구조로 구성되어있는지 알아보자.

 

 

클래스 로더 : Class Loader


클래스 파일을 JVM Runtime Data Area로 로딩하는 역할.

런타임시 동적으로 클래스 파일을 읽어온다. 즉, 애플리케이션 실행 중 처음으로 클래스가 로드되면 클래스 로더가 클래스 파일을 읽어오게 된다는 것이다.

 

실행 엔진 : Executor Engine


클래스 로더가 Runtime Data Area로 바이트 코드를 읽어오면 실행 엔진은 이 바이트 코드를 실행시키는 역할을 하게 된다.

실행 엔진 내부에서 바이트 코드를 바이너리 코드(기계어)로 변환하고 실행한다.

 

가비지 콜렉터 : Garbage Collector (GC)

더 이상 사용되지 않는 인스턴스를 메모리에서 삭제한다.

GC가 주로 동작하는 대상은 Heap 영역이다. 언제 GC의 대상이 되어 처리되는지는 뒤에서 다뤄보록 하겠다.

 

 

Runtime Data Area


프로그램을 실행시키기 위해 OS로 부터 할당받은 메모리 공간.

 

 

Method Area

클래스로더가 클래스 파일을 로딩하면 이 영역에 클래스 정보, 상수 정보, 인스턴스 변수 정보, 메소드 정보를 담아둔다.

그리고 static 변수도 이 영역에 저장된다.

 

Heap Area

객체를 저장하는 공간. new로 생성되는 객체들이 이 Heap영역에 저장된다.

Heap영역에 더 이상 참조되지 않는 객체가 있다면 GC의 대상이 되어 메모리에서 지워진다.

 

Method Area와 Heap Area는 쓰레드 간 공유되는 메모리 영역이다.

 

Stack Area

메소드가 호출되면 메소드 내부 지역 변수가 저장된다.

Heap영역에 생성된 객체의 주소값을 저장한다.

원시 타입은 그 값 자체를 저장한다.

Stack영역은 각 쓰레드 마다 생성되는 독립된 공간이다.

 

PC Register

쓰레드가 어떤 명령어를 실행해야하는지 기록하고 현재 수행중인 JVM 명령의 주소값을 갖는다.

 

동작 예시


코드

Counter.java

public class Counter {
    private String name;
    static int count;

    public Counter(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getCount() {
        return count;
    }

    public int increase() {
        return ++count;
    }

    public int decrease() {
        return --count;
    }
}

CounterTest.java

public class CounterTest {
    public static void main(String[] args) {
        Counter counter = new Counter("cnt1");
        counter.increase();
        System.out.println("Counter Name : " + counter.getName());
        System.out.println("Counter count : " + counter.getCount());
        // Counter Name : cnt1
        // Counter count : 1


        counter = new Counter("cnt2");
        counter.decrease();
        System.out.println("Counter Name : " + counter.getName());
        System.out.println("Counter count : " + counter.getCount());
        // Counter Name : cnt2
        // Counter count : 0
    }
}

 

실행 과정

1. CounterTest.main() 실행

Counter counter = new Counter("cnt1");

처음으로 Counter클래스가 로드되므로 Counter 클래스 정보가 Method 영역에  로드됨.

이때 Counter클래스의 static 변수 count도 Method영역에 로드됨.

2. new Counter("cnt1") 인스턴스 생성.

new Counter("cnt1")가 호출되어 인스턴스가 생성되고 Heap영역에 인스턴스가 저장됨.

Counter counter 지역변수는 Heap영역의 인스턴스 메모리 주소값이 담겨 Stack영역에 저장됨.

3. counter.increase()

counter.increase();

Method영역에 있는 static변수 count를 1증가시킨다.

4. 출력

System.out.println("Counter Name : " + counter.getName());
System.out.println("Counter count : " + counter.getCount());
// Counter Name : cnt1
// Counter count : 1

Heap영역에 저장되어있는  counter의 name을 가져와 출력하고, Method영역에 저장되어있는 count를 가져와 출력한다.

 

 

5. new Counter("cnt2") 새로운 인스턴스 생성.

counter = new Counter("cnt2");

역시 Heap영역에 새로운 인스턴스를 저장하고 counter는 새로운 주소값을 가지게 된다.

counter가 새로운 주소값을 가지게 되면서 더이상 기존 new Counter("cnt1") 인스턴스는 참조되지 않아 GC가 Heap영역에서 데이터를 지우게 된다.

GC가 동작할때는 모든 쓰레드가 멈춰 성능이 저하된다.

 

6. counter.decrease()

counter.decrease();

Method영역에 count를 1 감소 시킨다.

7. 출력

System.out.println("Counter Name : " + counter.getName());
System.out.println("Counter count : " + counter.getCount());
// Counter Name : cnt2
// Counter count : 0

 

이 과정으로 알 수 있었던 점.

1. static 메소드 안에서  인스턴스 변수를 사용할 수 없는 이유.

static 메소드는 클래스 로드시 Method영역에 저장됨.

하지만 인스턴스 변수는 인스턴스 생성시 메모리에 저장되므로 static 메소드 호출시 인스턴스 변수가 메모리에 저장되어있다고 보장할 수 없음. 따라서 static 메소드에는 static 변수만 사용 가능함.

 

2. 서로 다른 인스턴스가 static 변수 값을 변경, 조회하여도 같은 값을 갖는 이유.

static 변수는 Method영역에 한 번만 저장됨. 그리고 이 Method영역은 모든 쓰레드에서 공유함.

그렇기에 서로 다른 인스턴스가 static 변수 값을 변경, 조회하면 같은 주소값을 바로 보고 있기때문에 값을 공유하게됨.

따라서 자주 사용되고 인스턴스 변수와 관계 없는 메소드또는 상수라면 static으로 선언하여 여러 인스턴스로 생성해 불필요하게 많은 메모리를 사용하지 않도록 사용할 수 있음.

 

3. GC가 동작할때는 모든  쓰레드가 멈춤.

GC가 동작하여 메모리를 정리할때 모든 쓰레드가 멈추므로 이를 기억하고 유의하도록.

'자바' 카테고리의 다른 글

[OOP 트레이닝] 주차장 시스템 - (2)  (1) 2023.06.29
[OOP 트레이닝] 주차장 시스템 - (1)  (0) 2023.06.28
[Java] static 이란?  (0) 2022.09.30
[Java] 오버라이딩 (feat. 오버로딩)  (0) 2022.09.26
[Java] 상속  (0) 2022.09.26
Comments