JVM 개념정리

박종현·2022년 5월 15일

자바(Java) 기본

목록 보기
2/8


1. RuntimeDataArea

핵심 자바 메모리 구조(JAVA Static, Heap, Stack)

  1. Static Area(Method Area, Code Area):
    • 컴파일로 된 .class 코드(0과 1로 된 2진수 (binary)) 영역 (= 클래스/ 코드 /메서드 영역)
    • static 변수/ static 메서드 (=static영역) : class 가 실행되기 전에 미리 메모리가 확보되고 프로그램이 종료될 때까지 메모리공간에 존재한다. → JVM이 실행되면서 생기는 공간, 부하가 크다.
    • 프로그램이 실행되면 모든 코드가 저장되어있는 상태가 아니고 new 키워드로 객체가 생성되기 전엔 텍스트 일뿐이다. → 객체 생성 후 메서드를 실행하면 해당 클래스 코드에 대한 정보를 코드 영역에 저장하게 된다(클래스 로드).
    • Class 정보, 전역변수 정보, Static 변수 정보가 저장되는 공간이다.
    • Runtime Constant Pool 에는 말 그대로 '상수' 정보가 저장되는 공간이다.
    • 모든 스레드에서 정보가 공유된다.
  2. Heap Area:
    • 프로그램 실행중 객체의 생명주기에 따라 동적으로 메모리가 할당되는 영역
    • new 연산자로 생성된 객체, Array와 같은 동적으로 생성된 데이터가 저장되는 공간
    • Heap에 저장된 데이터는 Garbage Collector 가 처리하지 않는한 소멸되지 않는다.
    • Reference Type 의 데이터가 저장되는 공간
    • 모든 스레드에서 정보가 공유된다.
  3. Stack Area :
    • 메소드 호출 시 생성되는 지역 변수와 매개 변수가 저장되는 영역(잠시 사용되고 필요가 없어지는 영역)→ 메소드 호출이 완료되면 사라짐 , 생명주기가 가장 짧다
    • Last In First Out, 나중에 들어온 데이터가 먼저 나간다
    • 만약, 지역변수 이지만 Reference Type일 경우에는 Heap 에 저장된 데이터의 주소값을 Stack 에 저장해서 사용하게 된다.
    • 스레드마다 하나씩 존재한다.

2. Class Loader

Person.java 파일을 작성한 후 javac 명령어를 통해 컴파일하면 Person.class와 같이 .class 파일이 생성된다. 이 .class 파일은 java 코드를 바이트 코드로 변환한 파일이다. Class Loader는 이렇게 생성된 class 파일들을 엮어 JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area에 적재하는 역할을 한다.

  • 바이트 코드 (.class)를 읽고 메모리에 저장한다.
  • 로딩(클래스를 읽어오는)의 과정을 진행한다.
    • 런타임 영역 중 메소드 영역에 저장
      • FQCN (Fully Qualified Class Name) 기준으로 저장 -> 모든 경로를 포함한 파일
      • 클래스, 인터페이스, ENUM
      • 메서드와 변수
    • 로딩이 끝나면 해당 클래스 타입의 클래스 객체(인스턴스)를 생성하여 런타임 영역 중 Heap에 저장링크 과정을 진행한다.
  • 링크 과정은 Verify -> Prepare -> Resolve(Optional) 과정을 거치게 된다.
    • Verify: 바이트 코드(.class) 파일이 유효한 지 체크
    • Prepare: 심볼릭 레퍼런스 연결 -> 메서드 영역의 실제 주소로 교체한다.
  • 초기화 과정을 진행한다.
    • staitc 값들 초기화 및 변수에 할당 작업을 진행한다.

3. Execution Engine

Class Loader에 의해 메모리에 적재된 바이트 코드들을 기계어로 변경하여 실행하는 역할을 한다. Interpreter 방식과 JIT(Just-In-Time) 컴파일러를 이용하는 방식이 있다. Interpreter 방식은 명령어 단위로 바이트 코드를 읽어 실행한다. Interpreter 방식이 한 줄씩 수행하기 때문에 느리다는 단점이 있어 이 단점을 보완하기 위해 JIT 컴파일러 방식이 도입되었다. JIT 컴파일러 방식은 자주 실행되는 바이트 코드들에 대해 전체를 컴파일 하여 네이티브 코드로 변경하는 방식이다. 네이티브 코드는 캐시에 보관되기 때문에 한 번 컴파일된 코드는 빠르게 수행이 가능하기 때문이다. 하지만 한 줄씩 컴파일하는 Interpreter 방식에 비해 바이트 코드 전체를 컴파일하는 JIT 방식의 속도가 훨씬 느린 탓에 Interpreter 방식과 JIT 방식을 혼용하는 것이 가장 효율적이다.

  • 인터프리터: 바이트 코드를 한 줄씩 해석하고 실행한다. 상대적으로 속도가 느리다.
  • JIT 컴파일러
    • 인터프리터 단점인 속도를 개선한 방식
    • 정적 컴파일과 동적 컴파일을 동시에 사용하는 방식
    • 인터프리터로 실행하다가 적당한 시점에 전체 컴파일해 네이티브 코드로 변경하고 캐시(저장)하여 이후 동일 메서드는 인터프리터를 하지 않는다.

4. Garbage Collector

Heap 메모리 영역에 생성된 객체들 중 참조되지 않는 객체들을 탐색 후 제거하는 역할을 한다. GC가 수행되는 동안은 GC를 수행하는 쓰레드 외의 모든 쓰레드는 일시정지된다. 특히 Full GC가 발생하면 수 초간 모든 쓰레드가 정지하게 되어 장애로 이어지는 문제가 발생할 수 있다.

5. 자바 코드 실행 원리

  1. 자바의 모든 코드는 클래스 내부에 존재해야 한다.

  2. 자바는 실행전에 static 멤버를 모두 static 메모리 공간에 로드한다.

  3. 자바프로그램을 실행(컴파일된 자바파일 → 클래스파일 .class)하면 main 메소드를 실행하고

    1. 메인메소드는 반드시 static 메소드로 선언해야한다
  4. main 메소드가 끝나면 종료된다

    public class Ex01 {
        private static int n2 = 20;
    
        public static void main(String[] args) {
            int n1 = 10;
            TestClass testClass = new TestClass(); // Heap공간에 메모리가 동적으로 할당됨
            int n3 = testClass.getNum(); // TestClass의 메소드 실행결과가 stack공간의 n3에 자장
            System.out.println(n1); // main이라는 stack 공간의 n1
            System.out.println(n2); // Ex01이라는 클래스의 static 공간의 n2
            System.out.println(n3); // main이라는 stack 공간의 n3
        }
    }
    
    class TestClass {
        private int n3 = 30;
    
        public int getNum() {
            return this.n3;
        }
    }

일반변수와 참조변수

  • 일반변수(Primitive Type)는 컴파일 시점에 데이터 크기를 알 수 있으며 메모리에 값을 저장하고 있다.
    • int, double boolean, char ...
  • 참조변수(Custom Type) 는 컴파일 시점에는 데이터 크기를 알 수 없기 때문에(런타임시 알 수 있음) 4Byte의 메모리를 할당하고 힙메모리의 주소값을 저장한다.
    • Class 자료형

      // new 가 되어서 힙에 할당이 될 때 사이즈를 알 수 있다.( 프로그램이 실행 되었을 때  = Runtime )
      class MyData {
          int id = 1;
          int price = 1000;
      }
      public class Ex02 {
          public static void main(String[] args) {
      //        int num; -> 변수를 선언한다.
      //        int num = 10 -> 변수를 초기화(메모리에 할당)힌다.
              int num = 10; // 일반변수( 크기가 정해져 있는것 = 컴파일 시점 )
              MyData d = new MyData(); // 레퍼런스변수( 크기가 정해져 있지 않는 것 = 런타임 시점 )
      
              System.out.println(num);
              System.out.println(d.id);
              System.out.println(d.price);
          }
      }

0개의 댓글