JAVA의 특징 및 동작 방식

송규빈·2022년 5월 22일
0

JAVA의 특징

JVM만 설치하면 운영체제에 상관없이 동작한다.

💡 JVM이란? 바이트 코드를 해석하고 기계어로 번역하여 컴파일 된 코드를 실행시켜주는 가상 머신이다.

❗️ JVM은 플랫폼에 종속적이다.

가비지 컬렉터(GC)가 메모리를 관리해준다.

객체지향프로그래밍 언어이다.

💡 객체지향 프로그래밍이란?
객체를 만들기 위한 설계도인 클래스를 작성하고,
객체와 객체를 연결하여 프로그램을 만든다.
Java는 객체지향언어가 가져야 할 캡슐화, 상속, 다형성 기능을 갖추고 있다.

동적 로딩(Dynamic Loading)을 지원한다.

애플리케이션이 실행될 때 모든 객체가 생성되는 것이 아니라,
각 객체가 필요한 시점에 클래스를 동적으로 로딩하여 사용한다.
➡️ 실행 시 연결된 부분에 대한 판단이 필요하므로 '비교적' 속도가 느리다.

속도가 느리다.

위에서 말한 동적로딩의 영향도 있지만,
자바는 우리가 흔히 작성하는 코드가 한 번에 컴파일 돼서 컴퓨터가 알아들을 수 있는 기계어로 번역이 가능한 것이 아니다.

💡 .java ➡️ .class ➡️ 기계어

javac에 의해 .java파일이 컴파일 되어 .class(자바 바이트 코드)로 바뀐 후 JVM에 의해 기계어로 번역되고나서 실행한다.
이렇기에 C나 C++ 같이 컴파일 단계에서 만들어지는 기계어보다 비교적 속도가 느리다. 하지만, 바이트 코드를 기계어로 변환해주는 JIT(Just In Time)컴파일러 같은 기술 적용으로 JVM의 기능이 향상되어 속도의 격차가 많이 줄었다.

JAVA의 동작 방식

1) 작성한 자바 소스(.java)를 자바 컴파일러(javac)를 통해 자바 바이트 코드(.class)로 컴파일 합니다.

💡 자바 바이트코드: JVM이 이해할 수 있는 코드로, 컴퓨터는 읽을 수 없는 반기계어이다. 자바 바이트 코드의 각 명령어는 1바이트 크기의 Opcode와 추가 피연산자로 이뤄져있다.

2) 컴파일 된 바이트 코드를 JVM의 클래스 로더에게 전달한다.

3) 클래스 로더는 동적 로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area), 즉 JVM의 메모리 영역에 올린다.

  • 런타임 데이터 영역: 메소드 영역, 힙 영역, 스택 영역, PC Register, Native Method Stack
  • 로드 타임 동적 로딩: 하나의 클래스를 로딩하는 과정에서 필요한 다른 클래스를 동적으로 로딩하는 것
  • 런타임 동적 로딩: 코드를 실행하는 순간에 필요한 클래스를 로딩하는 것

4) 실행 엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다. 이 때, 실행 엔진은 2가지 방식으로 동작할 수 있다.

  • 자바 인터프리터(Java Interpreter): 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나하나의 실행은 빠르지만, 전체적인 실행 속도를 보면 비교적 느리다는 단점을 갖고있다.

  • JIT 컴파일러 (Just In Time Compiler): 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경. 그 후에는 더이상 인터프리팅 하지 않고 바이너리 코드로 직접 실행하는 방식.
    💡 바이트 코드 전체가 컴파일 된 바이너리 코드들 실행하는 것이기 때문에, 전체적인 실행속도는 인터프리팅 방식보다 빠르다.

5) 런타임 데이터 영역 (Runtime Data Area)

☑️ 런타임 데이터 영역은 JVM의 메모리 영역으로, 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다.

  • Method Area(=Class Area = Static Area)

    JVM 내에서 오로지 한 개만 존재할 수 있으며 공유가 가능한 영역이고, 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있는다. 이렇기에 서로 다른 스레드에서도 접근이 가능하다.
    필드 부분에서 선언된 변수(전역 변수)와 정적 멤버 변수(static이 붙은 자료형)를 저장하고, 타입 정보(타입의 속성이 클래스인지 인터페이스인지에 대한 정보)도 저장한다.
    또한, Method 영역에는 Runtime Constant Pool이라는 상수 풀도 존재하는데 final class 변수의 경우 상수로 치환되어 이 상수 풀에 값이 복사된다.
    ➡️ 호출한 클래스와 인터페이스에 대한 Runtime Constant Pool, 메소드와 필드, Static 변수, 메소드 바이트 코드 등이 저장이 된다.

❗️ 이 영역의 데이터는 프로그램의 시작부터 종료될 때까지 메모리에 남아있으므로, 전역 변수는 어디서든지 사용이 가능하다.하지만 이렇기에 전역 변수를 무분별하게 많이 사용하다 보면 메모리가 부족할 우려가 있다.

  • Heap Area

    모든 객체들의 정보(referece type)를 저장한다. 이 때 변수는 Stack영역의 공간에서 실제 데이터가 저장된 Heap영역의 참조값(reference value, hash code / 메모리에 저장된 주소를 연결해주는 값)을 new 연산자를 통해 리턴 받는다.
    다시 말하면, 실제 데이터를 갖고 있는 Heap 영역의 참조 값을 Stack 영역의 객체가 갖고있다.
    이렇게 리턴 받은 참조 값을 갖고 있는 객체를 통해서만 해당 인스턴스를 핸들할 수 있다.
    이러한 Heap Area도 Method Area와 같이 오직 하나의 영역만 존재하고, 공유할 수 있으므로 다른 스레드에서 접근이 가능하다.
    Heap Area에 저장된 데이터가 더이상 사용이 불필요하다면, 메모리 관리를 위해 JVM의 GC(가비지컬렉터)에 의해 알아서 해제된다.

  • Stack Area

    메서드 내에서 정의하는 primitive type에 해당되는 지역변수(매개 변수 및 블럭문 내 변수 포함)의 데이터 값이 저장되는 공간이다
    프로그램이 아닌 메서드의 시작과 종료를 함께한다. 즉, 메서드가 호출될 때 메모리에 할당되고 종료되면 메모리에서 해제된다.
    모든 스레드에 대해 JVM은 여기에 저장된 하나의 런타임 스택을 만드는데, 이는 메서드 호출 시마다 각각의 스택 프레임(해당 메서드만을 위한 공간)이 생성된다고 보면 된다.
    즉, 생성되는 스레드는 각자 고유의 stack 영역을 갖고 있고, static과 heap영역은 공유할 수 있다.

  • PC Registers

    스레드의 현재 실행 영역의 주소를 저장하고, 각 스레드에는 별도의 PC Register를 갖고 있다.

  • Native Method Stacks

    모든 스레드에 대해 별도의 Native Stack을 생성한 후 Native Method 정보를 저장한다.
    일반적인 메서드를 실행하는 경우 JVM 스택에 쌓이다가 해당 메서드 내부에 Native 방식을 사용하는 메서드 (C, C++로 작성된 메서드)가 있다면 해당 메서드는 Native Method Stack에 쌓인다.

profile
🚀 상상을 좋아하는 개발자

0개의 댓글