ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [whiteship] 자바 온라인 스터디 - 1주차. JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가?
    IT 발자취.../JAVA 2020. 11. 21. 14:03

    목표

    자바 소스 코드 파일(.java)을 JVM으로 실행하는 과정 이해하기

     

    학습할 것

    • JVM이란 무엇인가
    • 컴파일 하는 방법
    • 실행하는 방법
    • 바이트코드란 무엇인가
    • JIT 컴파일러란 무엇이며 어떻게 동작하는지
    • JVM 구성 요소
    • JDK와 JRE의 차이

     

    자바를 시작하는데 반드시 알아야 하는 내용으로 1주차 과제를 시작한다.

     

    자바란?
    더보기

    자바는 썬 마이크로시스템즈에서 개발하여 1996년 1월에 공식적으로 발표한 객체지향 프로그래밍 언어이다.

    자바의 가장 중요한 특징은 운영체제에 독립적이라는 것이다.

    이러한 장점으로 인해 자바는 다양한 기종의 컴퓨터와 운영체제가 공존하는 인터넷 환경에 적합한 언어로써 인터넷의 발전과 함께 많은 사용자를 확보하였다.

    ----

    자바 언어의 특징
    - 가볍게 특징을 이해하고, 자세한 내용은 스터디를 진행하면서 알아보도록 한다. ( 시작부터 어려운거 하면 머리 아프므로 )
    더보기

    1. 운영체제에 독립적이다.

    2.  객체지향언어이다.

    3. 자동 메모리 관리 (GC)

    4. 네트워크와 분산처리를 지원

    5. 멀티쓰레드를 지원

    6. 동적 로딩을 지원한다.

     

     

    자바의 동작을 이해하려면 먼저 기존의 언어와의 차이점에 대해서 이해를 해야한다.

    JVM

    기존의 다른 언어들 ( C, C++ 등..)은 운영체제에서 동작하기 때문에 운영체제의 의존성을 띄었다.

    JAVA는 JVM이라는 가상 머신을 통해 운영체제에서의 종속성을 벗어나게 되었다. 

     

     

    바로 이 JVM에 대해서 알아보기로 한다.

     

    JVM이란 무엇인가 ???

    JVM은 'Java virtual machine'을 줄인 것으로 자바를 실행하는 가상 머신이다. 

    컴퓨터를 처음 접하는 사람들에게는 virtual machine이라는 말이 이질적일 수 있다.

     

    virtual machine ??
    더보기

    virtual machine은 소프트웨어로 구현된 하드웨어를 뜻하는 넓은 의미를 가진 용어이다.

    쉽게 설명하면 컴퓨터 안에 가상의 컴퓨터가 여러개 있다는 의미이다. virtual machine은 IT 관련 업계에서는 아주 많이 사용되는 용어이므로 따로 공부하면 좋을 것이다.

    자바로 작성된 애플리케이션은 모두 JVM에서만 실행되기 때문에, 자바 애플리케이션이 실행되기 위해서는 반드시 JVM이 필요하다.

     

    일반 애플리케이션의 코드는 OS만 거치고 하드웨어로 전달되는데 비해 Java 애플리케이션은 JVM을 한 번 더 거치기 때문에, 그리고 하드웨어에 맞게 완전히 컴파일된 상태가 아니고 실행 시에 해석 (interpert) 되기 때문에 속도가 느리다는 단점을 가지고 있다.

    그러나 요즘엔 바이트코드 (컴파일된 자바코드)를 하드웨어의 기계어로 바로 변환해주는 JIT 컴파일러와 향상된 최적화 기술이 적용되어서 속도의 격차를 많이 줄였다. ( 거의 없다고 봐도 됨 )

     

    바이트코드확인
    더보기

    바이트코드는 여러 방법으로 확인 가능하지만, 요즘 대부분이 IDE를 사용하기 때문에 Intellij를 통해 바이트코드를 확인하는 방법을 알아본다.

    Intellij에서 바이트코드보기

     

    여기서 확인한 하나의 재밌는 사실은 현재 홈페이지도 JVM으로 돌아가는 듯하다.

    바이트코드를 코드 블럭에 씌우지 않고 그냥 붙여 넣으니, 해석이 된 채 붙여 넣기가 된다.

    아래와 같이 ㅎㅎ.. 처음에는 잘못 붙여넣기를 한줄알았다.

    Hello.java

    // class version 55.0 (55)
    // access flags 0x20
    class Hello {
    
      // compiled from: Hello.java
    
      // access flags 0x0
      <init>()V
       L0
        LINENUMBER 1 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V
        RETURN
       L1
        LOCALVARIABLE this LHello; L0 L1 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      // access flags 0x9
      public static main([Ljava/lang/String;)V
       L0
        LINENUMBER 3 L0
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        LDC "Hello Java"
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
       L1
        LINENUMBER 4 L1
        RETURN
       L2
        LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
        MAXSTACK = 2
        MAXLOCALS = 1
    }
    

     

    즉, JVM은 자바 바이트코드를 OS에 맞게 해석해주는 역할을 합니다.

    Java compiler (javac)는 .java파일 (우리가 앞으로 작성할 파일)을 .class라는 자바 바이트코드로 변환 시켜 줍니다.

    바이트 코드는 기계어가 아니기 때문에 OS에서 바로 실행이 되지 않습니다.

    이때 JVM은 OS가 바이트코드를 이해할수 있도록 해석해 줍니다.

     

    컴파일 하는 방법

    기본 컴파일 방법

    컴파일 하기 위해 테스트 코드를 하나 작성한다

    다음과 같이 코드를 작성하고, java study 폴더를 생성하고, Hello.java라고 저장한다.

    각종, 에디터를 사용해서 작성 가능하다.

    저는 Windows10 환경에서 vi 에디터를 이용하여, 작성하였습니다.

    class Hello{
      public static void main(String[] args) {
         System.out.println("Hello Java");
      }
    }
    

    java study 폴더에서 cmd 창을 켜고 기본적인 컴파일을 해보도록 한다.

    > javac Hello.java
    > dir
    Hello.class  Hello.java

    Hello.class라는 파일이 생성된 것을 확인할 수 있습니다.

    해당 파일을 열어보면 알아볼 수 없이된 언어들을 볼 수 있습니다.

    이는 컴파일된 바이트코드로서 JVM이 해석할 수 있는 언어로 변경된 것입니다.

     

    Êþº¾^@^@^@7^@^]
    ^@^F^@^O        ^@^P^@^Q^H^@^R
    ^@^S^@^T^G^@^U^G^@^V^A^@^F<init>^A^@^C()V^A^@^DCode^A^@^OLineNumberTable^A^@^Dmain^A^@^V([Ljava/lang/String;)V^A^@
    SourceFile^A^@
    Hello.java^L^@^G^@^H^G^@^W^L^@^X^@^Y^A^@
    Hello Java^G^@^Z^L^@^[^@^\^A^@^EHello^A^@^Pjava/lang/Object^A^@^Pjava/lang/System^A^@^Cout^A^@^ULjava/io/PrintStream;^A^@^Sjava/io/PrintStream^A^@^Gprintln^A^@^U(Ljava/lang/String;)V^@ ^@^E^@^F^@^@^@^@^@^B^@^@^@^G^@^H^@^A^@ ^@^@^@^]^@^A^@^A^@^@^@^E*·^@^A±^@^@^@^A^@
    ^@^@^@^F^@^A^@^@^@^A^@  ^@^K^@^L^@^A^@  ^@^@^@%^@^B^@^A^@^@^@   ²^@^B^R^C¶^@^D±^@^@^@^A^@
    ^@^@^@
    ^@^B^@^@^@^C^@^H^@^D^@^A^@^M^@^@^@^B^@^N
    

    javap -c 명령어로 해석된 바이트코드를 볼 수 있다.

    > javap -c Hello.class
    Compiled from "Hello.java"
    class Hello {
      Hello();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #3                  // String Hello Java
           5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }

     

    한번에 여러 파일 컴파일 하기

    컴파일하는 방법에는 다양한 방법이 있다. 그 중 몇개만 알아보도록 한다.

    패키지라는 것은 현재로선 그냥 폴더라고 생각하면된다.

    javac -d . <패키지명>/*.java

    패키지 ( 폴더 ) 단위로 컴파일이 가능하다.

     

    기타..

    javac의 다양한 옵션

    더보기
     javac -help
    Usage: javac <options> <source files>
    where possible options include:
      @<filename>                  Read options and filenames from file
      -Akey[=value]                Options to pass to annotation processors
      --add-modules <module>(,<module>)*
            Root modules to resolve in addition to the initial modules, or all modules
            on the module path if <module> is ALL-MODULE-PATH.
      --boot-class-path <path>, -bootclasspath <path>
            Override location of bootstrap class files
      --class-path <path>, -classpath <path>, -cp <path>
            Specify where to find user class files and annotation processors
      -d <directory>               Specify where to place generated class files
      -deprecation
            Output source locations where deprecated APIs are used
      --enable-preview
            Enable preview language features. To be used in conjunction with either -source or --release.
      -encoding <encoding>         Specify character encoding used by source files
      -endorseddirs <dirs>         Override location of endorsed standards path
      -extdirs <dirs>              Override location of installed extensions
      -g                           Generate all debugging info
      -g:{lines,vars,source}       Generate only some debugging info
      -g:none                      Generate no debugging info
      -h <directory>
            Specify where to place generated native header files
      --help, -help, -?            Print this help message
      --help-extra, -X             Print help on extra options
      -implicit:{none,class}
            Specify whether or not to generate class files for implicitly referenced files
      -J<flag>                     Pass <flag> directly to the runtime system
      --limit-modules <module>(,<module>)*
            Limit the universe of observable modules
      --module <module-name>, -m <module-name>
            Compile only the specified module, check timestamps
      --module-path <path>, -p <path>
            Specify where to find application modules
      --module-source-path <module-source-path>
            Specify where to find input source files for multiple modules
      --module-version <version>
            Specify version of modules that are being compiled
      -nowarn                      Generate no warnings
      -parameters
            Generate metadata for reflection on method parameters
      -proc:{none,only}
            Control whether annotation processing and/or compilation is done.
      -processor <class1>[,<class2>,<class3>...]
            Names of the annotation processors to run; bypasses default discovery process
      --processor-module-path <path>
            Specify a module path where to find annotation processors
      --processor-path <path>, -processorpath <path>
            Specify where to find annotation processors
      -profile <profile>
            Check that API used is available in the specified profile
      --release <release>
            Compile for a specific VM version. Supported targets: 6, 7, 8, 9, 10, 11
      -s <directory>               Specify where to place generated source files
      -source <release>
            Provide source compatibility with specified release
      --source-path <path>, -sourcepath <path>
            Specify where to find input source files
      --system <jdk>|none          Override location of system modules
      -target <release>            Generate class files for specific VM version
      --upgrade-module-path <path>
            Override location of upgradeable modules
      -verbose                     Output messages about what the compiler is doing
      --version, -version          Version information
      -Werror                      Terminate compilation if warnings occur

    하지만, 이론적으로 이렇게 컴파일하는 방법을 익히지만, 현대에 실질적으로 이런식으로 컴파일하는건 ... 무리이다.

     

    IDE를 사용하면 자동적으로 컴파일을 해준다. 컴파일을 해주고 있다는 걸 증명해주는 것은 IDE를 사용할 때 잘못된 문법을 사용하면 빨간줄로 경고를 날려주는 것을 보면 컴파일을 해주고 있는 것을 알고있다.

     

    컴파일은 IDE ( 이클립스, Intellij 등등... )에게 맡기자

     

    실행하는 방법

    기본 실행

    위에서 컴파일한 .class를 실행해보자.

    java 를 이용하여 실행 가능하다.

    > java Hello
    Hello Java

    java를 실행할 때도 다양한 옵션을 주어 실행 시킬 수 있다.

    실제 자바 어플리케이션을 운영할 때 많이 사용하는 옵션들에 대해서 알아본다.

    추후 알게된 내용이므로, 이러한 방법이 있다는 것 정도만 이해하면 될 것 같다.

    JVM 힙 메모리 할당 풀 지정

    -Xms, Xms를 이용하여, JVM의 힙 메모리 할당 풀을 지정할 수 있다.

    • -Xms : 초기 메모리 할당 풀
    • -Xmx : 최대 메모리 할당 풀

    JVM은 지정된 Xms 메모리양으로 시작되고 최대 Xmx메모리를 사용할 수 있음 의미 합니다.

    예를 들면 다음과 같이 설정 가능하다. JVM을 시작할 때 1g의 메모리로 시작하며 최대 2g 까지 사용할 수 있다.

     

    해당 설정은 (JVM의 한 영역)에 할당된 크기로 JVM은 이보다 더 많은 메모리를 사용할 수 있습니다.

    JVM은 힙보다 더 많은 메모리를 사용합니다. 예를 들어 Java메소드, 스레드 스택 및 기본 핸들은 JVM 내부 데이터 구조 뿐만 아니라 힙과 별도의 메모리에 할당된다.

    메모리 플래그는 킬로바이트, 메가 바이트, 기가바이트 등으로 지정가능하다.

    (-Xmx 1024k -Xms 512m -Xmx4g)

    java -Xms1g -Xmx2g

    GC 설정

    GC는 추후 공부할 것으로 자바의 장점으로 꼽히는 메모리를 관리해주는 동작을 하는 기능이다.

    GC는 여러가지가 있고, 시스템 운영상 중요하기 때문에 심도있는 학습과 시스템에 적합한 튜닝이 필요로 한다.

    설정 방법만 간단하게 알고 간다

    java8 부터 기본적인 GC는 G1 이다. 기본적으로 설정되어 있지만, 아래 옵션을 통해 수동으로 설정도 가능하다.

    java -XX:+UseG1GC

    기타 설정

    더보기

     - GC 로그 옵션 

    > verbosegc -Xloggc:app_gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps


      - Young Gen 영역을 위한 옵션 

    > -XX:NewSize=100m -XX:MaxNewSize=100m -XX:SurvivorRatio=32


      - GC 소요시간 최소화 옵션

    > -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:-CMSParallelRemarkEnabled


      - 성능을 위한 기타 추가 옵션

    > -XX:MaxTenuringThreshold=0 -XX:CMSInitiatingOccupancyFraction=60


      - OutOfMemoryError 발생시 자동으로 힙덤프 생성 옵션

    > -XX:+HeapDumpOnOutOfmemoryError -XX:HeapDumpPath=/path

    기타 어려 옵션을 주어 실행 시킬 수 있다.

    바이트코드란 무엇인가

    JVM 을 설명하며 바이트코드에 대해서 어느 정도 설명을 했으므로, 간단히 정리만 다시 해보겠다.

    바이트코드는 가상 머신이 이해할 수 있는 언어이다. 

    고급언어 (java, 코틀린 등등 )로 작성된 소스코드를 가상 머신이 이해할 수 있는 중간 코드로 컴파일한 것을 말합니다. 

    바이트코드는 다시 JIT(just in time)컴파일러에 의해 바이너리 코드로 변환됩니다.

     

    여기서 바이너리코드란 컴퓨터가 인식할 수 있는 0과 1로 구성된 이진코드입니다.

    즉, CPU가 이해할 수 있는 언어입니다

    JIT 컴파일러란 무엇이며 어떻게 동작하는지

    JIT은 Just In Time의 약어입니다.

    JIT 컴파일러는 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법입니다.

    https://singztechmusings.wordpress.com/2011/07/03/java-what-is-jit-compiler-and-how-does-it-work/

    Hello.java 소스코드를 생성한다고 가정해본다.

    1. Hello.java 소스코드를 작성한다.

    2. javac Hello.java로 컴파일하면 바이트코드 (Hello.class)로 변환된다.

    3. JVM에서 각 운영체제에 맞는 기계어로 번역해 전달한다.

    JIT 컴파일러

    심화내용

    더보기

    Interpreter는 중복된 코드들을 마치 처음 본 것처럼 계산하기 때문에 JIT은 중복을 효율적으로 처리하는 컨셉을 가지고 있습니다. JIT을 설명하기 전에 결론부터 말하면, JVM은 바이트코드를 해석할 때 JIT만 사용하는 것은 아니고 "Interpreter방식"과 "JIT"방식"을 혼용해서 사용한다

     

    https://medium.com/@ahn428/java-jit-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-c7d068e29f45

    JVM은 중복코드를 효과적으로 처리하기 위해 바이트코드를 해석할 때 First use, Succesive use 2가지로 구분한다.

    - First use는 처음 본 코드에 대한 과정이며, JIT 컴파일러가 바이트코드를 해석하여 Native Code로 바꾸고 Cache에 저장한다. Cache의 위치는 JVM의 Method Area에 코드 캐시이다.

    - Seuccessive use에서는 해석해야 할 명령어가 이미 Cache에 저장되어 있는 경우에 해당.

    별도의 해석 (compile) 과정을 거치지 않고 이미 compile 된 Native Code를 바로 얻을 수 있다.

    JVM 구성 요소

    JVM 아키텍처

    https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

    JVM 핵심 구성 요소

    https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

    java 7 JVM 구조

    https://blog.voidmainvoid.net/184

     

    java 8 JVM 구조

    https://blog.voidmainvoid.net/184

    JDK와 JRE의 차이

    JRE

    JRE는 자바 실행 환경이다.

    JRE는 JVM이 자바 프로그램을 동작시킬 때 필요한 라이브러리 파일들과 기타 파일들을 가지고 있다. JRE는 JVM의 실행환경을 구현했다고 할 수 있다.

    JDK

    JDK는 자바 개발 도구의 약자이다

    JDK는 JRE + 개발을 위해 필요한 도구들을 포함한다.

     

    https://www.dev-guide.org/the-java-language-specification-api-ide-jdk-jre-and-jvm/

    하지만 근대 자바에 오면서 JDK와 JRE는 합쳐져서 java 11을 다운받을을 때는 JRE 다운로드는 따로 없는 것을 확인 할 수 있다. JRE는 JDK에 포함된다.

    www.oracle.com/kr/java/technologies/javase-downloads.html

    Oracle에서 JDK 다운시

    참고

    - 자바의 정석 / 남궁 성

    - www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

    - singztechmusings.wordpress.com/2011/07/03/java-what-is-jit-compiler-and-how-does-it-work/

    - medium.com/@ahn428/java-jit-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC-c7d068e29f45

    - johngrib.github.io/wiki/java-g1gc/

    - medium.com/@lazysoul/jvm-%EC%9D%B4%EB%9E%80-c142b01571f2

    댓글

Designed by Gintire