자바 기초 지식 - 특징, JVM

KH·2023년 9월 14일

Java

목록 보기
9/11

자바의 특징

  • Java는 객체지향 프로그래밍 언어입니다. (상속, 캡슐화, 다형성, 추상화)
    • 객체지향적 언어 OOP란, 프로그래밍에 사용 될 데이터의 상태와 행위를 객체로 만들어, 객체간의 상호작용을 통해 비지니스 로직을 구성하는 프로그래밍 기법이다
  • 기본 자료형을 제외한 모든 요소들이 객체로 표현된다
  • JVM을 이용하기 때문에 운영체제에 독립적(Write once, Run anywhere)이다
  • GabageCollector를 통한 자동적인 메모리 관리가 가능(Managed 언어)하다
  • JVM 위에서 동작하기 때문에 실행 속도가 상대적으로 느리다
  • 다중 상속을 지원하지 않는다
    • 다중 상속을 지원하게 되면 하나의 클래스가 여러 상위 클래스를 상속 받을 수 있다
    • 이때 다중 상속을 받은 부모들에게 동일한 이름의 메소드가 있으면 오류가 발생한다
    • 이를 다이아몬드 문제라고 하고 이 때문에 자바는 내부적으로 막아두었다
    • 하지만 인터페이스는 상속받은 메소드가 구현체가 없는 추상 메소드이기 때문에 가능하다
    • 왜냐하면 구현체가 없기 때문에 상속받은 메소드의 부모를 알 필요가 없다
    • 어차피 상속받은 객체에서 구현을 할 것이기 때문이다
  • 컴파일시 데이터 타입이 결정되는 정적 타입 언어이다
    • 타입 에러로 인한 문제를 컴파일 시점에서 알 수 있기 때문에 안정성이 높다
    • 컴파일 시에 미리 타입을 정해놓기 때문에 실행속도 빠르다
    • 하지만 타입에 대한 제한으로 코드 작성의 유연성이 떨어진다
    • 동적 타입 언어는 런타임시 타입이 결정된다
    • 유연성이 높지만 안정성은 떨어진다
  • 컴파일러
    • 컴파일러는 전체소스코드를 한번에 기계어로 변환하고 실행파일을 생성하여 나중에 실행할 수 있다
    • 컴파일러 언어: c, c++ ..
    • 컴파일러 장점: 실행속도가 빠름, 컴파일 시 에러 검출
    • 컴파일러 단점: 컴파일 과정에서 시간이 많이 소요 -> 테스트 및 개발 시간 지연, 일반적으로 특정 플랫폼에 종속된다
  • 인터프리터
    • 인터프리터는 프로그램을 실행하는 동안에만 소스코드를 한 줄씩 기계어로 변환하고 즉시 실행한다
    • 인터프리터 언어: Python, Ruby, javaScript ...
    • 인터프리터 장점: 개발과 테스트가 빠름, 플랫폼 독립적입니다. 하나의 스크립트를 여러 플랫폼에서 실행할 수 있다
    • 인터프리터 단점: 실행속도가 컴파일러에 비해 느림, 일부 오류는 실행 중에 발견되며, 이로 인해 디버깅이 어렵다
  • 자바는 컴파일러와 인터프리터 언어의 특성을 둘다 가지는 하이브리드 언어이다
    • 자바는 다른 컴파일 언어들이 작동하듯이 자바 컴파일러를 이용해 전체 코드를 한방에 번역한다
    • 이 과정은 컴파일러 언어와 같지만 자바 컴파일러는 코드를 바로 기계어로 번역해주는 것이 아니라, 자바 가상 머신(JVM)이 실행 시킬 수 있는 자바 바이트코드(class파일)로 번역하는 것이다
    • 보통 자바를 컴파일하고나면 .java 파일들이 .class로 변환된 것을 확인할 수 있는데 클래스 파일은 바이트코드로 이루어져 있다
    • .java 코드를 작성하면, javac (java compiler)를 통해 .class 파일로 변환
    • .java -> javac(java compiler) -> .class
    • 자바 인터프리터는 자바 컴파일러에 의해 변환된 .class파일 내의 바이트코드를 특정 환경의 기계에서 실행될 수 있도록 바이트 코드를 한 줄씩 읽으면서 기계어(바이너리 코드)로 변환한다
    • .class -> java interpreter -> 110101 -> 실행

바이트 코드: 사람이 작성하는 소스 코드와 기계어의 중간 단계가 바이트코드이다.

  • 자바는 소스코드를 바이트코드로 컴파일 하고, 이 바이트 코드를 다시 기계어로 만든다.
  • 자바는 다양한 환경에서 실행될 수 있도록 하는 사상을 갖고 설계되었기 때문이다.
  • 기계어는 결국 데이터의 저장과 제어, 연산이 가능한 마이크로 프로세서(~= CPU, 프로세서)를 작동시키기 위해 작성된다.
  • 하지만 각 마이크로 프로세서는 물리적으로 구성된 구조에 따라 설계된 각자 다른 마이크로 아키텍처를 갖고 있어서 기계어를 적용하는 방식(마이크로 프로그래밍)이 다를 수 있다.
  • 따라서 어떠한 프로세서에서도 작동할 수 있도록 그 이전 단계인 바이트코드로 변환하는 것이다. https://whitepro.tistory.com/455

자바 작동순서

  1. 프로그램이 실행되면 JVM은 OS로부터 프로그램이 필요로 하는 메모리를 할당받는다. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다
  2. 자바 컴파일러(javac)가 자바 소스 코드(.java)를 읽어들여 자바 바이트 코드(.class)로 변환시킨다
  3. Class Loader는 실행 시간에 동적 로딩을 통해 class 파일들을 JVM 메모리로 로딩한다
  4. 로딩된 class 파일들은 검증과정을 거쳐 문제가 있다면 예외를 발생시키고 아니면, Execution Engine을 통해 인터프리터 방식 혹은 jit 컴파일러 방식으로 해석된다. JIT 컴파일러는 인터프리터보다 빠르게 실행하기 위해, 자주 사용되는 코드를 기계어로 미리 컴파일한다
  5. 해석된 바이트 코드(바이너리 코드)는 Runtime Data Area에 배치되어 실질적인 수행이 이루어지게 된다. 이러한 과정 속에서 JVM은 필요에 따라 Thread Synchronization과 GC 같은 관리 작업을 수행한다
  6. 런타임 과정에서는 클래스들의 static 변수를 자동 초기화
  7. static 블록 수행
  8. 인스턴스 블록 수행
  9. 생성자 호출
  10. main 함수 호출

JVM

  • Java Virtual Machine
  • 스택 기반의 가상 머신
  • 자바 애플리케이션을 클래스 로더를 통해 읽어들여 자바 API와 함께 실행하는 역할
  • Java Byte Code를 OS에 맞게 해석 해주는 역할
  • 가비지컬렉션을 통해 자동적인 메모리 관리를 해준다
  • JVM의 구조는 Class Loader, Execution engine, Runtime Data Area, JNI, Native Method Library로 이루어져 있다.

JVM의 메모리 구조

  • 메서드 영역 (Method Area)
    • 클래스 로더가 클래스 파일을 읽어들여 클래스 정보, 상수, static 변수를 저장하는 곳
    • JVM이 시작될 때 생성되며, Garbage Collector의 대상이 될 수 없다
    • 클래스 멤버 변수, 메소드 정보, Type 정보, Constant Pool, static, final 변수 등이 생성됩니다. 상수 풀(Constant Pool)은 모든 Symbolic Reference를 포함하고 있습니다
  • 힙 영역 (Heap)
    • 동적으로 생성된 오브젝트와 배열이 저장되는 곳으로 GC의 대상 영역입니다.
    • Garbage Collector의 대상이며, 힙 크기는 -Xms 옵션과 -Xmx 옵션으로 설정할 수 있다
  • 스택 영역 (Stack)
    • 지역 변수, 파라미터 등이 생성되는 영역. 실제 객체는 Heap에 할당되고 해당 레퍼런스만 Stack에 저장됩니다.
    • 각 스레드마다 자신만의 스택이 존재하며, 메서드 호출 시마다 새로운 스택 프레임이 생성
  • PC 레지스터 (Program Counter Register)
    • 스레드가 실행되는 부분의 주소 저장
    • PC 레지스터는 스레드가 실행되는 부분의 주소를 저장하는 곳입니다. 스레드가 어떤 부분을 실행하고 있는지를 나타내는 역할을 합니다.
  • 네이티브 메서드 스택 (Native Method Stack)
    • JNI(Java Native Interface)를 통해 호출되는 네이티브 코드의 스택을 저장하는 곳
    • 네이티브 메서드는 Java 코드와는 다른 방식으로 컴파일되기 때문에 별도의 스택을 필요로한다
    • JNI(Java Native Interface): 자바 애플리케이션에서 C, C++, 어셈블리어로 작성된 함수를 사용할 수 있는 방법을 제공해준다. Native 키워드를 사용하여 메서드를 호출한다. 대표적인 메서드는 Thread의 currentThread()가 있다
    • Native Method Library: C, C++로 작성된 라이브러리
  • Runtime Data Area
    • JVM이 프로그램을 수행하기 위해 OS로부터 할당받은 메모리 공간이다
    • 이 공간은 용도에 따라 여러 영역으로 나누어 관리한다
  • GC(Garbage Collector)
    • 가비지 컬렉터로 힙 영역에서 사용되지 않는 객체들을 제거하는 작업을 의미한다
  • Runtime Data Areas
    • 프로그램 실행 중에 사용되는 다양한 영역이다
  • PC Register
    • Thread가 시작될 때 생성되며 현재 수행 중인 JVM 명령의 주소를 갖고 있다

Class Loader(클래스 로더)

  • Runtime 시에 JVM내로 클래스(.class 파일)를 로드하고 링크를 통해 배치하는 작업을 수행하는 모듈 (Runtime : 클래스를 처음으로 참조할 때)
  • 사용하지 않는 클래스들은 메모리에서 삭제한다
  • 동적 로드를 담당한다

Execution Engine(실행 엔진)

  • 클래스를 실행시키는 역할이다
  • 클래스 로더가 JVM 내의 Runtime Data Area에 바이트 코드를 배치시키고 이는 실행 엔진에 의해서 실행된다
  • 자바 바이트 코드는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다
  • 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다
  • 최초의 JVM은 인터프리터 방식이었기 때문에 속도가 느린 단점이 존재했지만, JIT 컴파일러 방식을 통해 이 점을 보완했다
  • Interpreter(인터프리터)
    • 자바 바이트 코드를 명령어 단위로 읽어서 실행한다
    • 한 줄씩 실행하기 때문에 느리다
  • JIT(Just-In-Time) Compiler
    • 인터프리터 방식의 단점을 보완하기 위해 등장했다
    • 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다
    • 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행된다
    • 한 번만 실행되는 코드라면 JIT 컴파일러가 컴파일하는 게 인터프리팅하는 것보다 오래 걸리므로 인터프리팅하는 것이 유리하다

0개의 댓글