[JAVA] 더 자바 "코드를 조작하는 다양한 방법" 강의 정리[1]

유형찬·2022년 9월 8일
1

JAVA

목록 보기
1/1

서론

들어가기에 앞서서 간단하게 제 이야기를 해 보고자 합니다.

대학교에서 Java를 처음 배워 지금까지 관성적으로 사용해왔습니다. JAVA API , Annotation , JDK 등의 코드를

자바 개발자가 어떤 생각을 가지며 기술들을 만들었는지 왜 이렇게 동작 하는지는 하나도 이해 못하고

사용하고 있었다고 말 할 수 있겠네요. 그렇지만 이번 백기선님 강의를 들으면서 이런 부족한 마인드

배경지식을 확실히 공고히 하고자 합니다. 왜 이렇게 작동하지? 라는 근본적인 질문을 이해하지 못했던,

관성적으로 코딩을 해오셨던 분들이라면 백기선님 강의를 들어보시는 걸 추천 드립니다. GoF, 언어의 근본 등

개발자를 꿈 꾸는 사람이라면 무조건 필요로 하는 내용이라고 제가 감히 말하겠습니다.

1부 JVM 이해하기

처음은 진짜 간단한 JVM 에 대해 말해보고자 합니다.

자바 Language , JVM , JDK에 차이를 아십니까?
혹자는 어차피 한 몸 아니냐 , 알 필요가 있냐 말 할 수 있을 것 입니다.
그러나 저는 이렇게 말 하겠습니다. 자기가 쓰는 도구가 어떤 식으로 만들어졌는지 모르는데 어떻게 활용을 하겠습니까?

자바

  • 우리가 사용하는 프로그래밍 언어이다.
  • Javac 로 바이트 코드로 컴파일 한다. .class 파일의 내용을 보면 010101010 형태는 아니다. JVM 에서 이해 할 수 있는 내용으로 번역 했을 뿐 아직 컴퓨터가 이해하고 수행 할 수 있는 단계가 아니다.

JVM이란?

백기선님 더 자바 로드맵을 수강하며 정리한 내용 입니다.

자바, JVM, JDK 그리고 JRE

JVM

  1. 자바 가상 머신으로 자바 바이트 코드(.class)를 OS에 특화된 코드로 변환
    1. 인터프리터와 JIT 컴파일러로 변환 → 실행
  2. 바이트 코드를 실행하는 표준이자 구현체
    1. JVM 자체는 표준 / 특정 밴더가 JVM 구현체
  3. 특정 플랫폼에 종속적
  • JVM은 플랫폼에 종속적이다.
    • 어? JVM 때문에 OS에 상관없이 돌릴 수 있는 것 아니였어?
    • OS 별 JVM은 달라요!

예) 바이트 코드

Compiled from "HelloJava.java"
public class HelloJava {
  public HelloJava();
    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
}

이러한 바이트 코드를 인터프리터와 JIT 컴파일러를 사용하여 네이티브 OS에 맞게 코드(기계어)로 변환후 실행

→ 특정 플랫폼에 종속적!

JRE : JVM + 라이브러리

  1. 자바 Application을 실행할 수 있도록 구성된 배포판
  2. JVM과 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가짐
  3. 개발 관련 도구는 포함하지 않음 → 이것은 JDK에서 제공
  4. 자바 컴파일러는 제공하지 않음 (javac)

JDK : JRE + 개발 툴

  1. 소스 코드를 작성할 때 사용하는 자바 언어는 플랫폼에 독립적.
  2. 오라클은 자바 11부터 JDK만 제공한다.

자바

  1. 프로그래밍 언어
  2. JDK에 들어있는 자바 컴파일러(javac)를 사용하여 바이트코드(.class)로 컴파일 할 수 있음

JVM 언어

  1. JVM 기반으로 동작하는 프로그래밍 언어
  2. 자바, Kotlin, Scala, …

예) Hello.kt 파일 kotlinc로 컴파일 후 javap 명령어로 바이트 코드 확인

  • vim , cat 으로 확인하면 인간이 볼 수 없는 언어로 만들어져 있는 것을 볼 수 있다.
> javap -c HelloKt.class

Compiled from "Hello.kt"
public final class HelloKt {
  public static final void main();
    Code:
       0: ldc           #8                  // String Hello, Kotlin
       2: getstatic     #14                 // Field java/lang/System.out:Ljava/io/PrintStream;
       5: swap
       6: invokevirtual #20                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       9: return
 
  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #23                 // Method main:()V
       3: return
}

JVM 구조

https://user-images.githubusercontent.com/68279162/172682942-ad2c8a9f-568c-4987-9a1a-c9ab2213ecd4.png

JVM 구조 는 크게 4 가지로 구성

  • 클래스 로더 시스템
    • .class 에서 바이트 코드를 읽고 메모리에 저장
    • 로딩: 클래스를 읽어오는 과정
    • 링크: 레퍼런스를 연결하는 과정
    • 초기화: static 값들 초기화 및 변수에 할당
  • 메모리
    • 메서드 영역에서는 클래스 수준의 정보를 저장
      • 클래스 이름, 부모 클래스 이름, 메소드, 변수
      • 공유하는 자원들이다.
    • 영역에는 객체를 저장, 공유 자원이다.
    • 스택 영역에는 쓰레드 마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다.
      • 쓰레드가 종료하면 런타임 스택도 사라진다.
    • PC 레지스터: 쓰레드 마다 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 생성됨
    • native 메소드 스택: 이것도 쓰레드 마다 생성. native 메소드를 호출할 때 만드는 별도의 스택
      • native 메소드란?
        • 메소드 앞에 native 라는 키워드가 붙고 구현은 C 나 C++ 로 한 것.
        • 자바 내부에서 조작 하거나 내용을 확인 할 수 없음
        • ex) Thread.currentThread();
    • 스택PC네이티브 메소드 스택은 특정 쓰레드에 국한된 것. 즉 , 쓰레드 끼리 공유하지 않음
    •  , 메소드 영역은 모든 쓰레드에서 사용하는 공유 자원
  • 실행 엔진
    • 인터프리터: 바이트 코드를 한 줄 씩 실행
    • JIT 컴파일러: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복되는 코드를 모두 native 코드로 바꿔둔다. 그 다음부터 인터프리터는 native 코드로 컴파일 된 코드를 바로 사용
    • GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리
  • 네이티브 메소드 인터페이스 + 네이티브 메소드 라이브러리클래스 로더
    • JNI (Java Native Interface)
      • 자바 Application에서 C, C++, 어셈블리로 작성된 함수를 사용할 수 있는 방법 제공
      • Native 키워드를 사용한 메소드 호출
    • 네이티브 메소드 라이브러리
      • C, C++로 작성된 라이브러리

간단한 순서

  • Class Loader 가 class 파일을 읽음
  • 데이터를 메소드 영역에 저장 및 유효성 체크 후 static 값 할당
  • reference 교체 ( option ) 후 메모리 적재
  • 메모리 적재 후 실행 엔진 실행

https://user-images.githubusercontent.com/68279162/172682956-1ca356a2-4dec-462e-979a-c7f8dbe10ff5.png

클래스 로더

  • 로딩 → 링크 → 초기화 순으로 진행
  • 로딩
    • 클래스 로더가 .class 파일을 읽은 다음 적절한 바이너리 데이터를 만들고 메소드 영역에 저장
    • 메소드 영역에 저장하는 데이터
      • FQCN : 패키지 경로까지 포함한 클래스 이름
      • 클래스 | 인터페이스 | 이넘
      • 메소드와 변수
    • 로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여  영역에 저장
public class App 
{
    public static void main( String[] args ) {
        ClassLoader classLoader = App.class.getClassLoader();
        System.out.println(classLoader); // (1)현재 App 클래스를 읽어온 클래스 로더 출력
        System.out.println(classLoader.getParent()); // (2)클래스로더의 부모 출력
        System.out.println(classLoader.getParent().getParent()); // (3)부모의 부모 출력
    }
}
 
/**
(1) jdk.internal.loader.ClassLoaders$AppClassLoader@3b192d32
(2) jdk.internal.loader.ClassLoaders$PlatformClassLoader@6bdf28bb
(3) null
*/

(3) null이 나오는 이유는 최상위 클래스 로더인 부트스트랩 클래스 로더는 네이티브 코드로 구현되어 있기 때문에 자바 코드에서 참조해서 출력할 수가 없기에 null이 출력되게 된다.

  • 링크
    • Verify → Prepare → Resolve 세 단계로 나눠짐
    • 검증: .class 파일 형식이 유효한지 체크
      • 컴파일 된 class file 을 조작 할 수 있기 때문에 문제가 있다면 JVM 이 꺼진다.
    • Preparation: 클래스 변수(static 변수)와 기본값에 필요한 메모리
      • 메모리를 준비하는 과정
    • Resolve: 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체
      • 선택적이다. 이 단계에서 실제 Heap 영역에 있는 레퍼런스를 참조 할 수도 있지만 실제로 사용할때 참조하도록 교체할 수 도 있기에 선택적이다.
    • 초기화
      • static 변수의 값을 할당한다. (static 블럭이 있다면 이때 실행된다.)
  • 클래스 로더는 계층 구조로 이뤄져 있고 기본적으로 세 가지 클래스 로더가 제공
    1. 부트 스트랩 클래스 로더
      1. JAVA_HOME\lib에 있는 코어 자바 API를 제공
      2. 최상위 우선순위를 가짐
    2. 플랫폼 클래스 로더
      1. JAVA_HOME\lib\ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽음
    3. 애플리케이션 클래스 로더
      1. 애플리케이션 classpath(애플리케이션 실행할 때 주는 -classpath 옵션 또는 java.class.path 환경 변수의 값에 해당하는 위치)에서 클래스를 읽는다.

Reference:더 자바, 코드를 조작하는 다양한 방법 : 백기선

profile
rocoli에요

0개의 댓글