1. JVM 이란 무엇이고 왜 필요한지 설명해주실 수 있을까요?

Java Virtual Machine (JVM)은 자바 바이트코드(.class 파일)를 실행할 수 있는 가상의 실행 환경을 제공하는 스펙입니다. JVM이 있는 곳이라면 어떠한 운영체제에서도 자바 프로그램을 실행시킬 수 있습니다. 이런 특성 때문에 자바는 "한 번 작성하면, 어디에서나 실행할 수 있다(Write Once, Run Anywhere)"는 특징을 가지게 되었습니다.

JVM이 필요한 이유는 다음과 같습니다.

  1. 플랫폼 독립성: 자바 어플리케이션은 JVM 위에서 동작하며, JVM은 다양한 운영 체제에서 동일하게 동작합니다. 따라서 자바 어플리케이션은 한 번 작성하면 어떠한 운영 체제에서도 동작하는 플랫폼 독립적인 프로그램을 만들 수 있습니다.
  2. 메모리 관리: JVM은 가비지 컬렉션(Garbage Collection) 기능을 통해 프로그래머 대신 메모리를 관리해줍니다. 이로 인해 프로그래머는 메모리 관리에 신경 쓰지 않고, 비즈니스 로직에 집중할 수 있습니다.
  3. 보안: JVM은 클래스를 로드할 때 검증 과정을 거쳐 안전하지 않은 코드의 실행을 방지합니다.
    더보기

    JVM에는 몇 가지 보안 기능이 내장되어 있습니다. 일반적으로 이러한 기능은 Java의 "쓰기 한 번, 어디서나 실행"이라는 철학을 지원하면서도 악의적인 코드로부터 시스템을 보호하는 데 도움이 됩니다.

    1. 바이트코드 검증: JVM은 자바 애플리케이션을 실행하기 전에 클래스 로더를 통해 로드된 자바 바이트코드를 검사합니다. 이 검사 과정에서 바이트코드가 JVM 규칙을 준수하는지, 버퍼 오버플로우와 같은 악의적인 행동을 유발할 수 있는 코드가 없는지 확인합니다. 이것은 바이트코드가 안전하게 실행될 수 있도록 보장하는 첫 번째 방어선입니다.
    2. 클래스 로더: JVM의 클래스 로더는 애플리케이션에 필요한 클래스를 동적으로 로드합니다. 일반적으로 클래스 로더는 로컬 파일 시스템에서 클래스를 로드하지만 원격 소스에서 클래스를 로드하는 것도 가능합니다. 이러한 동적 로딩 방식은 보안상의 문제를 야기할 수 있지만, 클래스 로더의 구조는 서로 격리된 네임스페이스를 제공하여 이를 방지합니다. 이렇게 하면 악의적인 클래스가 기존 클래스를 오버라이드하거나 악의적인 방식으로 작동하는 것을 방지할 수 있습니다.
    3. 보안 매니저: Java의 보안 매니저는 애플리케이션이 수행하는 작업에 대한 접근 제어를 제공합니다. 보안 매니저는 파일 I/O, 네트워크 연결, 스레드 생성 등의 작업을 허용하거나 거부할 수 있습니다. 보안 정책에 따라 이러한 작업을 제한하거나 허용할 수 있습니다.

    이러한 기능들을 통해 JVM 실행하는 코드의 안전성을 보장하며, 시스템 리소스에 대한 접근을 제어합니다. 그러나 JVM 자체의 보안 기능만으로는 충분하지 않을 있으며, 보안은 어플리케이션 코드 작성, 데이터 보호, 네트워크 보안 여러 레벨에서 고려되어야 합니다.

JVM 자바 어플리케이션의 실행을 위한 핵심적인 요소입니다. JVM 스펙에 따라서 구현된 여러 가지 JVM 구현체 (Oracle HotSpot, OpenJDK ) 있으며, 이러한 구현체는 각각의 특성을 가지고 있습니다.

 

JVM이란 Java Virtual Machine 의 줄임말로, Java Byte Code 를 운영체제에 맞게 해석해주는 역할을 한다. 즉, 작성한 자바 프로그램의 실행 환경을 제공하는 자바 프로그램의 구동 엔진이다. Java compiler 는 .java 파일을 .class 라는 자바 바이트코드로 변환시켜주는데 Byte Code 는 기계어(Native Code)가 아니므로 OS 에서 바로 실행이 되지 않는다. 이때 JVM은 OS가 Byte Code 를 이해할 수 있도록 해석해주는 역할을 담당한다. JVM은 메모리 관리도 담당한다. 이를 '가비지 컬렉터'라고 하는데, 가비지 컬렉터는 Java7부터 힙 영역의 객체들을 관리하는 역할을 담당한다.

 

Java Virtual Machine의 줄임말로 OS에 종속받지 않고 Java를 실행시킬 수 있는 가상머신으로 JVM은 JAVA와 OS 사이에서 중개자 역할을 수행하여 JAVA가 OS에 관계없이 실행될 수 있게 해준다. 크게 세가지로 중요한 부분이 나뉜다. java 파일을 컴파일러가 자바 바이트 코드인 .class로 변환한다. 바이트 코드를 Class Loader 에서 Loading, Linking, Initialization 과정을 거치며 클래스를 실제 실행될때 사용하는 영역인 Runtime Data Area의 메모리로 로딩한다. 이때 메서드, 힙 ,스택 ,PC 레지스터,Native Method Stacks의 5개의 영역으로 구성된다. 위의 영역을 Execution Engine에서 실제로 실행한다. 이 때 반복되는 명령어는 JIT 컴파일러가 바로 실행시켜주며 가비지 컬렉터가 사용하지 않는 힙 영역에서 할당한 메모리를 주기적으로 삭제해준다.

 

JVM은 자바 가상 기계를 의미하며, 자바 소스 파일을 컴파일한 .class 바이트 코드 파일을 기계어로 번역 후 실행시켜줍니다. 자바 프로그램을 개발하기 위해서 자바 언어로 된 자바 소스파일(.java)을 생성한 뒤 컴파일하면 확장명이 .class인 바이트 코드 파일이 생성되는데, 해당 바이트 코드는 완전한 기계어가 아니므로 운영체제에서 바로 실행할 수 없습니다. 해당 과정에서 JVM이 바이트 코드 파일을 각 운영 체제에서 실행 가능한 기계어로 번역해줍니다. 그로 인해 다양한 운영체제에서 수정하지 않고도 바로 사용할 수 있습니다. 가령 예로 들자면 윈도우 운영체제에서 개발한 바이트 코드 파일을 리눅스로 옮겨도 바로 실행될 수 있게 해주는 경우와 같습니다.

 

2. JVM의 구조와 작동 원리에 대해 설명해주세요

Java Virtual Machine(JVM)은 자바 애플리케이션이 실행되는 런타임 환경입니다. 자바 프로그램은 바이트코드로 컴파일되고, 이 바이트코드는 JVM에서 실행됩니다.

JVM의 주요 기능은 메모리 관리와 가비지 컬렉션, 코드 최적화 등입니다. JVM의 작동 원리와 구조는 아래와 같습니다.

  1. 클래스 로더(Class Loader): 자바 애플리케이션은 여러 개의 클래스 파일로 구성되어 있습니다. 클래스 로더는 이러한 클래스 파일들을 로드하고 JVM 메모리에 배치하는 역할을 합니다.
  2. 런타임 데이터 영역(Runtime Data Area): JVM 메모리는 여러 개의 영역으로 구성되어 있습니다. 이러한 영역에는 객체, 메소드, 스레드 정보 등이 저장됩니다.
  3. 실행 엔진(Execution Engine): 바이트코드는 실행 엔진에 의해 해석되고 실행됩니다. JIT(Just-In-Time) 컴파일러는 여기에 포함되며, 자주 사용되는 바이트코드를 기계어로 변환하여 프로그램의 성능을 향상시킵니다.
  4. 네이티브 메소드 인터페이스(Native Method Interface): 자바 외의 언어로 작성된 함수를 실행하기 위한 인터페이스입니다.
  5. 가비지 컬렉션(Garbage Collection): JVM은 동적으로 할당된 메모리를 자동으로 관리합니다. 더 이상 참조되지 않는 객체를 자동으로 삭제하는 가비지 컬렉션 기능을 제공합니다.

이렇게 JVM 플랫폼 독립적인 바이트코드 실행 환경을 제공하여, 작성한 자바 프로그램이 다양한 플랫폼에서 실행될 있게 합니다.

 

3. JVM의 메모리 관리와 가비지 컬렉션에 대해 설명해주세요.

JVM(Java Virtual Machine)은 메모리 관리를 위해 몇 가지 특별한 영역을 정의하고 있습니다. 이 영역들은 다음과 같습니다.

  1. 힙(Heap) 영역: JVM의 가장 큰 메모리 영역으로, 모든 클래스 인스턴스와 배열이 이 영역에 할당됩니다. 힙 영역은 여러 스레드가 공유하며, 이 영역에서 가비지 컬렉션이 주로 발생합니다.
  2. 스택(Stack) 영역: 각 스레드마다 하나씩 생성되며, 메소드 호출과 로컬 변수를 위한 메모리 공간을 제공합니다. 메소드 호출이 발생할 때마다 스택 프레임이라는 블록이 스택에 추가되며, 메소드 호출이 종료되면 해당 스택 프레임이 제거됩니다.
  3. 메소드(Method) 영역: 모든 스레드가 공유하는 영역으로, 각 클래스의 구조정보를 담고 있습니다. 또한, 런타임 상수 풀, 필드와 메소드 데이터, 메소드와 생성자의 바이트코드 등도 이 영역에 저장됩니다.
  4. 네이티브 메소드 스택(Native Method Stack) 영역: JVM 외부의 네이티브 메소드를 위한 메모리 영역입니다.

JVM 메모리 관리의 핵심적인 부분은 가비지 컬렉션(Garbage Collection, GC)입니다. 이는 프로그램이 동적으로 할당한 메모리를 자동으로 회수하는 프로세스로, 이상 프로그램에 의해 참조되지 않는 객체를 탐지하고 제거합니다. 이런 방식으로, 자바 프로그래머는 메모리 해제 등에 대한 걱정 없이 코드를 작성할 있습니다. 다만 가비지 컬렉션은 CPU 리소스를 소모하기 때문에, 이를 효율적으로 관리하려면 GC 동작 방식을 이해하는 것이 중요합니다.

 

4. JIT 컴파일러란 무엇인가요? 그리고 그것이 왜 필요한지 설명해주세요.

JIT(Just-In-Time) 컴파일러는 JVM의 중요한 부분 중 하나로, 프로그램의 실행 속도를 향상시키는 데 사용됩니다.

일반적으로 자바 코드는 컴파일 단계에서 바이트코드로 변환되고, 이 바이트코드는 런타임에 JVM에 의해 기계어로 해석되어 실행됩니다. 이렇게 인터프리트 방식으로 코드를 실행하는 것은 단순하고 플랫폼 독립적이지만, 성능 면에서는 부족할 수 있습니다.

이런 성능 이슈를 해결하기 위해 JIT 컴파일러가 등장했습니다. JIT 컴파일러는 바이트코드를 런타임에 기계어로 컴파일하여 실행속도를 향상시킵니다. 더욱이, JIT 컴파일러는 동적으로 (즉, 실행 중에) 코드를 분석하므로, 인터프리트 방식보다 더 효율적인 최적화를 수행할 수 있습니다.

예를 들어, JIT 컴파일러는 반복적으로 호출되는 메소드(핫스팟이라고 불리는)를 발견하면 그 메소드를 특히 신경 써서 최적화합니다. 또한, JIT 컴파일러는 불필요한 계산을 제거하거나, 메모리 접근을 줄이는 등의 최적화도 수행합니다.

JIT 컴파일러의 도입으로 자바의 실행 속도는 크게 향상되었으며, 이로 인해 자바는 높은 수준의 플랫폼 독립성과 효율적인 성능을 동시에 제공할 있게 되었습니다.

 

5. JVM과 JRE, JDK의 차이에 대해 설명해주세요.

JVM (Java Virtual Machine):

  • JVM은 자바 바이트코드를 실행하는 런타임 환경입니다. JVM은 가상의 컴퓨터로 볼 수 있으며, 바이트코드를 해당 컴퓨터의 기계어로 해석하고 실행합니다. JVM은 플랫폼 독립적인 자바 코드를 가능하게 하는 중요한 역할을 합니다.

JRE (Java Runtime Environment):

  • JRE는 JVM과 함께 자바 프로그램을 실행하는데 필요한 라이브러리 파일들을 포함합니다. 즉, JRE는 JVM과 자바 클래스 라이브러리를 포함하여 자바 프로그램이 실행되는 환경을 제공합니다.

JDK (Java Development Kit):

  • JDK는 자바 개발자를 위한 통합 소프트웨어 환경으로, 자바 프로그램을 개발하고 컴파일하는 데 필요한 도구를 제공합니다. JDK는 JRE를 포함하며, 이는 개발된 프로그램을 실행하기 위해 필요합니다. JDK에는 컴파일러(javac), 디버거(jdb), 아카이버(jar) 등의 개발 도구가 포함되어 있습니다.

간단하게 요약하면, JDK 개발을 위한 환경이며 JRE 프로그램을 실행하는 필요한 환경이고, JVM 실제로 바이트코드를 해석하고 실행하는 역할을 합니다.

 

6. 자바 프로그램이 실행되는 과정을 설명해주세요.

  1. 코드 작성: 프로그래머는 .java 확장자의 파일에 자바 코드를 작성합니다. 이 파일을 소스 파일이라고 합니다.
  2. 컴파일: 작성된 자바 코드는 자바 컴파일러(javac)에 의해 컴파일됩니다. 이 과정에서 소스 파일(.java)은 바이트코드(.class) 파일로 변환됩니다. 바이트코드는 JVM이 이해할 수 있는 언어입니다.
  3. 로드: 컴파일된 바이트코드는 클래스 로더에 의해 JVM 메모리에 로드됩니다. 클래스 로더는 .class 파일들을 읽어들여 JVM이 이해할 수 있는 형태로 메모리에 로드합니다.
  4. 실행: 클래스 로더에 의해 메모리에 로드된 바이트코드는 실행 엔진에 의해 실행됩니다. 실행 엔진은 바이트코드를 한 번에 한 줄씩 읽어들여서 실행합니다. 이때 JIT(Just-In-Time) 컴파일러를 활용하여 성능을 향상시킬 수 있습니다.
  5. 가비지 컬렉션: 프로그램이 실행되는 동안, JVM은 더 이상 사용되지 않는 메모리를 해제하는 가비지 컬렉션을 수행합니다.
  6. 종료: 프로그램이 종료되면 JVM도 종료됩니다. 모든 자바 프로그램은 JVM 내에서 실행되므로, 프로그램이 종료되면 그에 따라 JVM도 종료됩니다.

위의 과정을 통해 자바는 '쓰기 쉽고, 실행하기 쉬운' 언어라는 목표를 달성합니다. 이를 가능하게 하는 핵심적인 요소가 JVM이며, 이로 인해 자바 프로그램은 다양한 플랫폼에서 작동할 있습니다.

 

7. Java가 컴파일되는 과정은 어떻게 되는지 설명해주실 수 있을까요?

  1. 소스 코드 작성: 프로그래머가 자바 언어로 코드를 작성하고 이를 .java 확장자를 가진 파일로 저장합니다. 이를 소스 코드 파일이라 합니다.
  2. 컴파일: 작성된 소스 코드는 자바 컴파일러(javac)에 의해 컴파일됩니다. 자바 컴파일러는 .java 파일을 읽어들이고, 이를 JVM이 이해할 수 있는 중간 언어인 바이트코드(.class 파일)로 변환합니다. 이 과정에서 문법 오류가 있는지 검사하고, 문법에 맞지 않는 코드가 있다면 컴파일 오류를 반환합니다.
  3. 바이트코드 실행: JVM은 컴파일러가 생성한 .class 파일을 읽어들입니다. 이 파일에는 바이트코드가 저장되어 있으며, JVM은 이 바이트코드를 해석하여 프로그램을 실행합니다. 이 과정에서 JIT(Just-In-Time) 컴파일러를 사용하여 바이트코드를 더 효율적으로 실행합니다.

JVM 바이트코드를 직접 해석하고 실행하는 방식은 자바가 " 작성하면 어디에서나 실행할 있다(Write Once, Run Anywhere)" 장점을 가질 있게 합니다. 이는 바이트코드가 플랫폼 독립적이며, JVM 설치되어 있다면 어떤 시스템에서든 실행할 있기 때문입니다.

개발자가 Java 소스코드를 작성하면 Java Compiler가 JVM이 읽을 수 있는 Bytecode로 컴파일 합니다. 컴파일된 Bytecode를 JVM의 class loader에 전달합니다. class loader는 dynamic loading을 통해 필요한 class 파일들을 로딩 및 링크하여 런타임 데이터 영역 즉, JVM의 메모리 영역에 올립니다. 그 다음 Java 언어 명세 및 JVM 명세에 명시된 대로 구성되어 있는지 검사하고 class의 필드, 메소드, 인터페이스 등으로 메모리를 할당합니다. class의 상수 풀 내 모든 symbolic reference를 direct reference로 변경하고 class 변수들을 적절한 값인 static 필드로 초기화 합니다. 이 모든 class loader의 세부 과정이 끝나면 실행 엔진은 JVM 메모리에 올라온 Bytecode들을 명령어 단위로 하나씩 가져와 인터프리터 또는 JIT 컴파일러로 변환하여 실행합니다.

1. 개발자가 자바 소스코드(.java)를 작성합니다. 2. 자바 컴파일러가 자바 소스코드(.java)파일을 읽어 바이트코드(.class)코드로 컴파일 합니다. 바이트코드(.class)파일은 아직 컴퓨터가 읽을 수 없는 JVM(자바 가상 머신)이 읽을 수 있는 코드입니다. (java - > class) 3. 컴파일된 바이트코드(.class)를 JVM의 클래스로더(Class Loader)에게 전달합니다. 4. 클래스 로더는 동적로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area), 즉 JVM의 메모리에 올립니다. 5. 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행합니다. 

 

8. JVM의 클래스 로더에 대해 설명해주세요.

클래스 로더(Class Loader)는 JVM 내부의 컴포넌트 중 하나로, .class 파일들 즉, 바이트코드를 메모리에 로드하는 역할을 합니다. 이 클래스 로더는 필요한 클래스를 찾아 메모리에 로드하고 이를 사용할 수 있도록 준비합니다.

클래스 로더의 동작 과정은 다음과 같습니다:

  1. 로딩(Loading): 클래스 로더는 필요한 .class 파일을 찾아서 JVM의 메모리 영역인 메서드 영역에 로드합니다. 이 때, 클래스 파일의 바이트코드를 읽어 클래스 또는 인터페이스의 바이너리 데이터를 생성하고 이를 메모리에 로드합니다.
  2. 연결(Linking): 로딩이 완료된 클래스들을 검증하고, 정적 변수에 대한 메모리를 할당하며, 해당 메모리를 기본값으로 초기화하는 과정을 포함합니다. 이 과정에서 JVM은 클래스의 바이트코드가 JVM의 규칙을 준수하는지, 예를 들어 final 클래스가 subclass를 갖는지 등을 검사합니다.
  3. 초기화(Initialization): 정적 변수들을 개발자가 지정한 초기값으로 설정합니다. 이 과정은 클래스의 static 블록이 실행되는 시점입니다.

JVM 클래스 로더는 위에서 언급한 기본적인 동작 외에도, 중요한 특징으로 "부모-자식 위임 모델" 있습니다. 이는 클래스 로더가 특정 클래스를 로드하려 , 먼저 부모 클래스 로더에게 위임하는 것을 말합니다. 모델은 자바의 보안 모델에 중요한 역할을 합니다. 이를 통해 JVM 클래스를 로드하는 과정에서 일관성을 유지하고, 악의적인 클래스로부터 보호될 있습니다.

 

9. JVM 언어가 무엇인지, 왜 사용하는지 설명해주세요.

JVM 언어는 Java Virtual Machine (JVM)에서 실행될 수 있는 프로그래밍 언어를 일컫습니다. 가장 대표적인 JVM 언어는 Java이지만, 이 외에도 Kotlin, Scala, Groovy, JRuby 등 다양한 언어들이 JVM에서 작동합니다.

JVM 언어를 사용하는 이유는 다음과 같습니다:

  1. 플랫폼 독립성: JVM 언어로 작성된 프로그램은 JVM이 설치되어 있는 모든 운영체제에서 실행될 수 있습니다. 이는 'Write Once, Run Anywhere'라는 자바의 핵심 철학을 구현한 것입니다.
  2. 성능 최적화: JVM은 Just-In-Time (JIT) 컴파일러를 포함하고 있어, 프로그램의 실행 속도를 높이는 데 도움을 줍니다.
  3. 메모리 관리: JVM은 가비지 컬렉션(Garbage Collection) 기능을 제공하여 개발자가 메모리 관리에 대한 부담을 덜 수 있게 도와줍니다.
  4. 보안: JVM은 프로그램 실행 중에 보안 문제를 체크하여 시스템을 보호합니다.
  5. 다양한 언어 지원: 다양한 JVM 언어들이 존재하기 때문에, 개발자는 자신의 목적에 맞는 언어를 선택하여 사용할 수 있습니다.

JVM 언어를 사용하면 JVM 생태계의 장점을 모두 활용할 수 있습니다. 이는 대규모 기업 환경에서는 매우 중요한 요소입니다. Java 라이브러리 및 프레임워크의 거대한 생태계, 훌륭한 성능 최적화, 플랫폼 간의 호환성 등을 활용할 수 있기 때문입니다.

그러나 JVM 언어를 사용할 때도 고려해야 단점이 있습니다. 예를 들어, JVM 애플리케이션의 시작 시간이 상대적으로 길고, 메모리 사용량이 크다는 점입니다. 이는 특히 마이크로서비스 아키텍처나 클라우드 기반 환경에서는 더욱 중요한 문제가 있습니다.

 

10. JVM의 스택과 힙메모리 영역에 대해 아는 만큼 설명해주실 수 있을까요?

JVM의 메모리는 크게 다섯 가지 영역으로 나눌 수 있습니다: 메서드 영역, 힙 영역, 스택 영역, 네이티브 메서드 스택, PC 레지스터. 이 중 힙과 스택에 대해 설명하겠습니다.

힙(Heap) 영역

힙 영역은 객체와 배열이 할당되는 곳입니다. 즉, new 키워드를 사용하여 생성된 인스턴스들이 저장되는 공간입니다. 또한, 힙 영역에 할당된 메모리는 가비지 컬렉션에 의해 관리됩니다.

힙 영역은 여러 스레드 간에 공유되므로 전역 변수와 같은 정적 변수를 저장하는 곳으로도 사용됩니다. 힙 영역의 메모리는 가비지 컬렉터가 자동으로 회수합니다. 따라서 개발자는 이 영역의 메모리를 직접 해제할 필요가 없습니다.

스택(Stack) 영역

스택 영역은 지역 변수와 메서드 호출에 대한 정보를 저장하는 공간입니다. 메서드가 호출될 때마다 해당 메서드에 대한 스택 프레임이 생성되고, 이 스택 프레임 안에는 지역 변수, 매개 변수, 메서드의 반환 값 등이 저장됩니다. 메서드 호출이 완료되면 해당 스택 프레임은 스택에서 제거(pop)됩니다.

스택 영역은 각 스레드마다 독립적으로 생성되며, 스레드 간에 데이터를 공유하지 않습니다. 따라서, 한 스레드에서 발생한 예외가 다른 스레드에 영향을 미치지 않습니다.

스택 영역의 메모리는 메서드 호출이 종료되면 자동으로 해제됩니다. 따라서 개발자가 직접 메모리를 관리할 필요가 없습니다. 하지만 스택 오버플로우와 같은 문제가 발생할 있으며, 이는 스택 영역의 메모리가 부족하게 되는 경우에 발생합니다. 이런 문제는 재귀 호출이 과도하게 이루어지는 경우 등에 발생할 있습니다.

더보기

메서드 영역(Method Area):

메서드 영역은 JVM이 읽어 들인 각 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 데이터, 메서드와 생성자 코드, 클래스, 인스턴스, 인터페이스 초기화 메서드 등을 저장합니다. 메서드 영역은 힙 영역과는 다르게 가비지 컬렉션의 대상이 아닙니다.

네이티브 메서드 스택(Native Method Stacks):

네이티브 메서드 스택은 자바 외의 언어로 작성된 네이티브 메서드를 위한 메모리 영역입니다. 네이티브 메서드는 보통 C나 C++ 등의 언어로 작성되며, 자바 네이티브 인터페이스(Java Native Interface, JNI)를 통해 호출됩니다.

PC 레지스터(Program Counter Register):

PC 레지스터는 현재 실행 중인 JVM 명령의 주소를 가지고 있습니다. 이것은 스레드마다 하나씩 존재하며, 스레드가 어떤 명령을 실행해야 할지 결정하는데 사용됩니다. 만약 실행 중인 명령이 있다면 PC 레지스터는 명령의 주소를 가리키고, 만약 실행 중인 명령이 없다면 (예를 들어, 해당 스레드가 네이티브 메서드를 실행 중일 경우) PC 레지스터는 undefined 값을 가집니다.

 

Stack의 경우에는 정적으로 할당된 메모리 영역이다. Stack에서는 Primitive 타입 (boolean, char, short, int, long, float, double) 의 데이터가 값이랑 같이할당이 되고, 또 Heap 영역에 생성된 Object 타입의 데이터의 참조 값이 할당 된다. 그리고 Stack 의 메모리는 Thread당 하나씩 할당 된다. 만약 새로운 스레드가 생성되면 해당 스레드에 대한 Stack이 새롭게 생성되고, 각 스레드 끼리는 Stack 영역을 접근할 수 가 없다. Heap의 경우에는 동적으로 할당된 메모리 영역이다. 힙 영역에서는 모든 Object 타입의 데이터가 할당이 된다. (참고로 모든 객체는 Object 타입을 상속받는다.) Heap 영역의 Object를 가리키는 참조변수가 Stack에 할당이 된다. 어플리케이션에서의 모든 메모리 중에서 Stack에 쌓이는 애들 빼고는 전부 이 Heap 쌓인다고 보면 된다.

 

JVM 스택 영역은 프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다. 각종 형태의 변수나 임시 데이터, 스레드나 메소드의 정보를 저장한다. 메소드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성된다. 메서드 수행이 끝나면 프레임 별로 삭제를 한다. 메소드 안에서 사용되는 값들을 저장한다. 또 호출된 메소드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다. 힙 영역은 객체를 저장하는 가상메모리 공간. new 연산자로 생성되는 객체와 배열을 저장한다.Class Area(Static Area)에 올라온 클래스들만 객체로 생성할 수 있다.

 

스택은 정적 메모리가 저장되는 영역입니다. 원시타입의 데이터의 값과 힙 영역에서 생성된 Object 타입의 참조 값 등이 저장되며 함수가 호출될 때 사용되는 메모리입니다. 기능 수행이 끝나면 자동으로 반환되며, 이는 LIFO 방식으로 관리됩니다. 또한 쓰레드당 1개씩 존재합니다. 힙 영역은 동적 메모리가 저장되는 영역이며, 프로그램을 실행하면서 생성한 모든 인스턴스들을 저장합니다. 또한 모든 쓰레드가 공유하고 있으며, 스택과 연결되어 자동으로 관리되지 않는 메모리들은 GC가 관리하게 됩니다.

 

Stack 영역은 자료를 순차적으로 저장하며 LIFO구조로 정적 메모리 공간을 할당한다 그리고 기본타입 (정수타입, 실수타입, 논리타입)을 저장하며 메소드 작업이 종료되면 할당되었던 메모리 공간은 반환되어 비워진다 Heap 영역은 실행 시간 동안 사용할 메모리 공간을 할당하며 정적 메모리 공간 할당과 대조적이다 참조타입 (배열, 열거, 클래스, 인터페이스)를 저장하며 명시적 해제 혹은 garbage collect가 일어나기 전까지 유지된다

 

11. JVM의 성능 측정 방법에 대해 알고 있나요?

JVM의 성능 측정은 여러 가지 관점에서 볼 수 있습니다. 일반적으로 가장 중요한 성능 지표는 실행 시간, 메모리 사용량, CPU 사용률, 가비지 컬렉션(GC) 횟수 및 지속 시간 등입니다.

  1. 실행 시간: 프로그램이 작업을 완료하는 데 걸리는 시간을 측정합니다. 이는 종종 응답 시간이라고도 불리며, 사용자 대화형 애플리케이션에서 매우 중요한 지표입니다.
  2. 메모리 사용량: JVM이 얼마나 많은 메모리를 사용하고 있는지를 측정합니다. 힙 메모리 사용량이 너무 높으면 OutOfMemoryError가 발생할 수 있으므로 이를 모니터링하는 것이 중요합니다.
  3. CPU 사용률: JVM이 얼마나 많은 CPU 자원을 사용하고 있는지를 측정합니다. CPU 사용률이 높으면 시스템이 과부하 상태에 빠질 수 있습니다.
  4. 가비지 컬렉션(GC) 횟수 및 지속 시간: GC는 JVM의 효율성에 큰 영향을 미칩니다. GC 횟수가 너무 많거나 GC에 너무 많은 시간이 소요되면 애플리케이션 성능이 저하될 수 있습니다.

이러한 지표를 측정하려면 다양한 도구를 사용할 수 있습니다. JVM은 JMX(Java Management Extensions)를 통해 많은 성능 데이터를 제공합니다. 이를 통해 JVM의 동작을 모니터링하고 분석할 수 있습니다.

JConsole, VisualVM, Java Mission Control 등의 도구는 JVM의 성능 데이터를 시각적으로 표현하며, 이를 통해 JVM 성능을 모니터링하고 튜닝할 수 있습니다.

또한, GC 로그 분석 도구들을 사용해 가비지 컬렉션의 성능을 측정하고 이해하는 도움이 있습니다.

 

12. JVM의 튜닝과 최적화에 대해 어떤 경험이 있나요?

JVM 튜닝과 최적화는 자바 애플리케이션의 성능을 향상시키는 데 중요한 역할을 합니다. 여기에는 여러 가지 방법이 있습니다:

  1. 힙 메모리 설정: JVM의 힙 메모리 크기를 적절히 설정하는 것은 성능 향상에 중요합니다. 힙 메모리가 너무 작으면 가비지 컬렉션이 자주 발생하며, 너무 크면 가비지 컬렉션의 시간이 길어질 수 있습니다.
  2. 가비지 컬렉션 튜닝: JVM의 가비지 컬렉션 동작을 튜닝하여 성능을 개선할 수 있습니다. 가비지 컬렉터의 종류(G1GC, ParallelGC, CMS 등)를 선택하거나, 가비지 컬렉션 시간을 최소화하는 방법 등이 있습니다.
  3. JVM 옵션 최적화: JVM 옵션을 통해 자바 애플리케이션의 성능을 개선할 수 있습니다. 예를 들어, Just-In-Time 컴파일러의 동작을 조절하는 옵션, 시스템 프로퍼티를 설정하는 옵션 등이 있습니다.
  4. 프로파일링 도구 사용: JVM의 동작을 모니터링하고 분석하는 데는 다양한 프로파일링 도구(JVisualVM, JProfiler 등)를 사용할 수 있습니다. 이 도구들을 통해 JVM의 성능 문제를 파악하고, 이를 개선하는 방법을 찾을 수 있습니다.

그러나 JVM 튜닝과 최적화는 매우 복잡한 작업이며, 애플리케이션의 성능을 개선하기 위해 적절한 지식과 경험이 필요합니다. 특히 프로덕션 환경에서는 JVM 튜닝과 최적화 작업이 잘못되면 애플리케이션의 성능을 저하시키거나, 예상치 못한 문제를 발생시킬 있으므로 주의가 필요합니다.

 

13. 여러 JVM 구현체 중 Oracle의 HotSpot과 OpenJDK의 차이점에 대해 알고 있나요?

Oracle의 HotSpot과 OpenJDK는 모두 Java의 실행을 위한 가장 널리 사용되는 JVM 구현체입니다. 두 가지 모두 Java SE 스펙을 준수하며, Java 애플리케이션을 실행하기 위한 핵심 기능을 제공합니다.

  1. 라이선싱: OpenJDK GPL 라이센스 하에 배포되는 오픈 소스 프로젝트입니다. 이는 무료로 사용할 있으며 소스 코드를 자유롭게 있다는 것을 의미합니다. 한편, Oracle HotSpot JVM Oracle Binary Code License Agreement 따라 사용이 허가됩니다. 라이선스는 Oracle JDK 11 이후로 상업적인 목적의 사용에 대해 유료로 변경되었습니다.
  2. 기능과 플러그인: JVM 모두 Java SE 스펙을 준수하므로 기본적인 기능은 동일합니다. 그러나 Oracle JVM 추가적인 몇몇 상용 기능을 포함하고 있습니다. 예를 들어, Java Flight Recorder Java Mission Control 같은 고급 진단 도구들이 있습니다. OpenJDK에는 이런 추가적인 도구들이 포함되어 있지 않지만, 대부분의 경우 이런 도구들 없이도 충분히 사용할 있습니다.
  3. 성능: JVM 성능은 거의 동일하다고 있습니다. 고급 최적화 기술, JIT 컴파일, 그리고 세련된 가비지 컬렉션 알고리즘을 사용합니다. 그러나 이들 간의 성능 차이는 특정 애플리케이션의 특성에 따라 다소 다를 있습니다.
  4. 업데이트 주기와 지원: Oracle HotSpot 대해 주기적인 업데이트와 보안 패치를 제공하며, 이는 종종 유료 서비스로 제공됩니다. 반면에 OpenJDK 빈번하게 업데이트되며, 보안 패치도 커뮤니티에서 제공합니다. 이러한 차이는 OpenJDK Oracle JVM 선택하는 중요한 요소가 있습니다.

14. Garbage Collector의 역할, 원리, 알고리즘에 대해 아는 만큼 설명해주실 수 있을까요?(GC 동작 방식)

Garbage Collector(GC)의 기본적인 역할은 더 이상 사용되지 않는 메모리를 자동으로 회수하여 메모리 누수를 방지하고, 이로 인해 생길 수 있는 애플리케이션 성능 저하를 막는 것입니다.

Garbage Collector의 기본 원리는 'Reachability(접근성)'입니다. 즉, 어떤 객체가 루트로부터 연결되어 있지 않으면, 그 객체는 더 이상 사용되지 않는 것으로 간주되어 가비지 컬렉션의 대상이 됩니다. 여기서 루트는 GC가 추적을 시작하는 시작점으로, 일반적으로 스택에 위치한 지역 변수나 액티브 스레드, static 필드 등을 말합니다.

GC의 주요 알고리즘은 다음과 같습니다:

  1. Mark and Sweep: GC 먼저 모든 루트로부터 시작하여 사용되고 있는 객체를 찾아내 '마킹'합니다. 다음에, 마킹되지 않은 객체들을 메모리에서 '스위핑'하여 제거합니다. 알고리즘은 간단하고 효과적이지만, 스위핑 후에 메모리 공간이 분편화될 있다는 단점이 있습니다.
  2. Copying: 알고리즘은 메모리를 부분으로 나눕니다. 하나는 사용 중인 객체를 저장하고, 다른 하나는 비어 있습니다. GC 동작하면 사용 중인 객체 '살아 있는' 객체만 비어 있는 공간으로 복사됩니다. 과정이 끝나면, 이전에 사용하던 공간은 모두 비워집니다. 방식은 메모리 분편화 문제를 해결하지만, 항상 일정량의 메모리 공간이 비어있어야 한다는 단점이 있습니다.
  3. Generational: 알고리즘은 객체가 생존하는 시간에 따라 메모리를 분리합니다. 일반적으로 'young generation' 'old generation'으로 나누는데, 새로 생성된 객체는 young generation 위치하며, 오래동안 생존한 객체는 old generation으로 이동합니다. 대부분의 객체는 빠르게 비활성화되므로, young generation에서 자주 GC 수행하게 됩니다. 방식은 대부분의 모던 JVM에서 사용되는 기본 알고리즘입니다.
    더보기

     가비지 컬렉션(GC)의 알고리즘은 어떻게 메모리에서 불필요한 객체를 찾아내고 제거할지를 결정하는 규칙이나 절차를 말합니다. 이러한 알고리즘은 자원의 효율적인 관리를 위해 매우 중요하며, 프로그래밍 언어나 시스템의 메모리 관리 전략에 따라서 다르게 구현될 수 있습니다.

    앞서 언급한 'Mark and Sweep', 'Copying', 'Generational' 등의 알고리즘은 가비지 컬렉션의 기본적인 알고리즘입니다.

    • 'Mark and Sweep'은 사용되고 있는 객체를 '마킹'하고, 사용되지 않는 객체를 '스위핑'하는 방식을 사용합니다.
    • 'Copying'은 사용되는 객체와 사용되지 않는 객체를 분리하기 위해 메모리를 두 부분으로 나누는 방식을 사용합니다.
    • 'Generational'은 객체의 생존 시간에 따라 메모리를 관리하는 방식을 사용합니다.

    각각의 알고리즘은 자체로서 중요한 가치를 가지고 있지만, 실제로는 이러한 기본 알고리즘들이 조합되거나 변형되어서 사용되는 경우가 많습니다. 예를 들어, 'Generational' 알고리즘 내에서 'Mark and Sweep'이나 'Copying' 등의 방식이 적용될 있습니다. 이런 방식으로, GC 알고리즘은 주어진 상황에 가장 효과적인 메모리 관리를 있도록 설계되고 개선되어 왔습니다.

실제로 GC 동작 방식은 JVM의 종류와 버전, 그리고 설정에 따라 다소 달라질 수 있습니다. 각 GC 알고리즘은 특정 애플리케이션의 특성에 따라 더 잘 동작하거나 그렇지 않을 수 있습니다. 예를 들어, 사용자 응답 시간이 중요한 애플리케이션의 경우 'Stop-The-World' 시간을 최소화하는 GC 알고리즘을 선택할 수 있습니다. 'Stop-The-World'는 GC가 작동하는 동안 애플리케이션의 실행을 일시 중지하는 시간을 말합니다.Java 11에서는 ZGC(Z Garbage Collector)가 소개되었는데, ZGC는 'Stop-The-World' 시간을 거의 없애는 것을 목표로 하는 GC입니다. ZGC는 메모리의 분편화를 방지하기 위해 객체를 이동시킬 수 있으며, 이 과정을 병렬로 처리하여 GC 성능을 향상시킵니다. 다양한 GC 알고리즘에 대해 알아보고, 애플리케이션의 특성과 요구 사항에 가장 적합한 GC 알고리즘을 선택하고 튜닝하는 것은 JVM 성능 최적화의 중요한 부분입니다. Java 9부터 G1(Garbage-First) GC가 기본 GC로 사용됩니다. G1 GC는 메모리를 여러개의 동일한 크기의 Region으로 나누고, GC 동작 시에는 가장 가비지가 많이 쌓인 Region부터 처리하는 방식을 사용합니다. 이 방식을 통해 GC 효율을 높이고, 'Stop-The-World' 시간을 줄일 수 있습니다.

 

자바 가비지 컬렉터(GC)의 동작 방식

  1. 마킹(Marking): 가비지 컬렉터는 먼저 어떤 객체들이 "살아있는" 객체인지 파악하기 위해 모든 객체를 훑습니다. 이 과정에서 GC는 루트(root)라 불리는 객체들(로컬 변수와 입력 매개변수, 실행 중인 메소드, 네이티브 스택, 클래스 등)부터 시작해 참조를 따라가면서 마킹을 합니다.
  2. 삭제(Sweeping): 마킹 후에 GC 메모리에서 마킹되지 않은 객체들을 제거합니다. 이들은 프로그램에 의해 이상 참조되지 않는 객체들로서, 가비지라고 있습니다.
  3. 콤팩션(Compaction): 가비지 컬렉션 후에 메모리에는 여전히 사용 중인 객체와 이상 사용되지 않는 객체들이 섞여 있을 있습니다. 콤팩션은 이러한 메모리 공간을 재정렬하여 연속적인 공간을 만드는 과정입니다. 과정은 메모리 단편화를 방지하며, 새로운 객체들이 메모리에 할당될 있도록 합니다.

자바 가비지 컬렉션(GC) 알고리즘에는 여러 종류가 있습니다. 여기에는 Serial GC, Parallel GC, CMS (Concurrent Mark Sweep) GC, 그리고 G1(Garbage First) GC 등이 포함되며, 각각의 알고리즘은 특정 유형의 애플리케이션에 더 적합하다고 볼 수 있습니다.

  1. Serial GC - 이 GC는 단일 스레드에서 작동하며, 모든 GC 과정(Mark, Sweep, Compact)을 수행하는 동안 애플리케이션 스레드를 일시 중지합니다. 주로 단일 프로세서 시스템이나 소규모 애플리케이션에 사용됩니다.
  2. Parallel GC - 이 GC는 여러 GC 스레드를 사용하여 가비지 컬렉션 작업을 병렬로 수행합니다. 그러나 마찬가지로 GC 작업이 진행되는 동안에는 애플리케이션 스레드를 일시 중지합니다. 이 GC는 멀티 프로세서 시스템이나 CPU 리소스가 많은 시스템에 적합합니다.
  3. CMS (Concurrent Mark Sweep) GC - 이 GC는 애플리케이션 스레드와 병렬로 작동하여 가비지 컬렉션의 대부분 과정을 처리합니다. 이로 인해 애플리케이션의 중단 시간이 줄어들지만, CPU 리소스를 더 많이 소모합니다. 또한, 메모리 단편화 문제가 발생할 수 있습니다.
  4. G1 (Garbage First) GC - 이 GC는 메모리를 여러 개의 작은 영역으로 나누고, 이 영역들 중에서 가비지가 가장 많은 영역부터 수집하는 방식으로 작동합니다. 이로 인해 GC의 효율성과 성능을 개선하며, 중단 시간을 예측 가능하게 만들 수 있습니다.

위에서 언급한 GC 알고리즘 외에도 ZGC(Z Garbage Collector), Shenandoah 등과 같은 알고리즘들이 있으며, 이들은 특히 대용량 힙을 가진 애플리케이션에 사용됩니다. 이들 알고리즘은 GC 중단 시간을 최소화하려는 목적으로 설계되었습니다.