[F-lab 모각코 챌린지 37일차] TIL

JeongheeKim·2023년 7월 7일

TIL

목록 보기
37/66

JVM

작성한 코드가 실행되기까지 과정

  1. 프로그래머가 자바 .java파일 을 작성한다.
  2. javac compiler에 의해 .java.class 파일로 컴파일 된다.
    • build란? 간단히 말해 build = compile + linking 과정이다. build란 compile - packaging - testing - distribution 과정으로 구성되어있다. 위의 compile이 빌드 단계중 하나이다. 대표적인 build tool로는 maven, gradle, ant가 있다.
    • .class 파일은 JVM이 이해할 수 있는 바이트 코드로 구성되어있다. 바이트 코드로만은 실행이 불가하다.
    • .java.class로 컴파일했다라는것은 문법검사는 마쳤다의 의미도 내포됨, 시간 단축
    • 하지만 소스코드 변경 시 마다 컴파일 필요하지만 java의 모토로 보면 한번 컴파일로 어디서나 실행 시킬 수 있다.(one compile, run every where)
  3. .class 파일은 Class Loader에 의해 JVM의 메모리 영역인 Runtime Data Area로 로딩
  4. Execution Engine에 의해 .class 파일 해석
    • 인터프리터를 통해 메모리에 로드된 바이트 코드를 한줄 씩 해석하고 실행함
    • JIT(Just-In-TIme)컴파일러를 통해 바이트 코드를 컴퓨터가 이해할 수 있게 기계어로 바꾸는 작업
  5. Engine에 의해 기계어로 해석된 것들이 Runtime Data Area에 배치되어 쓰레드 동기화나 GC를 수행하게 됨
    1. 실행 준비가 완료되면 JVM은 main 메서드(entry point)를 호출
    2. 호출된 main 메서드를 실행할 메인 스레드가 생성되며 메인 스레드의 JVM stack이 생성됨
    3. 앱이 실행되며 필요한 시점 마다 필요한 처리 및 메모리 확보 데이터 저장

왜 런타임까지 컴파일을 미룰까?

아래와 같이 추론해 봤습니다.

  • "Write once, run anywhere” 자바의 모토에 따라 javac에 의해 컴파일된 바이트 코드만 있다면 어떠한 플랫폼에 맞게 구현된 JVM에 가져가서 해석될 수 있기 때문이다. 이는 플랫폼에 종속되지 않고 독립성을 가질 수 있다.
  • 파이썬은 코드를 전달한 뒤, 직접 실행해야 하지만 자바의경우 이미 컴파일한 바이트 코드(.class파일)만 전달 하면되기때문에 직접적인 코드 공개가 되지 않고 이미 컴파일이 되었다는것은 문법 검사가 완료되었다는것을 보장한다.
  • JIT 컴파일러 특성 상 자주 사용되는 코드를 분석하여 미리 컴파일 한다고 한다. 이 특성을 살려 미리 문법 검사를 마친 바이트 코드가 있다면, 매번 컴파일 하지 않고 어플리케이션 실행 성능에 도움이 되지 않을까 생각이 든다.

객체의 Lifecycle

  1. Created (생성)

    객체를 heap 메모리에 할당하고, 생성자 호출 및 인스턴스 변수 초기화를 한다.

    Dog dog = new Dog();

  2. In use or reachable ( 사용중 )

    참조중인 객체를 뜻한다. 생성된 객체의 참조변수를 통해 객체를 사용한다.

    객체가 참조중일때, GC대상이 되지 않는다.

  3. Invisible ( 사용 중이며 접근불가 )

    프로그램 상에서 더이상 객체에 대한 참조가 존재하지 않아 접근할 수 없을때

    모든 객체가 이 단계를 거치는것은 아니다.

    아래 코드에서 try문을 지나면 foo 참조변수에 대해서 더이상 접근 할 수 없다.

    GC의 대상이 아니기 때문에 메모리 누수의 원인이 될 수 있다.

    try {
    	Object foo = new Object();
    	foo.doSomething();
    } catch (Exception e) {
    
    }

    GC대상이 되기 위해 try 블럭 위에서 객체를 선언하는것이 좋다.

    Object foo = null;
    try {
    	foo = new Object();
    	foo.doSomething();
    } catch (Exception e) {
    
    }
  1. Unreachable ( 사용되지 않음 )

    더 이상 객체에 대한 참조가 되지 않는 경우

    GC 후보 대상이 된다. JVM은 해당 객체가 점유하는 메모리가 꼭 필요하지 않다면 GC작업을 늦출 수 있다.

    Dog dog = new Dog();
    dog = null;//Unreachable
  2. Collected ( GC 대상이 되는 상태)

    메모리 해제 직전 단계. 객체가 finalize 메소드(Resource를 정리)를 가지고 있다면, 메모리 해제 전에 수행한다.

  3. Finalized ( Finalize 를 거친 상태 )

    객체의 finalized()가 호출되어 unreachable 상태

  4. Deallocated (메모리 해제 된 상태)

    메모리 반환이 끝난 상태로 GC동작 마무리된 상태

GC

Heap영역의 물리적 공간 Young / Old

https://journaldev.nyc3.digitaloceanspaces.com/2014/05/Java-Memory-Model.png

  • Young generation = Eden + S0(Survival0), S1(Survival1)
    • 대부분의 객체가 금방 접근 불가능 상태이므로 많은 객체가 여기서 생성되었다가 사라진다.
    • Young generation에서 객체가 사라질 때 Minor GC(Minor Garbage Collection)가 발생한다고 표현한다.
  • Old generation
    • Young영역에서 살아남은 객체가 복사되는 곳
    • Young영역보다는 GC가 적게 발생한다.
    • Old generation에서 객체가 사라질 때 Major GC(Major Garbage Collection)가 발생한다고 표현한다.
  • Perm 영역 (Method Area)
    • intern된 문자열 정보를 저장하는곳
    • 일반적으로 GC대상은 아니지만 문자열 참조대상이 없는 경우 GC가 발생. Major GC로 포함된다.
    • 단, 동적으로 생성된 문자열은 여기에 포함되지 않는다.

Heap 메모리 동작 과정

  1. 새로운 객체가 Eden영역에 생성
  2. Eden영역의 메모리가 꽉 차면 GC 한번 발생 후 살아있는 객체만 Survivor영역으로 복사
  3. Survivor영역이 꽉차면 S1영역으로 객체가 복사된다.

STW - Memory fragmentation

  • stop-the-world란? gc를 실행하기위해 jvm의 어플리케이션 실행을 멈추는것 stop-the-world 발생 시 스레드를 제외한 나머지 스레드는 모두 작업을 멈춘다.

그리하여 stop-the-world가 길어 질수록 성능이 떨어지게 된다. 이러한 단점에도 stop-the-world상태로 GC처리가 되는것인가?

메모리 파편화

  • 메모리 공간을 할당하고 해제하는 과정에서 발생하는 현상
  • 외부 파편화
    • 메모리 공간이 여러개로 나뉘어서 사용되어, 충분한 메모리 공간이 있어도 큰 메모리 블록을 할당할 수 없는 경우
  • 내부 파편화
    • 메모리 할당 후 남는 공간이 발생하는 경우
    • 메모리를 효율적으로 사용하지 못하게 됨

→ GC 동작 시 이를 해결하기 위해 Compaction이 수행되어야한다. 이를 위해 stop-the-world가 발생해야한다. 또한 메모리 해제 및 Compaction이 진행 중 어플리케이션 작업 수행으로 인해 메모리가 변경되어 GC작업이 방해가 될 수 있기때문이다. 이러한 이유로 GC작업 시 stop-the-world가 필요하다.

GC 대상이 되는 조건 (GC Root)

GC수행 시 Mark and Sweep이라는 동작이 있다.

  • Mark and Sweep이란?
    • Garbage Collection 작업 시 참조하는 객체 마킹
    • Marking작업이 끝난 후 Marking되지 않은 객체 해제

Mark and Sweep 동작은 GC Root로 부터 시작된다.

GC Root란, Garbage Collection의 root, root는 다른 object를 참조하여 객체들을 탐험할 수 있게 연결되어있다.

https://sihyung92.oopy.io/java/garbage-collect/1

https://sihyung92.oopy.io/java/garbage-collect/1

Root로 부터 시작되서 Mark and Sweep을 진행한다.

GC Root가 될 수 있는 대상은 아래와 같다.

  • 실행중인 쓰레드
  • static 변수
  • local 변수
  • JNI 레퍼런스

Mark과정이 끝나면 Garbage Collector는 Heap영역을 돌면서 Mark되지 않은 메모리를 해제한다.이를 Sweep이라고 한다.


[참고]

https://hodongman.github.io/2019/11/30/Java-Finalize().html

https://himanshugpt.wordpress.com/2010/03/17/life-cycle-of-java-object/

https://gominhaja.tistory.com/25

https://golf-dev.tistory.com/68

https://velog.io/@cham/JAVA-GCGarbage-Collector

https://sihyung92.oopy.io/java/garbage-collect/1

0개의 댓글