개요

JVM 에 대한 이해를 다시 깨우치기 위함. (해당 글은 2018년 2월에 첫 작성되었다.)

 

JVM은 자바 애플리케이션을 실행하기 위한 런타임 엔진의 역할을 수행한다. JVM은 자바 코드에서 우리가 흔히 쓰고 있는 public static void main(String[]args){} 의 메인메소드를 실제로 호출한다. 

 

우리는 사실 자바를 배우면서 자바의 기본적인 사상 혹은 동기를 초기에 알게되었는데, 그것은 WORA(Write Once Run Anywhere) 이다. 개발자가 단일의 시스템에서 Java 코드를 이용해 애플리케이션을 개발하더라고 해당 애플리케이션은 단일의 플랫폼에서만 작동되는 것이 아닌 JVM 환경이 갖추어진 환경 어디서든 작성된다.

 

Class Loader 기능 세가지

  • loading
  • linking
  • initialization
Loading

클래스 로더는 .class 파일을 읽고 이에 대응하는 바이너리 데이터를 생성한다. 그리고 생성된 바이너리 데이터를 method area저장한다. 각각의 .class 파일에 대해서 JVM은 method area에 다음과 같은 정보들을 저장한다.

  • 로드된 클래스 및 직접적인 부모 클래스의 정규화된 이름 
  • .class 파일이 Class or Interface or Enum과 관련있는지 여부
  • 멤버변수와 메소드 정보 등 
    - 변수는 Intance Variable 과 Static Variable 을 의미한다.

.class 파일을 로딩한 후 JVM은 힙 메모리에 해당 클래스 유형의 오브젝트를 생성한다. 여기서 중요한 사실은 해당 오브젝트가 java.lang 패키지에 미리 정의된 클래스 유형이라는 점이다. 해당 객체에 참조를 얻으려면 Object 클래스의 getClass() 메소드를 사용할 수 있다. 왜냐하면 자바의 모든 클래스들은 최상위 클래스인 Object 클래스를 상속받기 때문이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
// Sample Class
class Student{
    private String name;
    private int roll_No;
    
    public String getName(){
        return name;
    }
    
    public void setName(String name){
        this.name = name;
    }
    
    public int getRoll_no(){
        return roll_No;
    }
    
    public void setRoll_no(int roll_no){
        this.roll_No = roll_no;
    }
}
 
public class Main {
    public static void main(String[]args){
        Student s = new Student();
        
        // 생성된 클래스 객체를 JVM 을 통해 반환
        Class c = s.getClass();
        
        System.out.println(c.getName());
        >> Student
        
        
        // 클래스 내의 선언된 메소드를 배열 형태로 모두 반환
        Method m[] = c.getDeclaredMethods();
        for(Method method : m)
            System.out.println(method.getName());
        >> getName
        >> setName
        >> getRoll_no
        >> setRoll_no
        
        
        // 클래스 내의 선언된 필드를 배열 형태로 모두 반환
        Field f[] = c.getDeclaredFields();
        for(Field field : f)
            System.out.println(field.getName());
        >> name
        >> roll_No
        
        
        // 로드한 모든 .class 파일에 대해서 클래스 하나의 Object 만 생성.
        Student s2 = new Student();
        Class c2 = s2.getClass();
        System.out.println(c == c2);
        >> true
    }
}
cs

 

Linking

  • Verification (확인)
    - .class 파일의 정확성을 보장한다. 예를 들어 컴파일러에 의해서 생성되었는지 여부와 형식이 맞는지 등과 같은 것이다. 만약 이러한 Verification 작업이 실패하면 java.lang. VerifyError를 띄운다.

  • Preparation (준비) 
    JVM은 클래스 변수를 메모리에 할당하고, 메모리를 기본값으로 초기화한다.

  • Resolution (결정)
    심볼릭 레퍼런스를 직접적 레퍼런스로 변경하는 프로세스이다. 이 부분은 method area 에서 검색해 참조된 엔티티를 찾는다.

Initialization
모든 스태틱 변수는 정의된 값으로 할당된다. 해당 단계는 클래스의 위에서 아래로 실행되며 부모에서 자식으로 계층적으로 실행된다. 일반적으로 3개의 클래스 로더가 존재한다.
  1. Bootstrap class loader
    모든 JVM의 구현에는 반드시 부트스트랩 로더가 존재하여야 한다. JAVA_HOME/jre/ lib 디렉토리에 있는 핵심 JAVA API 클래스를 로드한다.

  2. Extension class loader
    부트스트랩 로더의 자식 로더이다. 확장 디렉토리 JAVA_HOME/jre/lib/ext(확장경로) 또는 시스템 특성으로 인해 지정된 다른 디렉토리에 있는 클래스를 로드한다.

  3. System/Application class loader
    확장 로더의 자식이다. 애플리케이션 클래스 경로에서 클래스를 로드할 책임을 가지고 있다. 
위의 클래스 로더는 계층적인 구조를 띄고 있다. 1 ㅡ 2 ㅡ 3 의 순서를 가진다.
 
 
JVM Memory
  • Method area 
    메소드 영역에서는 스태틱 변수를 포함하여 클래스 이름, 부모 클래스 이름, 메소드, 변수 정보들이 저장된다. JVM 하나 당 하나의 메소드 영역이 존재하여 이는 공유되는 자원이다.

  • Heap area 
    모든 오브젝트에 대한 정보들은 힙 영역에 저장된다. 힙 영역 또한 공유되는 자원이다.

  • Stack area
    모든 스레드에 대해서 JVM은 하나의 런타임 스택을 여기에 생성한다. 스택 영역의 모든 블록들은 메소드 호출을 저장하는 activation record / stack frame 으로 부른다. 해당 메소드의 로컬 변수들은 모두 해당 프레임에 저장되며 스레드가 종료된 이후에는, 런타임 스택이 JVM에 의해서 삭제된다. 해당 자원은 공유자원이 아니다.

  • PC Register 
    스레드의 현재 실행 명령의 주소를 저장한다. 각 스레드는 별도의 PC 레지스터를 가지고 있다.

  • Native method stacks
    모든 스레드에 대해서 개별로 기본적인 스택이 만들어진다. 네이티브 메소드 정보들을 저장한다.

 

Execute Engine

Execution engine 은 .class 파일 즉 바이트 코드를 실행한다. 그것은 한 줄씩 바이트 코드를 읽고 다양한 메모리 영역에 있는 데이터와 정보를 사용한다. Execute Engine 은 세 부분으로 분류된다.

  • Interpreter 
    - 바이트 코드를 한 줄씩 해석한 다음 실행한다. 여기서 동일한 메소드가 여러번 반복해서 호출될 때마다 인터프리터는 이를 해석해야하는 단점이 있다.

  • Just-In-Time Complier (JIT)
    - 인터프리터의 효율을 높이기 위해 사용된다. 전체 바이트 코드를 컴파일하여 기본코드로 변경하기 때문에 동일한 메소드가 여러번 반복해서 호출되는 것을 JIT가 해당 부분에 직접 고유 코드를 제공하므로 재해석하지 않는 장점이 있다.

  • Garbage Collector (GC)
    - 참조되지 않은 오브젝트를 제거한다. 

Java Native Interface (JNI)

Native Method Libraries 와 상호작용하고 실행에 필요한 기본 라이브러리 (C, C++)을 제공하는 인터페이스이다. JVM은 C/C++ 라이브러리를 호출한다.

 

 

 

Native Method Libraries

해당 라이브러리는 Execute Engine 에 필요한 네이티브 라이브러리 (C, C++) 의 모음이다.

 

 

Posted by doubler
,