객체지향 프로그래밍, JVM

송현진·2023년 4월 9일

Java

목록 보기
2/11
post-thumbnail

JVM/자바 코드 실행 원리

  • JVM(Java Virtual Machine)

  • 자바 어플리케이션을 어느 CPU나 OS에서도 실행할 수 있게 지원하는 역할을 수행
    • 자바 코드를 컴파일하여 바이트 코드로 변환하여 해당 운영체제(OS)가 이해할 수 있는 기계어로 실행
    • 구성
      • Class Loader
      • Execution Engine
      • Garbage Collector
      • Runtime Data Area
  • 자바 어플리케이션 실행 과정
    1. 어플리케이션이 실행되면 JVM이 OS로부터 메모리를 할당 받음
      • JVM은 할당 받은 메모리를 용도에 따라 영역을 구분하여 관리
    2. 자바 컴파일러(javac.exe)가 자바 소스코드(.java)를 읽어 바이트 코드(.class)로 변환
    3. Class Loader를 통해 바이트 코드를 JVM으로 로딩
    4. 로딩된 바이트 코드(.class)는 Execution Engine을 통해 해석됨
    5. 해석된 바이트 코드는 Runtime Data Areas에 배치되어 실행됨
      • Runtime Data Areas가 실질적인 메모리를 할당 받아 관리를 하게 되는 영역
      • 실행되는 과정에서 GC(Garbage Collector) 같은 작업이 수행됨
  • Execution Engine

  • Runtime Data Areas에 할당된 바이트 코드를 실행시키는 주체
    • 코드를 실행하는 방식
      1. Interpreter

        • 바이트 코드를 해석하여 실행하는 역할
        • 같은 메소드라도 여러번 호출될 때 매번 새로 수행해야 함
      2. JIT(Just In Time) Compiler

        • 반복되는 코드를 발견하여 전체 바이트 코드를 컴파일하고 그것을 Native Code로 변경하여 사용

        Note. Native : 자바에서 부모가 되는 C 언어나 C++, 어셈블리어를 의미

      3. Garbage Collector
        - 더이상 참조되지 않는 메모리 객체를 모아 제거하는 역할을 수행
        - 일반적으로 자동으로 실행되지만, 수동으로 실행하기 위해 ‘System.gc()’를 사용할 수 있음(실행이 보장되지는 않음)

        JVM을 공부해야 하는 이유?

      • Garbage Collection을 이해하기 위해; 자바의 특징인 메모리를 자동으로 관리해준다는 부분에서 ‘퍼포먼스 튜닝’이라는 것을 하는데, 이 부분에서 가장 큰 파이를 차지하는 것이 Garbage Collection(GC)에 대한 설정값들을 변경해주는 작업
  • Garbage Collector
    • 앞으로 사용되지 않는 객체의 메모리를 Garbage라고 부르고, 이런 Garbage를 정해진 스케줄에 의해 정리해주는 것을 Garbage Collection(GC)이라 부름
    • GC는 Heap 메모리에서 활동하며, JVM에서 GC의 스케줄링과 개발자가 직접 관여하지 않아도 더 이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 담당
    • Stop The World(STW)
      • GC를 수행하기 위해 JVM이 멈추는 현상

      • GC가 작동하는 동안 GC관련 Thread를 제외한 모든 Thread는 멈춤(어플리케이션이 멈춰 있는 상태)

        Thread? 프로세스(process, 사용자가 작성한 프로그램이 OS에 의해 메모리를 할당 받아 실행 중인 것) 내에서 실제로 작업을 수행하는 주체. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행함

      • 일반적으로 ‘퍼포먼스 튜닝’이라는 것은 STW 시간을 최소화하는 것을 의미함

        퍼포먼스 튜닝 : GC가 어떤 방식으로 동작하는지와 어떠한 경우에 발생하는지를 체크하고, 주어진 어플리케이션에 맞는 설정값을 넣는 것

    • GC의 종류
      • Serial GC
      • Parallel GC(자바 8의 default GC)
      • CMS GC
      • G1 GC (자바 9, 10의 default GC)
      • Z GC (자바 11의 default GC, 레퍼런스가 많지 않음)
      • 어떤 GC가 좋다고 할 수는 없고, 주어진 어플리케이션, 서비스 특성에 맞는 GC를 선택하여 사용하는 것이 좋다
  • Class Loader

  • JVM으로 바이트 코드(.class)를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈
    • 로드된 바이트 코드들을 엮어서 JVM의 메모리 영역인 Runtime Data Areas에 배치함
    • 클래스를 메모리에 올리는 로딩 기능은 한번에 메모리에 올리지 않고, 어플리케이션에서 필요한 경우 동적으로 메모리에 적재하게 됨 : 클래스 내의 멤버를 호출하게 되면 로드한다
    • 클래스 파일의 로딩은 3단계로 구성됨 : Loading → Linking → Initialization
  • Runtime Data Area

  • 어플리케이션이 동작하기 위해 OS에서 할당받은 메모리 공간

  • 구성 요소

    • Method Area
      • static으로 선언된 변수들을 포함하여 Class 레벨의 모든 데이터가 이곳에 저장됨
        • JVM마다 단 하나의 Method Area가 존재
        • Runtime Constant Pool이라는 영역이 존재해서 상수 자료형을 저장하여 참조하는 역할을 함
        • 저장되는 정보의 종류
          • Field Info : 멤버 변수의 이름, 데이터 타입, 접근 제어자의 정보
          • Method Info : 메소드 이름, Return 타입, 매개변수, 접근 제어자의 정보
          • Type Info : Class인지 Interface인지 여부 저장, Type의 속성, 이름, Super Class의 이름
        • GC 관리 대상임
    • Heap Area

         
  • 객체를 저장하기 위한 메모리 영역

    • new 연산자로 생성된 모든 Object와 Instance 변수, 그리고 배열을 저장함
      • 구분
        • Young Generation : 생명 주기가 짧은 객체를 GC 대상으로 하는 영역
          Eden에 할당 후 Survivor 0과 1(S0, S1)을 거쳐 오래 사용되는 객체를 Old Generation으로 이동시킴
        • Old Generation : 생명 주기가 긴 객체를 GC 대상으로 하는 영역
        • GC 생명주기에 의해 지속적으로 메모리가 정리됨
          • Minor GC
          • Major GC
        • Method Area와 함께 여러 Thread들 간에 공유되는 메모리
    • Stack Area

    • 각 Thread를 위한 분리된 Runtime Stack 영역

      • 메소드를 호출할 때 마다 Stack Frame으로 불리는 Entry가 Stack Area에 생성됨
      • Thread의 역할이 종료되면 바로 소멸되는 특성의 데이터를 저장
      • 각종 형태의 변수나 임시 데이터, Thread 또는 메소드의 정보를 저장
    • PC Register (Program Counter Register)

      • 각 Thread가 시작될 때 생성되며, 현재 실행중인 상태 정보를 저장하는 영역
      • Thread가 로직을 처리하면서 지속적으로 갱신됨
      • Thread가 생성될 때마다 하나씩 존재함
      • 어떤 명령을 실행해야 할지에 대한 기록(현재 수행 중인 부분의 주소를 가짐)
    • Native Method Stack

      • 바이트 코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역
      • 자바가 아닌 다른 언어로 작성된 코드를 위한 영역
      • Java Native Interface를 통해 바이트 코드로 전환하여 저장
      • 각 Thread 별로 생성됨
  • JNI (Java Native Interface)

    • Java가 다른 언어로 만들어진 어플리케이션과 상호 작용할 수 있는 인터페이스를 제공
    • JVM이 Native Method를 적재하고 수행할 수 있도록 함
    • 실질적으로 제대로 동작하는 언어는 C / C++

Heap Area을 제대로 알아야 메모리 관리를 어떻게 최적화 할 수 있는지를 알 수 있고, GC가 어떻게 동작하는지도 알 수 있다.

  • GC의 원리
    • 약한 세대 가설(weak generational hypothesis)
      • 대부분의 객체는 금방 접근 불가능(Unreachable)한 상태가 된다.
        • 대부분의 객체는 중괄호({}) 안에서 생성되며, 이 객체들은 괄호가 끝나는 시점에서 더 이상 사용되지 않는다.
        • 특수한 경우에는 오래 사용할 수 있지만, 대부분의 경우 Unreachable한 상태가 되어 GC의 대상이 된다.
      • 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생한다.
        • 순차적인 로직에 의해 객체를 생성하여 활용하는데, 앞에 생성된 객체는 그다음의 로직에서 사용된 이후 대부분 사용되지 않게 된다.
    • 위의 가정을 기반으로 메모리 구조를 크게 2개로 나눈다.
      • Young Generation : Eden, S0, S1
      • Old Generation
    • GC Algorithm
      • Reference Counting Algorithm
        • Garbage 탐색에 초점을 맞춘 알고리즘
        • 각 객체마다 Reference Count를 관리하며, 이 카운트가 0이 되면 GC를 수행한다.(바로 메모리에서 제거된다.)
        • 순환 참조 구조에서 Reference Count가 0이 되지 않는 문제가 발생하여 Memory Leak이 발생할 수 있다.(Memory Leak : 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 것)
      • Mark-and-Sweep Algorithm
        • Root Set에서 Reference를 추적하여 참조 상황을 파악하고, Mark 단계에서 Garbage 대상이 아닌 객체를 마킹한다.
        • Sweep 단계에서 마킹되지 않은 객체를 지우는 작업을 수행하고 마킹 정보를 초기화한다.
        • GC가 동작하고 있을 경우, Mark 작업과 어플리케이션 Thread의 충돌을 방지하기 위해 Heap 사용이 제한된다.
        • Compaction 작업(디스크 조각 모음 같은)이 없어서 비어 있는 공간이 충분하지 않으면 Out of Memory가 발생할 수 있다.
      • Mark-and-Compact Algorithm
        • 위의 알고리즘에서 Compact 작업이 추가되어 흩어져 있는 메모리를 모아주는 작업을 진행한다.
        • 메모리 효율을 높일 수는 있지만, Compact 작업과 Reference를 업데이트하는 작업 때문에 오버헤드(Overhead)가 발생할 수 있다.
      • Copying Algorithm
      • Concurrent Mark-Sweep
      • Generational Algorithm
    • 일반적인 GC 과정 : 각 영역을 활용하여 최적의메모리 운영을 하게 됨
      1. 맨 처음 객체가 생성되면 Eden 영역에 생성
      2. Minor GC가 발생하면 미사용 객체의 제거와 함께 아직 사용되고 있는 객체는 Survivor0, Survivor1 영역으로 이동시킴
        단, 객체의 크기가 Survivor 영역의 크기보다 클 경우에는 바로 Old Generation으로 이동
      3. Survivor0과 Survivor1 영역은 둘 중 한 곳에만 객체가 존재하게끔 운영되며, 다른 한 곳은 비어 있어야 한다.
        객체가 존재하는 Survivor 영역(From)이 가득 차면 다른 Survivor 영역(To)로 보내고, 기존의 Survivor 영역(From)을 비우는 작업을 진행
      4. 1~3번 과정을 반복하면서 Survivor 영역에서 계속 살아남은 객체들에게 일정 score가 누적이 되어 기준치 이상이 되면 Old Generation 영역으로 이동하게 된다.(Promotion)
      5. Old Generation 영역에서 살아남았던 객체들이 일정 수준 쌓이게 되면, 미사용으로 판단된 객체들을 제거해주는 Full GC가 발생하고, 이 과정에서 STW가 발생한다.
    • GC 종류
      • Serial GC
        • 하나의 CPU로 Young Generation과 Old Generation을 연속적으로 처리하는 방식
        • 가장 오래된 GC이며 Mark-and-Compact 알고리즘을 사용한다.
        • GC가 수행될 때 STW가 발생한다.
      • Parallel GC
        • 자바 7, 8 버전에서 Default GC
        • Parallel GC의 목표는 다른 CPU가 GC의 진행시간 동안 대기 상태로 남아 있는 것을 최소화 하는 것
        • GC 작업을 병렬로 처리하여 STW 시간이 비교적 짧다
      • Parallel Compacting GC
        • Parallel GC 에서 Old Generation의 처리 알고리즘을 변경했다.
      • Concurrent Mark-Sweep(CMS) GC - 가장 많이 사용함(약 1년 전 기준)
        • Application의 Thread와 GC Thread가 동시에 실행되어 STW를 최소화하는 GC
        • Parallel GC와는 Compaction 작업의 유무로 구분된다.
      • Garbage First(G1) GC - 가장 많이 사용함(약 1년 전 기준)
        • 큰 메모리에서 사용하기 적합한 GC (대규모 Heap 사이즈에서 짧은 GC 시간을 보장하는데 목적을 둔다)
        • 전체 Heap 영역을 Region이라는 영역으로 분할하여 상황에 따라 역할이 동적으로 부여된다.(YG, S0, S1, OG처럼 역할이 고정되어있지 않다.)
      • Z GC
        • ZPage라는 영역을 사용하며, G1 GC의 Region은 크기가 고정인데 비해 ZPage는 2mb 배수로 동적으로 운영된다.
        • 정지 시간이 최대 10ms를 초과하지 않은 것을 목적으로 운영된다.
        • Heap 크기가 증가하더라도 정지 시간이 증가하지 않는다.
profile
개발자가 되고 싶은 취준생

0개의 댓글