ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] 자바 스터디 이모저모 OOP / POJO / JVM 메모리 구조 / GC / GC 알고리즘
    DEV/JAVA 2024. 5. 4. 18:09

    스터디 주제

    • java 에서 객체의 의미, OOP 란?
    • POJO 가 무엇이고 왜 필요한가요?
    • jvm 구조관점에서 메모리 영역에 객체와 메소드, 변수들이 어떻게 저장 되나요?
    • 상수와 static 의 필요성
    • 최신 자바 메모리 모델 소개
    • gc 란?
    • gc 는 어떻게 객체를 수집하나요? ( how do? )
    • gc 대표 알고리즘 소개

    * OOP란?

    객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 약자로, 현실세계를 프로그래밍으로 옮겨와 프로그래밍하는 것을 말한다. 현실 세계의 사물들을 객체라고 보고 그 객체로부터 개발하고자 하는 애플리케이션에 필요한 특징들을 뽑아와 프로그래밍하는 것이다. 이것을 추상화라한다.

     

    주요 특징

    1. 캡슐화 (은닉화) : 객체의 속성과 행위를 하나로 묶는 것. 목적은 구현 코드를 외부에 감춰 은닉하는 것

    2. 상속 : 상위 클래스에 정의된 기능을 가지고 재사용하거나 새로운 기능을 추가하여  코드의 중복을 줄이고 재사용성을 늘리는 방법

    3. 추상화 : 객체의 공통적인 속성과 행위를 하나로 묶는 것

    4. 다형성 : 객체가 상속을 통해 기능을 확장, 변경하여 여러 형태의 객체로 재구성 되는것을 의미 (Overriding, Overloading)

     

     

    * POJO 가 무엇이고 왜 필요한가요?

    : Spring Framework 특징 중 하나로 특정 '기술'에 종속되어 동작하는 것이 아닌 순수한 자바 객체를 의미한다.

    스프링 이전에는 특정 기술과 환경에 종속되어 자바 코드의 가독성이 떨어져 유지보수에 어려움이 있었다.

    또한 특정 기술의 클래스를 상속받거나 직접 의존하게 되어 확장성이 매우 떨어지는 단점이 존재 했고,

    이에 따라 본래 자바의 객체지향적인 장점을 살리는 POJO가 등장했다.

     

    * 스프링 프레임워크의 특징

    경량 컨테이너(크기와 부하의 측면)로서 자바 객체를 직접 관리

    각각의 객체 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수 있다.

    • POJO : 자바SE로 된 자바 객체(POJO)를 자바EE에 의존적이지 않게 연결해준다. POJO(Plain Old Java Object)란 단순히 평범한 자바빈즈(Javabeans) 객체를 의미한다.
    • IoC : 제어역전의 의미로 컨트롤러의 제어권이 사용자가 아닌 컨테이너에 있다.
    • DI : 의존성 주입이라는 뜻으로 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
    • AOP : 관점 지향 프로그래밍이라는 뜻으로 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.
    • PSA : Portable Service Abstraction의 약자로 환경의 변화와 관계없이 일관된 방식의 기술로의 접근환경을 제공하려는 추상화 구조를 말한다. 예를들어, 데이터베이스에 관계없이 동일하게 적용할 수 있는 트랜잭션 처리 방식등을 들 수 있다. 스프링이 제공하는 대부분의 API가 PSA이다.

    사용이유 :

    스프링이 나타나기 이전 엔터프라이즈 시스템 개발이 너무 복잡했고, 시스템 개발시 비즈니스 로직이외에도 고려할 사항들이 많았다.(타시스템과의 연계, 분산 트랜잭션 지원, 보안등) 또한 개발이 진행됨에 따라 비즈니스로직이 점점 복잡해지고 잦은 변경이 요구되기때문에, 이와 같은 복잡함을 해결하기 위해 스프링은 위와 같은 다양한 특징을 가졌다.

     

     

    * JVM 구조관점에서 메모리 영역에 객체와 메소드, 변수들이 어떻게 저장 되나요?

    • JVM이란 JAVA Virtual Machine, 자바 가상 머신의 약자
    • JVM은 JAVA와 OS사이에서 중개자 역할을 수행하여 운영체제에 독립적인 플랫폼을 갖게 해준다.
    • JVM은 자바 프로그램을 클래스 로더를 통해 읽어 자바 API와 함께 실행하는것이다.
    • JVM은 프로그램의 메모리 관리를 알아서 해준다. => GC

     

    * JVM 구조

    [출처] 위키피디아 자바가상머신

     

    이와 같이 JVM은 클래스로더(Class Loader), 런타임 데이터 영역(Runtime Data Area, =JVM Memory), 실행 엔진(Execution Engine)으로 구성되어있다.

    - JVM의 동작 방식

    1. 자바 프로그램을 실행하면 JVM은 OS로 부터 메모리를 할당받는다.

    2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 자바 바이트 코드(.class)로 컴파일한다.

    3. 클래스로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 Runtime Data Area(실질적 메모리 할당 및 관리 영역)에 올린다.

    4. Runtime Data Area에 로딩 된 바이트 코드는 Execution Engine을 통해 interpret(해석)된다.

    5. 이 과정에서 Execution Engine에 의해 Garbage Collector의 작동과 Thread 동기화가 이루어진다.

     

    간단하게 JVM의 구조와 메모리 영역을 확인했으니, 메모리 영역에 데이터가 어떻게 저장되는지 확인해보자.

     

    * JVM 메모리 구조

    -  Stack

    프로그램 실행 과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다. 메서드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame메소드내에서 정의하는 기본 자료형(int, double, byte, long, boolean 등)에 해당되는 지역변수(매개 변수 및 블럭문 내 변수 포함) 데이터의 값이 저장되는 공간이 Stack(스택) 영역이다. 해당 메소드가 호출될 때 메모리에 할당되고 종료되면 메모리가 해제된다.

     

    - Static area (= method = class area)

    클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다. 클래스와 인터페이스에 대한 런타임 상수 풀, 메서드와 필드, 전역변수와 정적변수(static이 붙은 자료형)를 Static 영역에 데이터를 저장한다. Static 영역의 데이터는 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있게 된다.

    * Runtime Constant Pool ? => Method 영역에 포함되지만 독자적 중요성을 띈다.

    클래스와 인터페이스 상수, 메서드와 필드에 대한 모든 레퍼런스를 저장하며

    JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리 상 주소를 찾아 참조한다.

     

    - Heap

    객체를 저장하는 가상 메모리 공간으로서 new연산자로 생성된 객체와 배열을 저장한다.

    참조형(Reference Type)의 데이터 타입을 갖는 객체(인스턴스), 배열 등은 Heap 영역에 데이터가 저장된다. 이때 변수(객체, 객체변수, 참조변수)는 Stack 영역의 공간에서 실제 데이터가 저장된 Heap 영역의 참조값(reference value, 해시코드 / 메모리에 저장된 주소를 연결해주는 값) new 연산자를 통해 리턴 받는다. 다시 말하면 실제 데이터를 갖고 있는 Heap 영역의 참조 값을 Stack 영역의 객체가 갖고 있다.

     

    - PC Register

    현재 실행중인 JVM 주소를 가지고 있다. CPU명령어 즉 Instruction을 수행한다.

    CPU instrunction 수행하는 동안 필요한 정보를 CPU내 기억장치인 레지스터에 저장, 연산 및 결과 값을 메모리에 전달하기 전 CPU내 기억장치이다.

     

    - Native Method Stack

    자바 외 언어로 작성된 네이티브 코드를 위한 메모리로 C/C++ 등의 코드를 수행하기 위한 스택이다.

    native 메서드의 매개변수, 지역변수 등을 바이트 코드로 저장한다.

     

    여기서 Static 영역과 Heap 영역은 모든 쓰레드가 공유하는 영역이고,

    나머지 Stack 영역, PC Register, Native Method Stack 영역은 각 쓰레드 마다 생성되는 개별 영역이다.

     

     

    *  상수와 static 의 필요성

    • 모든 곳에서 하나의 값을 일관되게 사용하기 위함. 따라서 상수를 선언할 때는 static final로 선언하는 것이 관례이다.
    • static final로 상수를 선언하면 인스턴스가 만들어질 때마다 새로운 메모리에 초기화하지 않고, 하나의 메모리 공간만을 사용할 수 있어 효율적이다.

    final keyword

    변하지 않는, 오직 하나만 존재한다는 의미가 강함. static keyword와 주로 같이 사용

    • final class : 다른 클래스에서 상속하지 못한다.
    • final method : 다른 메소드에서 오버라이딩하지 못한다.
    • final variable : 변하지 않는 상수값이 되어 새로 할당할 수 없는 변수가 된다.

    static keyword

    클래스를 호출할 때 메모리 공간을 할당하는데 처음 설정된 메모리 공간이 변하지 않음을 의미한다. 객체를 아무리 많이 만들어도 해당 변수는 하나만 존재한다는 것이다.

     

     

    * 최신 자바 메모리 모델 소개

    알아보니 최신 자바 메모리 모델까지는 아니고 Java8 이전과 이후에 Heap영역 메모리에 차이가 있었다.

    Java8 이전 Heap영역 메모리 구조

    1. New/Young

    • 메모리에 객체가 생성되면 Eden영역에 객체가 저장된다.
    • Eden 영역에 데이터가 가득 차면, Eden 영역에 있던 객체가 Survivor1 또는 Survivor2로 옮겨진다. 이 두 개의 영역 사이에 우선순위가 있는 것은 아니다.
    • Eden 영역에서 Survivor1 또는 Survivor2로 옮겨지는 객체들은 어딘가에서 참조되고 있는 객체들이다. 둘 중 하나의 영역이 가득 차게 되면 공간이 남아있는 Survivor로 이동하게 된다.
    • 이러한 매커니즘 때문에 Survivor1 또는 Survivor2 둘 중 하나는 항상 비워있는 공간이 있는 채로 유지된다.

    이러한 과정에서 1차 GC라고 불리는 Minor GC가 발생한다. Minor GC는 New/Young 영역에서 발생하는 GC로 Eden영역 또는 Survivor1 또는 Survivor2에서 사용되지 않는 객체들을 삭제한다.

    2. Old

    • Survivor1 / Survivor2를 왔다 갔다 하는 과정에서 오랫동안 살아남은 객체들은 Old 영역으로 이동한다. 보통 Old 영역은 Young 영역보다 크게 할당하며, 이러한 이유로 Old 영역의 GC는 Young 영역보다 적게 발생한다.
    • 단, Young 영역에서 Old 영역으로 넘어가는 객체 중, Survivor 영역을 거치지 않고 Eden 영역에서 바로 Old 영역으로 넘어가는 객체도 존재하는데, 이는 객체의 크기가 아주 클 경우 발생한다. 예를들어 Survivor영역의 크기가 16MB인데 객체의 크기가 20MB일 경우에 해당한다.
    • Old 영역에서는 2차 GC인 Major GC(FULL GC)가 일어나게 되며 GC를 진행하는 Thread를 제외하고 이외의 모든 Thread를 멈춘 상태로 GC가 진행된다. 이와 같이 GC를 진행하는 Thread 이외에 모든 Thread를 멈추는 상태를 Stop-the-world라고 하며, 어떠한 GC알고리즘을 사용하더라도 Stop-the-World 상태를 피할수는 없다.

    Java8 이후 Heap영역 메모리 구조

    위에서 Heap메모리 구조를 설명하면서 설명하지 않은 영역이 하나 있다. 바로 Permanent 영역이다.

    Permanent 영역의 경우 Java8부터 Metaspace 영역으로 변경되었다. 기존의(자바8 이전) Permanet 영역에는 다음과 같은 정보들이 저장되었다.

    • Class의 Meta Data
    • Method의 Meta Data
    • Static Object 변수, 상수
    • JVM, JIT 관련 데이터 등

    이러한 데이터들을 저장하던 Permanent 영역을 자바8에서부터는 Metaspace라는 영역으로 대체하였고, 이 Metaspace영역은 Native메모리 영역으로 JVM이 아닌 OS에 의해 관리되도록 변경되었다.

    즉, 자바8 이후부터 static 객체는 heap 영역이 아닌 별도의 네이티브 메모리 영역에서 관리된다.

     

    거의 그럴일은 없지만 가끔씩 Collection 객체를 static하게 구현하여 값을 계속해서 추가하다가 Perm영역이 가득차서 OutOfmemoryerror permgen space 라는 Error를 경험한 적이 있을 것이다.

    static List<Object> list = new ArrayList<>();

    즉, 이러한 OOM 에러가 발생하는 현상을 개선하기 위해 기존에 Perm 영역에 저장되던 static Obejct의 변수와, 상수화된 static Object를 Heap영역으로 이동시켜 GC의 대상이 되도록 변경하고, 메타데이터 정보들을 OS가 관리하는 영역으로 옮겨 Perm 영역의 사이즈 제한을 없앤 것이라고 할 수 있다.

     

     

    * gc 란?

    Garbage Collection의 약자, JVM의 Heap영역에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.

     

    - 실행 순서

    1. 참조되지 않은 객체들을 탐색 후 삭제
    2. 삭제된 객체의 메모리를 반환
    3. Heap 메모리의 재사용

     

    * gc 는 어떻게 객체를 수집하나요? ( how do? )

    도달 가능성(reachability) 이라는 개념을 사용해 메모리 관리를 수행한다.

    객체에 레퍼런스가 있다면 Reachable, 객체에 유효한 레퍼런스가 없다면 Unreachable로 구분되어 내부 알고리즘(Mark And Sweep)을 통해메모리 수거한다.

     

    - Mark And Sweep

    • 가비지 컬렉터는 루트(root) 정보를 수집하고 이를 ‘mark(기억)’ 합니다.
    • 루트가 참조하고 있는 모든 객체를 방문하고 이것들을 ‘mark’ 합니다.
    • mark 된 모든 객체에 방문하고 그 객체들이 참조하는 객체도 mark 합니다. 한번 방문한 객체는 전부 mark 하기 때문에 같은 객체를 다시 방문하는 일은 없습니다.
    • 루트에서 도달 가능한 모든 객체를 방문할 때까지 위 과정을 반복합니다.
    • mark 되지 않은 모든 객체를 메모리에서 'Sweep(삭제)'합니다.

     

    * gc 대표 알고리즘 소개

     

    * Serial GC

    • Young 영역에서 Mark and Sweep대로 수행됨
    • Old 영역의 경우 Mark Sweep Compact라는 알고리즘이 사용됨
    • Compact는 Heap영역을 정리하기 위한 단계로, 우선 Old 영역의 살아있는 객체를 식별(Mark)하고 Sweep하여 살아있는 것만 남긴다. 이후 Heap영역의 앞 부분부터 채워서 객체가 존재하는 부분과 존재하지 않는 부분으로 나누는 것이다(Compact).
    • CPU 코어가 하나일 때 사용하기 위해 개발된 것으로 모든 GC를 처리하기 위해 하나의 스레드만 사용함

     

    - 실행명령어

    java -XX:+UseSerialGC -jar Application.java

     

     

    * Parallel GC

    • Serial GC와 기본적인 처리 과정은 동일
    • 여러 개의 스레드를 사용하여 병렬적으로 GC를 수행함으로써 GC에서 발생하는 오버헤드를 많이 줄여줌
    • 멀티 프로세서나 멀티 스레드 머신에서 큰 규모의 데이터를 처리하는 애플리케이션을 위해 만들어졌다.

     

    - 실행명령어

    java -XX:+UseParallelGC -jar Application.java 
    # -XX:ParallelGCThreads=N : 사용할 쓰레드의 갯수

     

    * Parallel Old GC

    • JDK 5 update 6부터 제공한 방식
    • Parallel GC와는 Old 영역의 GC 알고리즘만 다름
    • Parallel Old GC는 Mark Sweep Compact가 아닌 Mark Summary Compaction이 사용
    • Summary 단계에서 앞서 GC를 수행한 영역에 대해서 별도로 살아있는 객체를 식별한다는 점에서 조금 다르며 약간 더 복잡하다.

     

    -실행명령어

    java -XX:+UseParallelOldGC -jar Application.java
    # -XX:ParallelGCThreads=N : 사용할 쓰레드의 갯수

     

    * CMS GC

    • Paraller GC와 마찬가지로 여러 개의 스레드를 사용
    • 기존 방식과는 다르게 Mark Sweep 알고리즘을 Concurrent하게 수행
    • 다른 알고리즘보다 CPU를 더 많이 사용하며 Compaction 단계를 수행하지 않는 단점이 있음. 그래서 시스템이 오래 운영되면 조각난 메모리들이 많아 Compaction 단계가 수행되면 오히려 stop-the-world 시간이 길어지는 문제가 발생할 수 있기 때문에 Compaction 작업이 얼마나 자주 수행되고 오랫동안 수행되는지 확인해줘야 한다.
    • java 9부터 deprecated 됐고 java14에서는 사용 중지가 되었다.

     

    -실행명령어

    java -XX:+UseConcMarkSweepGC -jar Application.java

     

    * G1 GC

    • CMS GC에서 생기는 문제를 대체하기 위해 개발되었으며 java 7부터 지원
    • 기존 GC들은 Heap 영역을 Young 영역과 Old 영역으로 나누어서 사용했지만 G1 GC는 Heap을 동일하게 나눈 바둑판처럼 생긴 각 영역에 객체를 할당하고 GC를 실행. 그러다가 각 영역이 가득 차면 다른 영역에서 객체를 할당하고 GC를 실행한다.
    • 기존의 Young → Old로 이동하는 단계가 사라진 GC 방식
    • G1 GC는 기존의 Eden, Survivor, Old의 역할 + Available/Unused 와 Humonogous 라는 2가지 역할이 추가
    • Available/Unused는 사용되지 않는 영역을 의미하며 Humonogous는 영역 크기의 50%를 초과하는 객체를 저장하는 영역을 의미
    • Heap영역을 동일한 크기로 나누고 Garbage가 많은 영역을 우선적으로하여 GC를 수행
    • 다른 GC보다 훨씬 빠르고 큰 메모리 공간에서 멀티 프로세스 기반으로 운영되는 서비스를 위해 고안
    • Java9부터 기본 Garbage Collector로 사용

     

    -실행명령어

    java -XX:+UseG1GC -jar Application.java

     

    * Shenandoah GC

    • Java 12에 release
    • 레드 햇에서 개발한 GC
    • 기존 CMS가 가진 단편화, G1이 가진 pause의 이슈를 해결
    • 강력한 Concurrency와 가벼운 GC 로직으로 heap 사이즈에 영향을 받지 않고 일정한 pause 시간이 소요가 특징

     

    -실행명령어

    java -XX:+UseShenandoahGC -jar Application.java

     

    * ZGC

    • Java 15에 release
    • 대량의 메모리(8MB ~ 16TB)를 low-latency로 잘 처리하기 위해 디자인 된 GC
    • G1의 Region 처럼,  ZGC는 ZPage라는 영역을 사용하며, G1의 Region은 크기가 고정인데 비해, ZPage는 2mb 배수로 동적으로 운영됨.
    • ZGC가 내세우는 최대 장점 중 하나는 힙 크기가 증가하더도 'stop-the-world'의 시간이 절대 10ms를 넘지 않는다는 것

     

    - 실행명령어

    java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar Application.java

     

     

    참고 문헌

    출처: https://inpa.tistory.com/entry/JAVA-☕-JVM-내부-구조-메모리-영역-심화편

    출처: https://1-7171771.tistory.com/140

    출처: https://yeon-kr.tistory.com/112

    출처: https://ko.javascript.info/garbage-collection

    출처: https://flightsim.tistory.com/240

    출처: https://seungh1024.tistory.com/73

    출처: https://inpa.tistory.com/entry/JAVA-☕-가비지-컬렉션GC-동작-원리-알고리즘-💯-총정리#garbage_collectiongc_이란?

    댓글

Designed by Tistory.