상냥한 세상 :: Introduction of Java-2

본문으로 바로가기

Introduction of Java-2

category Computer Science/Java 2020. 5. 12. 10:45

자바 프로그램의 실행 과정

 

Credit:TCP School

Java->(Java Compile)-> Byte code (. class)->(JVM의 Interpreters)->Machine Language

 - 컴파일러와 인터프리터 (Compilers and Interpreters) 
 → 컴파일러  - 한 언어로 작성된 프로그램을 다른 언어의 프로그램으로 바꾸어 주는 프로그램 
 → 처음 작성된 프로그램을 원시 프로그램(source program)이라고 부르고, 변환된 프로그램을 목적 프로그램(target program)이라 한다.
 → 고급언어로 작성된 원시 프로그램은 기계어의 목적 프로그램으로 컴파일된다. 
 → 컴파일러가 원시 프로그램 전체를 한꺼번에 기계어로 바꾼 다음 실행한다.   
 → 인터프리터는 프로그램을 일부분씩 기계어로 변환하여 실행한다.
 → 자바 컴파일러
는 자바 원시 프로그램을 바이트 코드로 변환한다.
 → 자바 바이트 코드는 특정 컴퓨터로부터 독립적인 자바 가상 기계의 기계어이다.
 → 자바 인터프리터
는 자바 바이트 코드를 해석(interpret)한다.

Java Compiler(javac.exe)

자바 컴파일러 역할:자바 소스 코드를 실행시키려면 먼저 자바 가상 머신(JVM)이 잘 알아들을 수 있는 Java Byte Code로 변환해줍니다.

★이렇게 특정 프로그램에서 실행할 수 있도록 변환한 코드를 'Target code'라고 부릅니다. 그 특정 기계(예를 들어 자바에선 JVM)가 없으면 실행할 수 없습니다. 

즉. java파일을 바이트 코드로 쓰인 java bytecode files(.class/jar) 파일로 변환해줍니다. 

Q: 분명 컴파일러는 기계어로 변환하는 프로그램이라고 하였는데,. class파일 안의 바이트 코드가 그럼 기계어인가요?
A: 컴파일러사 소스코드를 오브젝트 코드로(C언어 쪽) 고레벨 언어를 저 레벨 언어인(Machine Lavel Language)로 변환시킵니다. 그러나 여기서 말하는 기계는 하드웨어적인 기계가 아닙니다. JVM (Java Virtual Machine) 즉 JVM을 위한 기계어로 변환한다는 것입니다.

JDK(Java Development Kit)

자바 환경에서 돌아가는 프로그램을 개발하는데 필요한 툴들을 모아놓은 소프트웨어 패키지 입니다. 자바 프로그래밍을 하기 위해서는 JDK를 설치해야 합니다. JDK의 구성은 다음과 같습니다.

  • JRE=JVM+Java API
  • javac.exe=컴파일러.HelloWorld.java >HelloWOrld.class
  • java.exe=인터프리터. Byte code (HelloWorld.class)해석, 실행
  • javap.exe=역어셈블러. HelloWorld.class>HelloWorld.java
  • javadoc.exe=자동문서생성기. 소스파일의 주석을 이용해 Java API문서와 같은 형식의 문서를 자동으로 생성
  • jar.exe=압축프로그램. 클래스파일과 프로그램의 실행에 관련된 파일을 jar파일로 압축하거나 해제

참고링크

ko.wikipedia.org/wiki/자바_개발_키트

 

자바 개발 키트 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 자바 개발 키트(Java Development Kit, JDK)는 자바 SE, 자바 EE, 또는 자바 ME 플랫폼 중 하나를 구현한 것으로[1] 솔라리스, 리눅스, 맥 OS X, 또는 윈도 자바 개발자를 대��

ko.wikipedia.org

Process vs Thread

  • Process(프로세스)
    실행되고 있는 컴퓨터 프로그램을 말합니다. 프로그램을 실행하게 되면 운영체제로부터 필요한 메모리를 받아 프로세스가 됩니다.
  • Thread(쓰레드)
    프로세스가 가진 메모리를 가지고 실제로 작업하는것을 쓰레드라고 합니다. 
  • Multi-Threading(멀티 쓰레딩)
    여러 쓰레드로 여러 작업을 동시에 처리하는 것을 멀티 쓰레딩이라고 합니다.

    장점
    -CPU사용률을 향상시킵니다.
    -자원을 효율적으로 사용할 수 있습니다.
    -사용자에 대한 응답성이 향상됩니다.

    단점
    -프로세스의 자원을 공유하며 작업하기 때문에 동기화나 교착상태와 같은 문제가 발생 할 수 있습니다.


Credit:https://medium.com/@ahn428/java-thread-1-basic-6ccae58781a4

 

프로세스와 쓰레딩에 관한 예시
ex)워드를 입력하는동안 워드는 주기적으로 쓴 글들을 자동 저장하고, 내용을 입력하는동안 프린트를 할 수도 있습니다. 또한 입력도중 자동으로 맞춤법 검사도 진행됩니다. 이처럼 사용자의 입력을 받는동안 행해지는 이 모든 각각의 작업들을 쓰레드라고 부릅니다. 

즉 워드라는 큰 프로세스 안에 여러개의 쓰레드가 모여있는것 입니다. 

자바에서 쓰레드 구현 2가지 방법은 이후 설명토록 하겠습니다.

참고
medium.com/@ahn428/java-thread-1-basic-6ccae58781a4

 

Java - Thread(1) : Basic

먼저 프로세스와 쓰레드의 관계에 대해서 알아보겠습니다.

medium.com

 

JRE(Java Runtime Environment)

 


Runtime Data Area
-JVM이라는 프로그램이 OS위에서 실행되면서 할당받는 메모리 영역(즉 .class파일을 JVM으로 로딩시키는 역할)

-자바프로그램이 실행되기 위한 최소환경으로서 자바프로그램을 개발하지 않고 실행만 시킬거라면 JRE만 있으면 됩니다.
-Thread area: 쓰레드는 프로세스의 실행 단위( 프로세스는 실행중인 프로그램으로 햇갈리지 않도록 합시다)

-Heap Arwa:객체를 생성 및 보관하는 영역
-Method Area:클래스 정보가 차지하는 영역

 

Java Bytecode

자바 바이트 코드
-자바 가상 머신(JVM)이 이해할 수 있는 언어로 변환된 자바 소스 코드입니다.

자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불리고 있습니다.
자바 바이트 코드의 확장자는. class입니다. 
자바 바이트 코드는 JVM만 설치되어 있으면, 어떤 운영체제에서든지 실행 가능합니다. 

 

Java Virtual Machine (JVM)

JVM: Java Virtual Machine 즉 자바 가상 머신의 약자를 딴 용어입니다. 

 

  • JVM의 역할:자바 애플리케이션을 클래스 로더를 통해 자바 API와 함께 실행하는 것. 그리고 JAVA와 OS 사이에서 중개자 역할을 수행하여 운영체제에 독립적인 플랫폼을 갖게 해 줍니다.

    보다 쉽게 풀어서 설명드리면 자바를 실행하기 위한 가상 기기라고 보시면 됩니다.

    ※API:java언어를 사용하면서 누구나 코딩을 쉽게 할 수 있도록 만들어 놓은것입니다.
    ex)System.out.println은 배운적없는데 이 코드만 쓰면 출력문으로 나옵니다. 이는 이미 앞선 자바개발자들이 만들어 놓은것입니다. 이처럼 자바에 기본적으로 내장되어있는 API가 있고(JDK설치시 기본적으로 내장되있음), 뛰어난 개발자들이 추가적으로 만든 API가 있습니다.  
    게임 매크로를 만들었는데, 게임 클라이언트가 의도치 않게 종료되도 매크로는 계속 실행되고 있습니다. 이럴때 우리는 의도치 않게 종료되었을때 매크로도 동시에 자동멈춤 기능을 추가하고 싶습니다. 이후 우리는 시간단위매크로에 이미지서치 기능을 추가하고자 하여 자바에 이미지 서치 API를 다운 후 조건문을 추가로 넣어 해당 기능을 넣을 것입니다. 이처럼 누군가가 이미 만든 API를 다운받고 사용하면 프로그램의 완성도가 높아지고, 이미 만들어진 API를 사용하는 것이기 때문에 해당 기능을 직접 만들지 않아도 되서 시간적으로도 단축이 됩니다.  ** https://docs.oracle.com/javase/**


  • JVM의 기능
    -Java 프로그램이 어느 기기, 또는 어느 운영체제 상에서도 실행될 수 있게 하는 것
      (이식성 증가, 단 각 OS에 맞는 JVM을 사용해야 함)
      (Windows JVM≠Mac JVM)
    -프로그램 메모리를 관리하고 최적화하는 것

 

Credit:JavaWorld/ IDG

 

 

일반적으로 프로그래밍 언어는 소스코드가 컴파일러에 의해 기계어로 변환되면 컴퓨터에서 바로 실행할 수 있으나, Java는 JVM위에서 동작하게 됩니다.

일반 프로그램은 왼쪽과 같이 프로그램 실행-> OS->하드웨어 순으로 움직이게 됩니다. 하지만 우측에 자바는, JVM이 OS위에 있고 그 위에서 JAVA 프로그램이 작동하는 방식입니다. 

Q:왜 JAVA가 왼쪽처럼 컴퓨터에서 직접적으로 운영되지 않고 JVM을 꼭 거쳐야 하나요?
A:코드의 이식성(다른 컴퓨터로 옮겨 실행하는 것)을 보다 좋게 하기 위함입니다. 기계어(Machine Language)처럼, 컴퓨터에서 직접적으로 운영되면 다른 컴퓨터에선 실행되지 않습니다. 그러나, JVM을 통한 JAVA는 JVM이라는 가상 머신 환경만 제공되면 어디서든 실행되기 때문에 이식성이 좋기 때문에 거쳐야 합니다.

즉 위의 말들을 비추어보면

프로그래밍할 때 각 운영체제 전용 프로그래밍을 해줄 필요가 없다는 것입니다.

 

Credit:TCP school

JVM의 구성

자바 가상 머신은 다음과 같이 구성됩니다.

①자바 인터프리터(Interpreter)
②클래스 로더(Class loader)
③JIT 컴파일러(Just-In-Time compiler)
④가비지 컬렉터(Garbage Collector)

이번 포스팅에서 설명 못한 것들은 다른 포스팅에서 다루겠습니다.



자바 바이트 코드(반 기계어)로는 아직 컴퓨터가 읽을 수 없습니다. 

  • 자바 인터프리터(Interpreter)
    자바 인터프리터는 자바 컴파일러에 의해 변환된 클래스 파일 내의 바이트코드를 특정 환경의 기계에서 실행될 수 있도록 변환해줍니다. 다시 말해 Windows에서 작성된 프로그램이 Mac에서도 실행되게 변환시켜 준다는 말입니다. 또한, 필요한 부분만 컴파일을 하기 때문에 하나하나 컴파일을 하는 속도는 빠릅니다. 대신, 프로그램을 실행하면서 컴파일러를 통한 컴파일을 동시에 수행하기에 프로그램 전체 구동 시간은 오래 걸리는 편입니다.
    (아래 인터프리터는 자바 인터프리터가 아닌 단순 인터프리터만을 설명한 것입니다. 자바 인터프리터의 과정은 앞선 컴파일러를 병행하여 같이 구동하는 것입니다)


    ★컴파일러(Compiler)
       특정 프로그램 언어로 작성된 문장을 처리하여 기계어 또는 컴퓨터가 사용할 수 있는 언어로 변환시켜주는 역할을 합니다.
       컴파일러의 경우, 프로그램을 컴파일하기 위해서는 모든 프로그램 소스를 가지고 한꺼번에 번역을 한 뒤, 목적 코드(Target code)를 제작합니다.
       이러한 이유로, 한꺼번에 모든 부분을 번역해야 하기에 번역 속도가 상당히 느린 편이라 할 수 있습니다. 

    요약
    -컴파일러는 한꺼번에 컴파일을 하기 때문에 컴파일 시간은 오래 걸리지만 목적 프로그램을 실행할 때는 컴파일을 하지 않아 실행에 있어서 속도가 월등히 빠릅니다.
    -인터프리터는 라인별로 컴파일을 진행하기 때문에 컴파일 시간이 짧으나, 프로그램을 실행하는 동안에도 컴파일 작업을 같이 병행하기 때문에 프로그램 자체의 속도는 느립니다.

    Q:왜 굳이 인터프리터를 쓰나요?
    A:개발할 때 편리하기 때문입니다. 가령 예를 들어, 컴파일만 쓰면 프로그래밍을 했는데, 컴파일만 하루 꼬박했다고 칩시다. 근데 중간에 인수를 잘못 설정하여 로직 에러든 런타임 에러든 어떤 이유로든 에러가 났습니다. 그럼 그 에러를 수정 후 다시 컴파일만 하루 꼬박 돌려야 합니다. 하지만, 인터프리터의 경우, 컴파일을 라인별로 하기 때문에, 변수 이름 하나를 잘못 착각해서 오류가 나면 그냥 그거만 수정한 다음에 다시 인터프리터로 실행하면 됩니다. 이러한 이유로 인터프리터는 일반 컴파일러보다 디버깅에 있어서 용이합니다.
    또 다른 장점 하나는, 여러 운용체제에서도 사용 가능하다는 것입니다. Windows와 Mac은 OS 자체가 달라 OS를 바꾸게 되면 바꾼 OS에 맞춰서 또 컴파일을 해줘야 하는 경우가 있습니다. 하지만 인터프리터를 사용할 시에 고급언어를 바로 인터프리터에 입력해 실행시키는 방식이기 때문에 실행하려는 OS에 인터프리터가 설치되어 있다면 따로 컴파일해줄 필요 없이 즉각 사용이 가능합니다.

    다시 말해, 인터피르터는 OS마다 호환되는 인터프리터만 설치되어 있으면 해당 소스를 아무 곳에서나 사용하기에 유리합니다.

    요약
    -전체를 컴파일하지 않아 세부적인 디버깅을 하기 쉽습니다.(시간을 쓸데없이 낭비시키지 않습니다)
    -OS별로 이식하기가 쉽습니다. 

VM(자바 실행 프로그램)이 위와 같은 과정에서 얻어진 바이트코드(. class 파일)를 실행할 때 컴파일 과정에서 진행한 일들을 진행하지 않고 단지 실행만 합니다. 이러한 이유로 소스 코드를 보다 이해가 쉽고 속도가 더 빠르며, C언어같이 매번 소스코드 문법을 검사하는 등과 같은 불필요한 작업을 생략할 수 있어 효율적입니다. 게다가 소스코드를 외부로부터 지키는 보안성의 의도도 있습니다. 

 

하지만, 자바에서처럼 인터프리터만 사용하거나 컴파일러만 사용하거나 같은 방식이 아닌 위와 같이 바이트코드를 실행하는 "2중"단계를 거치는 방식이 좋다고만은 할 수 없습니다.

위 방식은 소스코드를 변경하게 될 때마다 자바 컴파일러를 통해 컴파일, 인터프리터를 통한 실행 과정을 거쳐야 하는 번거로움이 뒤따릅니다. 


-총정리-
인터프리터:실시간으로 한 줄, 한 줄 번역 진행
장점:빠르게 준비해서 실행 가능합니다./전체 코드를 컴파일하는 번거로움을 거칠 필요가 없습니다.
단점:동일한 코드를 한번 실행하게 되면 동일한 번역 작업을 계속 반복해야 합니다.
        ex) 반복문을 돌릴 때마다 계속 그 부분을 번역하게 됩니다.(비효율적)

https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/

컴파일러:미리 작동해서 번역 내용을 생성하여 적어 둡니다.
장점:전체 코드를 컴파일한 후에 반복문 같은 코드가 반복해서 실행되면 더 빠릅니다. 왜냐하면 이미 반복 코드는 한번 번역을 해놓았기 때문에 루프가 진행되면서 더 이상 번역할 필요가 없기 때문입니다.
단점:초기 구동 시간은 인터프리터보다 느립니다. 왜냐하면 처음부터 모든 컴파일 과정을 진행해야 하기 때문입니다.

https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/


  • 클래스 로더(Class loader)
    컴파일 시점(Compile Time)이 아닌 실행 시점(Run Time)에 클래스(Class)를 로딩할 수 있게 해 줍니다. 다시 말해 컴파일러에 의해서 컴파일된. class파일의 자바 바이트 코드를 읽어서 JVM의 Execution Engine이 사용할 수 있도록 Runtime Data Area에 적재하는 역할을 합니다. 쉽게 말해  Bootstrap(최상위 클래스)-Extension-Application의 구조를 갖습니다. 클래스 로더들이 클래스를 로딩하게 되면 Cache-Parent-self순으로 class를 확인하게 됩니다.  (Bootstrap등에 관한내용은 다음포스팅에서 다룰 예정입니다)


    클래스 로더는 로딩을 하게 될 때 가장 먼저 해당 class가 이미 로딩되어 있는지 확인하게 되고, 로딩되어 있다면 메모리에 Cache 되어 있을 것입니다. 이 경우 해당 클래스를 반환하게 됩니다. 만약 로딩이 되어있지 않았다면 부모(Parent)에 클래스를 로딩하게끔 위임(delegation)하게 되고 부모가 클래스를 로딩할 수 없으면 자신(self class loader)이 클래스를 탐색하게 됩니다.

    로딩:불러오다

    클래스를 로딩할 확률은 delegation model의 상위로 갈수록 높아집니다. 아래 표와 같이 bootstrap class loader은 JVM의 수행을 위한 핵심적인 클래스만을 로딩하게 되고, 가장 기본적인 클래스들의 버전이 정확히 일치하는지 확인합니다. 또한 각 클래스가 누구에 의해 로딩되었는지에 대한 정보도 제공하고 있습니다. (해당 정보는 자신 포함 상위 클래스 로더가 로딩한 것만 확인 가능하고 하위가 로딩한 것은 알 수 없습니다.
  • 참고:www.homoefficio.github.io/2018/10/13/Java-클래스 로더-훑어보기/

  • JIT 컴파일러 (Just-In-Time Compiler)
    C나 C++에서 하는 것처럼 프로그램을 실행하기 전에 처음 한 번 컴파일을 하는 대신, 프로그램을 실행하는 시점에서 필요한 부분을 즉석으로 컴파일하는 방식을 말합니다. 보통 인터프리터 방식의 언어 구현들이 성능 향상을 목적으로 도입하는 경우가 많은데, JIT 컴파일러는 같은 코드를 매번 해석해야 하는 대신 처음 실행될 때 인터프리트를 하면서 자주 쓰이는 코드를 캐싱(저장)한 뒤, 캐싱된 코드를 이후 가져다 쓰기 때문에 인터프리터의 느린 실행 속도를 개선할 수 있습니다. 
    사실 JIT 컴파일러보다는 JIT인터프리터가 좀 더 정확한 표현입니다. 바이트코드 컴파일을 사용하는 Java도 바이트코드를 기계어로 인터프리팅할때 JIT 컴파일러를 사용합니다. 이에 반해 단점은 초기 구동 시에는 소스코드(혹은 바이트코드)를 실행 단계에서 컴파일하는 데에 시간과 메모리를 소모하기 때문에 정적 컴파일된 프로그램에 비해 실행 속도 면에서 손해를 본다는 것으로, 특히 실행시간이 매우 짧은 경우에는 애써 컴파일된 코드를 제대로 우려먹기도 전에 프로그램이 끝나는 배보다 배꼽이 더 큰 상황이 벌어지기도 합니다.

    정리하자면,
    -실제 실행하는 시점(실시간)에 기계어로 컴파일하는 기법
    -컴파일 방식에 비해 한 줄 한 줄 읽고 실행시키는 인터프리터 방식이 속도가 느려서 이를 보완하기 위해 사용함
    -반복문과 같이 프로그램 내에서 반복 실행되는 부분에 대한 처리로 효율을 높임
    -인터프리터를 실행하면서 성능개선을 하고자 사용되는 도구
    -실행 시점에서 기계어를 생성하고 기계어로 변환된 코드를 캐싱함으로써 같은 함수가 여러 번 불릴때 매번 기계어로 java.compiler (자바컴파일러)가 번거롭게 변환하는것을 방지

Credit:helloworld.naver.com


 

Credit:https://medium.com/

jython파일은 제외하고 Java 파일만 봅시다.
HelloWorld.java 소스코드를 생성했다고 가정하겠습니다.

작동순서
1.HelloWorld.java 소스 코드를 작성
2.javac.exe(Java컴파일러)가 Bytecode 파일(Helloworld.java->HelloWorld.class)로 변환
3.JVM에서 각OS에 적합한 기계어로 번역
4.JRE가 HelloWorld.class파일을 JVM으로 로딩
5.JVM은 HelloWorld.class파일을 해석해 실행할 수 있는 상태로 변환
앞선과정들에 의해 자바 프로그램은 각 OS에 독립적으로 동작할 수 있게 됩니다.

Credit:https://medium.com/

 


  • 가비지 컬렉터(Garbage Collector, GC)
    메모리 관리 기법 중 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요 없게 된 영역을 해제하는 기능입니다.


    1.String [] array=new String [2];
    2.
    3.array [0]='0';
    4.array [1]='1';
    5.
    6.array=new String [] {'G', 'C');

    3,4번째 줄에서 String배열이 할당되기 전 0,1은 어디로 갔냐라는 의문점이 들것입니다. 왜냐하면 6번째 줄에서 G와 C를 할당해주었기 때문에 0과 1은 더 이상 사용할 수 없는 메모리가 될 것이기 때문입니다. 이때 이러한 '정리되지 않은 쓰레기 메모리'를 프로그래밍 언어에선 Danling Object, 자바에선 Garbage라고 부릅니다. 이후로도 사용하지 않고 메모리를 가지고 있는 객체 역시 가비지입니다. 이런 힙 영역 내의 참조가 없는 쓰레기 메모리를 청소하는 프로그램을 가비지 컬렉터(Garbage Collector)라고 부릅니다.
    자바를 제외하고선 사용하지 않을 객체의 메모리를 개발자가 직접 해제해주어야 하나, 자바는 GC가 자동으로 잡아주니 편리성 또한 있습니다. (모든 쓰레기 메모리를 잡아주는 것도 아닙니다)
자바의 가비지 컬렉터의 2가지 작업수행 수행

-힙(heap)내의 객체 중 쓰레기(Garbage)를 찾아냅니다.
-찾아낸 쓰레기를 처리해서 힙의 메모리를 회수합니다.

그렇다면 GC는 어떤 게 가비지이고 어떤 게 가비지가 아닌 객체인지 구별을 할까요? 
이러한 가비지를 구별하기 위해선 GC는 Reachability라는 개념을 사용합니다. 

GC&Reachability
-어떤 객체에 유효 참조가 있으면 Reachable 없으면 Unreachable로 구별
-Unreachable 객체를 가비지로 간주 후 GC
-위 예시의 1번째 코드처럼 유효한 참조 여부를 파악하기 위해선, 항상 유효한 최초의 참조코드가 있어야 하는데, 이를 객체 참조의 Root set이라고 합니다.


힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나입니다.

 

  • 힙 내의 다른 객체에 의한 참조

  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
    스택:응용프로그램을 사용하여 문서 편집 혹인 그림 편집 중 방금 한 작업을 취소하기 위해 사용하는 되돌리기 기능과 같은 것. (컵 꽂이에서 맨 위 컵을 빼서 물을 먹으려다가 물을 안 먹고 다시 넣어 놓는 경우, 즉 마지막에 입력된 작업을 먼저 처리하는 방식-후입 선출)

  • 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조

  • 메서드 영역의 정적 변수에 의한 참조

이들 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 됩니다.

Root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상입니다. 오른쪽 아래 객체처럼 reachable 객체를 참조하더라도, 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체입니다.

객체가 GC대상이 되는 경우

 

  • 모든 객체 참조가 null일 경우 (object = null)
    ex) var person=null;//value는 null, type은 objcet
    *Object는 클래스에 선언된 모양 그대로 생성된 실체입니다.

  • 객체가 블록 안에서 생성되고 블록이 종료되었을 경우

  • 부모 객체가 null이 되었을 경우, 자식 또는 포함된 객체들은 자동적으로 GC 대상이 됩니다

 

Stop-the-world
GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것입니다. STW가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 모두 작업을 멈춥니다. GC작업을 완료한 후에야 중단했던 작업을 다시 시작하게 됩니다.

 

heap의 각 영역

 

가비지 컬렉터는 두 가지 가설 하에 만들어집니다.

 

  1. 대부분의 객체는 금방 접근 불가능 상태가 됩니다.

  2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재합니다.

이 가설의 장점을 최대한 살리기 위해서 HotSpot VM에서는 크게 2개로 물리적 공간을 나누었습니다. Young 영역과 Old 영역

 

Young 영역 : 새롭게 생성한 객체의 대부분이 여기에 위치합니다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라집니다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 말합니다.

--> 실제로 시스템이 정상적으로 동작하고 설정을 제대로 했다면 발생되는 GC의 대부분은 Minor GC입니다.

Young영역의 구성

Young영역은 크게 Eden/Survivor, Survivor 3가지 영역으로 나뉩니다.

https://dingue.tistory.com/7

처리절차
새로 생성한 대부분의 객체는 Eden영역에 위치-> GC가 한번 발생한 후 살아남은 객체는 Survivor영역 중 하나로 이동-> Eden영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor영역으로 객체가 계속 쌓임->이후 Survivor영역이 가득 차면, 그중 살아남은 객체를 다른 Survivor 영역으로 이동-> 이후 가득 찬 Survivor영역은 아무 데이터도 없는 상태로 됨->이 과정을 반복하면서 계속 살아남은 객체는 Old영역으로 이동

*이때 Survivor영역 중 하나는 반드시 비어있는 상태로 남아 있어야 합니다. 만약 두 Survivor영역에 모두 데이터가 존재하거나 두 영역 모두 사용량이 0이라면 정상적인 시스템 상황은 아닙니다.

Old 영역 : 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(Full GC)가 발생한다.

 

--> 크기가 크기 때문에 Minor GC보다 GC 시간이 오래 걸림

처리절차
Old영역은 기본적으로 데이터가 가득 차면 GC를 실행합니다. GC방식에 따라서 처리절차가 달라집니다. 

Old영역에서의 GC방식
Serial GC
Parallel GC
Parallel Old GC(Parallel Compacting GC)
Concurrent Mark&Sweep GC(CMS 방식)
G1(Garbage First) GC

Serial GC(-XX+UseSerialGC)
Young 영역에서의 GC는 앞 절에서 설명한 방식을 사용합니다. Old 영역의 GC는 mark-sweep-compact이라는 알고리즘을 사용합니다. 이 알고리즘의 첫 단계는 Old 영역에 살아 있는 객체를 식별하는 것(Mark). 그다음에는 힙(heap)의 앞부분부터 확인하여 살아 있는 것만 남깁니다(Sweep). 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눕니다.(Compaction).

 


Parallel GC(-XX:+UseParallelGC)
Parallel GC는 Serial GC와 기본적인 알고리즘은 같습니다. 그러나 Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 스레드가 여러 개입니다. 그렇기 때문에 Serial GC보다 빠른 게 객체를 처리할 수 있습니다. Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리합니다. Parallel GC는 Throughput GC라고도 부릅니다.

 

Parallel Old GC(-XX:+UseParallelOldGC)

이 방식은 앞서 설명한 Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다릅니다. 이 방식은 Mark-Summary-Compaction 단계를 거칩니다. summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 약간 더 복잡한 단계를 거칩니다.


CMS GC(-XX:+UseConcMarkSweepGC)
CMS 방식은 지금까지 설명한 GC 방식보다 더 복잡합니다.

초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝냅니다. 따라서, 멈추는 시간은 매우 짧습니다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인합니다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것입니다.

 

그다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인합니다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행합니다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행합니다.

 

이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧습니다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부릅니다.

장점:Stop-the-world 시간이 짧습니다.

단점: 다른 GC 방식보다 메모리와 CPU를 더 많이 사용합니다./Compaction 단계가 기본적으로 제공되지 않습니다.

 

따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 합니다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 합니다.


G1 GC
G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행합니다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행합니다. 즉, 지금까지 설명한 Young의 세 가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 됩니다. G1 GC는 장기적으로 말도 많고 탈도 많은 CMS GC를 대체하기 위해서 만들어졌습니다.

 

장점:어떠한 GC방식보다도 빠릅니다.
*(하지만, JDK 6에서는 G1 GC를 early access라고 부르며 그냥 시험 삼아 사용할 수만 있도록 합니다. 그리고 JDK 7에서 정식으로 G1 GC를 포함하여 제공합니다. Java8에서는 기본 방식으로 적용되었습니다.)

단점:어떤 서비스에서 A라는 GC 옵션을 적용해서 잘 동작한다고 그 GC 옵션이 다른 서비스에서도 훌륭하게 적용되어 최적의 효과를 볼 수 있다고 생각하지 말아야 합니다.

동작 방식 : heap 영역이 기본적으로 연속된 구간에 동일한 크기의 여러 세트로 나뉘어 있습니다. 세트들은 이전 가비지 콜렉터와 같은 역할들을 할당받으나 그 크기는 정해지지 않습니다. 각 영역의 크기가 정해지지 않았기 때문에 메모리 영역의 사용에 대한 유연성을 제공합니다

G1 방식은 CMS 콜렉터와 비슷하게 동작합니다. G1은 heap 메모리에서 객체가 살아있는지 결정하는 동시적이고 전역적인 마킹을 수행합니다.

 

마킹 단계가 끝난 뒤에는 어떤 지역이 가장 많이 비어 있는지 알 수 있게 됩니다. 빈 공간이 많은 지역부터 메모리 회수를 시작하고, 그렇기 때문에 이름이 Garbage-First입니다.

 

G1에 의해 교체를 해야 하는 지역으로 확인된 곳은 배출에 의해 가비지 컬렉션을 당합니다. G1은 하나 이상의 지역으로부터 object를 카피하여 하나의 지역에 넣습니다. 그 과정에서 메모리를 비우고 최적화를 합니다.

 

이전과 동일하게 eden, survivor, old 영역은 있으나 permanent 영역은 사라졌습니다.

heap 영역을 작은 단위로 쪼개어 각 지역은 eden, survivor, old 영역으로 사용 가능하다. 이 것이 뜻하는 것은 각 영역의 크기가 가변적이라서 효율적인 활용을 뜻합니다.

병렬적으로 각 영역을 마킹하고 회수하기 때문에 stop-the-world 시간이 줄고 처리 속도가 늘어났습니다.

G1 GC 특징

GC의 대상 영역이 여러 개의 region으로 나뉘어 있기 때문에 GC 가 일어나면 전체 heap에 대해서 GC를 하지 않고, 일부 region에서만 GC를 수행합니다.

Young, Old 영역에 대해서 따로 region이 나뉘지 않고 같이 사용됩니다.

요약

Young

Young 영역의 GC는 parallel GC입니다.

 

Old

Old 영역의 GC는 Old 영역의 일부 region만 GC가 일어납니다. 이때 Young 영역의 GC도 같이 일어날 수 있습니다.

GC 하는 방식은 CMS와 유사합니다.

Old 영역의 GC는 필요에 따라서 모든 application의 thread를 멈추고 GC thread와 동시에 실행될 수 있습니다.

CMS와는 달리 Old 영역의 GC는 Heap 사용량이 특정 threshold 값을 넘어가면 실행됩니다.

유효한 객체들은 young, old GC동안 live 영역으로 이동하는데, 이때 virtual memory address로 이동하기 때문에 객체들 사이에 비어있는 공간이 생기는 memory 파편화가 발생하지 않습니다.

전체 heap에 대해서 GC가 일어나지 않고 일부 region에서만 하기 때문에 큰 heap을 가질 경우 유리합니다.

사용자가 GC pause time을 어떻게 목표로 하느냐에 따라 G1은 young 영역과 전체 heap size를 자동으로 조절합니다.

Java Heap이 많이 필요한 경우 G1을 사용하는 것이 유리합니다.

Parallel GC와 CMS GC의 절충안 정도의 GC 알고리즘을 사용하기 때문에 준수한 throughput, latency를 제공합니다.


참고:www.yangbongsoo.gitbook.io/study/naver_tech_javawww.dingue.tistory.com/7, www.velog.io/@litien/가비지-컬렉터 GC, www.mygumi.tistory.com/115

 

자바 가상 머신은 가비지 컬렉터(garbage collector)를 이용하여 더는 사용하지 않는 메모리를 자동으로 회수해 줍니다.

따라서 개발자가 따로 메모리를 관리하지 않아도 되므로, 더욱 손쉽게 프로그래밍을 할 수 있도록 도와줍니다.