JVM은 Java Virtual Machine의 약자이다. 자바의 대부분 강의에서 초반 부분에서는 System.out.println("Hello world")라는 java 코드를 막 작성하고 이것을 자바 컴파일러로 컴파일하고 콘솔로 실행시키는 실습을 많이 하곤 한다. 이 때 JVM이라는 용어가 등장한다. JVM 자체도 깊게 다루면 끝도 없고 컴퓨터 구조 관련 얘기가 무수히 나오기 때문에 그 땐 이야기를 자세히 다뤄주진 않는다. 이전에 시간을 따로 내서 정리해야 겠다는 생각을 했었고 그게 바로 지금인거 같아서 글을 작성하려 한다. 

 

JVM

자바가 다른 언어와 구별되는 큰 장점으로 가장 많이 거론되는 것이 OS 플랫폼의 독립성이다. WORN(write once, run, anywhere)라는 말이 있는데 이말은 플랫폼 상관없이 한번 쓰기만 하면 어디에서나 동작가능하게 해준다는 말로 이를 가능하게 해주는 것이 바로 JVM이다. (반면 C언어는 OS에 종속적인 컴파일러가 해당 소스코드를 컴파일한다. 그래서 OS 종속적이다. ) 

 

JAVA의 실행

우리가 작성한 자바코드(.java확장자)는 자바 컴파일러가 컴파일하여 자바 바이트코드라는 것이 된다. 이것은 JVM이 실행할 수 있는 형태로 .class 확장자가 붙은 것이 바이트코드이다. JVM이라는 프로그램 자체는 물론 리눅스 전용 JVM, 윈도우 전용 JVM 따로 있지만 .class 파일을 받아서 해석하는 방식은 모두 통일된 방식으로 해석하기 때문에 OS 독립적이라 말한다. JVM이 동작하는 위치를 아래의 그림처럼 도식화 할 수 있다. 

앞에서 언급한 .class는 아직 기계어가 아니다. 아직 중간언어인 셈이다. 이것이 다른 OS에 가더라도 동일하게 동작되는 형태인 것이며 아직 한번의 변환이 더 남았다. 그 과정은 중간언어와 주변 OS 정보들을 정리하여 해당 OS에 적합한 형태로 변환하는 과정이다. 이러한 작업을 하는 것이 Execution Engine이고 이 때 사용되는 방식은 인터프리터 방식으로 이 때 최종 기계어로 바꾸며 한줄 한줄 읽어가면서 실행이 된다. 하지만 같은 메소드라도 반복적으로 호출이 되면 매번 해석하고 수행하는데 있어서 속도가 느리기 때문에 이때 JIT 컴파일러가 사용된다. JIT 컴파일러자주 사용될 수 있는 bytecode를 캐싱하고 최적화함으로써 코드가 interpreter에 의해 여러번 변환되는 작업의 수를 줄일 수 있다. 이와 같이 자바는 컴파일러 방식과 인터프리터 방식을 모두 사용한다. 

 

JVM 구성요소

이제 위 그림에서 구성 요소들을 하나씩 알아보려 한다.

  • Garbage Collector 
    • 메모리 관리 기능을 자동으로 수행한다. 애플리케이션이 생성한 객체의 생존 여부를 판단하여 더이상 사용하지 않는 객체를 메모리 해제한다. ( Garbage Collector에 대한 동작원리는 나중에 정리하자 )
  • Class Loader
    • JVM내로 클래스를 로드(올림)하고 링크(배치)하는 작업을 수행하는 모듈로 런타임시 동적으로 클래스를 로드한다. Runtime Data Area에 올라간다.
  • Execution Engine
    • Class Loader를 통해 JVM 내 런타임 데이터 영역에 배치된 바이트 코드를 실행한다. 자바 바이트 코드를 명령어 단위로 읽어서 실행한다. 
  • Runtime Data Areas
    • JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다.
    • 실서버에서 호스팅을 하던 중에  java.lang.OutOfMemoryError: Java heap space라는 에러가 나면 이쪽에서 나는 문제라 한다. ( 해결방법은 구글링~ )
    • 해당 영역은 5가지로 구분된다. Method, Heap, Stack, PC Register, Native Method

Method Area, Heap Area는 모든 스레드가 공유하는 영역,

Stack Area, PC Register, Native Method Stack Area는 각 스레드마다 생성되는 개별영역이다. 

 

공유의 형태를 다음과 같이 도식화할 수 있다. 

(Heap은 모든 스레드가 공유하기 때문에 동기화 문제가 생길 수 있다는 것 주의!)

힙 영역은 가비지 컬렉션에 대상이 되는 공간인데 효율적인 가비지 컬렉션을 수행하기 위해

다음과 같이 여러 영역으로 나뉜다.

 

힙힙!

  • Young Generation : 생명주기가 짧은 객체를 GC 대상으로 한다.
    • Eden : new를 통해 새로 생성된 객체가 위치되고 정기적인 쓰레기를 수집하고나서 살아남으면 Survivor로 이동된다.
    • Survivor1/2 : 살아남은 객체가 순차적으로 이동된다. 
  • Old Generation : 생명 주기가 긴 객체를 GC 대상으로 하는 영역, Young Generation에서 마지막까지 살아남은 객체가 이동
  • Permanent : 클래스와 메소드 메타 정보가 저장된 공간이다. static 변수와 static 메소드가 저장되는 공간
    • java8부터 heap space가 아니다. native area로 이동
    • metaspace라는 용어로 변함
 

마로의 Java(자바) 정리 - 8. 자바 메모리 구조

JVM 구조 실행될 클래스 파일을 메모리에 로드 후 초기화 작업 수행 메소드와 클래스변수들을 해당 메모리 영역애 배치 클래스로드가 끝난 후 JVM은 main 메소드를 찾아 지역변수, 객체변수, 참조

hoonmaro.tistory.com

 

☕ JVM 내부 구조 & 메모리 영역 💯 총정리

저번 포스팅에서는 JRE / JDK / JVM에 대해서 간략하게 알아보는 시간을 가졌다면, 이번 포스팅에서는 JVM의 내부 구조에 대해 좀 더 자세하게 알아보도록 할 예정이다. JVM(자바 가상 머신)은 자바 언

inpa.tistory.com

 

Garbage Collector 제대로 알기

ㅅㅅ슈규슈슉 g..gc 슈슉 g...gc GC! 슈슉

velog.io

 

'자바' 카테고리의 다른 글

스트림 정리  (0) 2023.05.07

자바 8에서 추가된 스트림 개념은 배열이나 컬렉션을 다루는 개념으로 다양한 프레임 워크를 다루는데 혹은 클린코드 등의 주제에서 많이 등장한다. 자바8이라고 해서 얼마 안된 느낌이지만 이러한 기술이 뭔가 지금 사용하는 기술에 매우 유용하게 사용되고 있는 것 같아서 정리하려고 한다. 

 

스트림에서 주로 사용되는 것은 람다식과 표준 API 함수 인터페이스다. 뭔가 낯선 개념이지만 하나씩 정리하려 한다.

 

람다식

함수형 프로그래밍 방식을 지원하는 표현식이다. 컬랙션 or 배열 조작을 위한 방법으로 사용되며 이러한 람다식을 사용하면 코드가 매우 간결해진다. 먼저 람다식을 표현하는 방법에 대해 살펴보자

 

람다식 작성 법칙

람다식은 익명 함수다. 이름이 없는 함수다. 람다식은 아래와 같이 작성된다. 

// 일반 함수(메서드)
int max(int a, int b) {
	return a > b ? a: b;
}

// 람다식으로 바꾼 식
(a, b) -> a > b ? a: b;

함수의 return 타입과 함수 이름이 사라지며 ()와 { } 사이에 화살표(->) 가 추가 된다.  또한 정해진 규칙에 따라 특정 형태에서 타입, 괄호, return, 세미클론 등을 생략할 수 있다. 

 

1. 매개변수의 타입이 추론 가능하면 생략가능하다.(보통 제네릭과 함께 사용되어 타입이 추론된다.)

(int a, int b) -> a > ? a: b  ☞ (a, b) -> a > ? a: b

2. 괄호({   })안에 문장이 하나인 경우 괄호({  })가 생략하능하다. 괄호가 생략되는 경우 끝에 세미클론(;)을 붙이지 않는다.

(int a, int b) -> { return a > b ? a : b; }  ☞ (int a, int b) -> a > b ? a : b
  • 주의 사항
    • 매개변수가 하나일 때만 (  )를 생략할 수 있다. 두 개이상일 때는 (  )를 생략 불가 / 그리고 (  ) 를 생략하는 경우에는 자료형도 함께 생략한다. 매개변수 옆에 자료형이 붙어 있다면 (  )는 생략할 수 없다. 
    • return이 붙어있다면 {   } 를 생략할 수 없다. 
  • 람다식 접근 순서
    • 먼저 함수 이름, 반환타입 제거하고 ( )과 { } 사이에 -> 를 넣자
    • 매개변수가 2개 이상이면 ( )를 지우지 말고 제네릭으로 매개변수 타입이 추론 가능하면 매개변수 타입을 지우고 {  } 안에 문장이 1개이면 return을 지우고 return을 지우면 {  } 를 지우고 {  } 안에 있는 세미클론(;)을 을 빼자

 

람다식은 사실 익명함수가 아니라 익명객체이다. 

(a, b) -> a > b ? a : b   

↕ 위 아래는 사실 동일한 표현

new Object() {
	int max(int a, int b){
    	return a > b ? a : b;
    }
}

Object obj = new Object() {
	int max(int a, int b){
    	return a > b ? a : b;
    }
}
Object obj = (a, b) -> a > b ? a : b ;// 에러
int value = obj.max(3, 5); // 에러

사실 람다식은 Object로 정의한 익명클래스다. 그래서 익명 객체라고도 한다. 하지만  obj.max(3, 5);을 호출하려고 하기에는 Object에서 max가 정의되지 않아 에러가 난다. 결과적으로 반드시 함수형 인터페이스를 써야 한다. 

 

함수형 인터페이스 

단 하나의 추상 메서드만 선언된 인터페이스다. @FunctionalInterface라는 애너테이션을 붙이는데 이 직접 함수형 인터페이스를 구현할 때 @FunctionalInterface를 안붙여도 에러는 나지는 않는다. ( @FunctionalInterface를 붙이면  인터페이스에 메서드를 2개이상 정의시 컴파일 에러를 띄운다. ) 그리고 이것은 익명 클래스와 비슷하다. (interface냐 class냐 차이)

 

interface MyInterface {
	public abstract int max(int a, int b); // 추상메서드는 이거하나 만듬
}

위의 인터페이스의 메서드는 아래의 코드에서 익명 클래스로 메서드가 구현이 되고 인터페이스 자료형으로 메서드를 호출한다. 

MyInterface m = new MyInterface() { // 
    @Override
    public int max(int a, int b) {
        return a > b ? a : b;
    }
};

int value = m.max(3, 5);  // 위의 호출을 다음의 방식으로 해결, 왜냐면 MyInterface에 max 메서드가 정의되어 있다.

↕ 위, 아래 동일

MyInterface f = (a, b) -> a > b ? a : b; 
int value = f.max(3, 5);

// 축약시에는 new MyInterface() { } 부분이 벗겨진다. > 후에 람다식 표현 적용

결과적으로 람다식의 모형과 함수형 인터페이스로 인스턴스를 만드는 방식과 모형이 동일하고 실제로 이 둘은 같다. 람다식은 이러한 함수형 인터페이스를 베이스로 만들어진 것이다. 

 

표준 API 함수 인터페이스

자바에서는 미리 정의된 함수형 인터페이스 API를 제공한다. 

 

1. Comsumer : 소비만 한다. 리턴이 없는 구조이다. 

 - 메서드 이름 : accept

- T -> void
- Consumer<T>
- BiConsumer<T, U> : 매개변수 갯수가 2개 

 

2. Supplier : 공급만 한다. 소비가 없다. 매개변수가 없다. 

 - 메서드 이름 : get

- () -> T 
- Supplier<T>

 

3. Function : 매개변수와 리턴값이 있는 함수다. 함수의 개념을 생각하면 된다. 

 - 메서드 이름 : apply
- Function<T, R>
- BiFunction<T, U, R> : 매개변수 갯수가 2개 

 

4. Operator

- 메서드 이름 : identity

- T -> T
- UnaryOperator<T>
- BinaryOperator<T>
- Function 하위셋

 

5. Predicate : true/false 인지 판단하는 의미로 boolean 리턴형을 가진다. 

- 메서드 이름 : test

- T -> boolean 
- Predicate<T>
- BiPredicate<T, U>
- Function 하위셋

- true/false가 결과로 나오는 형태 

 

6. Comparator : 비교 메서드를 구현, sort 함수에서 인자로 넣는 경우가 있음

- 메서드 이름 : compare  

- (T, T) -> int

 

7. Runnable : 실행 가능한 이라는 의미를 가진다. 매개변수도 없고 반환형도 없는 메서드를 가짐

- 메서드이름 : run

- () -> void

 

8.Callble : 호출 가능한 의미를 가진다. 호출해서 결과를 반환하는 리턴값을 가짐 

- 메서드 이름 :call

- () -> T

 

 

 

'자바' 카테고리의 다른 글

JVM 구조  (0) 2023.07.23

+ Recent posts