객체지향 프로그래밍(OOP, Object Oriented Programming) 이란 프로그램을 개발하는 기법으로 부품에 해당하는 객체(Object)들을 먼저 만든다. 이러한 객체들을 조립 및 연결하여 전체 프로그램을 완성하는 것이다.
객체를 생성하기 위해서는 설계도에 해당하는 클래스를 작성하고, 객체와 객체를 연결하여(의존성 주입) 목적에 맞는 프로그램을 만들어 낸다. 따라서 JAVA는 객체지향 언어의 특징인 추상화, 캡슐화, 상속성, 다형성의 특징을 제공한다.
- 추상화(Abstraction)
구체적인 사물들의 공통적인 특징들을 파악하여 이를 하나의 개념(집합)으로 다루는 것
- 캡슐화(Encapsulation)
정보 은닉(클래스내의 데이터에 대한 외부의 접근을 제한하는 것), 높은 응집도, 낮은 결합도를 유지하여 유연함과 유지보수성을 증가시키는 것.
- 상속(Inheritance)
- 다형성(Polymorphism)
서로 다른 클래스의 객체가 같은 메시지를 받았을 때 각자의 방식으로 동작하는 것으로 오버라이딩(Overriding)과 오버로딩(Overloading)이 있다.
JVM이란 Java Virtual Machine의 약자로, 자바 가상 머신이라고 부릅니다. 자바와 운영체제 사이에서 중계자 역활을 수행하며, 자바가 운영체제에 종속되지 않으며 프로그램을 실행할 수 있도록 도와줍니다. 하지만 자바의 바이트코드를 운영체제에 맞는 기계어로 번역해야 하기 때문에 JVM이 대신 운영체제에 종속적인 특징을 가지고있습니다.
또한, JVM은 GC(Garbage Collector)를 사용한 메모리 관레도 자동으로 수행하며 다른 하드웨어와 다르게 레지스터 기반이 아닌 스택 기반으로 동작합니다.
자바 프로그램은 완전한 기계어가 아닌 javac를 통해 컴파일 된 중간 단계의 바이트코드(Bytecode)형태의 클래스 파일이 되기 때문에 운영체제에서 이를 해석하고 실행하기 위해서는 JVM이 필요합니다.
이처럼 운영체제와 자바 프로그램을 중계하는 JVM을 두어 여러 운영체제 에서도 동일한 결과를 보장하도록 설계한 것입니다. 따라서 JVM은 자바 프로그램을 바이트코드로, 바이트코드를 운영체제가 이해하는 기계어로 번역하여 실행해야 하기 때문에 운영체제에 맞는 JVM을 설치해야 합니다.
컴파일 언어 Java와 인터프리터 언어 Python은 어떤 차이가 있는지 알아보겠습니다.
Java는 사용자가 작성한 소스코드를 JVM 위에서 실행하기 위해 바이트코드로 변환하는 컴파일 단계를 거칩니다. 이는 운영체제에 종속되지 않는다는 특징이 있습니다.
Python은 사용자가 작성한 소스코드를 따로 변환하지 않고, 인터프리터 소스 코드를 한 줄씩 읽어서 동적으로 기계어로 번역하고 실행하는 방식으로 작동합니다. 이러한 방식은 코드를 즉시 실행할 수 있는 특징이 있습니다.
그렇다면 어떠한 방식이 더 좋을까요? 상황에 따라 다르기 때문에 각 방식의 장단점을 잘 알고 사용하는 것이 중요합니다.
JVM의 메모리 구조는 크게 4가지(Garbage Collector, Execution Engine, Class Loader, Runtime Data Area) 로 나눌 수 있습니다.
- Class Loader (클래스 로더)
바이트코드로 변환된 클래스 파일을 JVM 내부로 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈입니다. 런타임 시에 동적으로 클래스를 로드합니다.
- Execution Engine (실행 엔진)
클래스 로더를 통해 JVM의 Runtime Data Area에 배치된 바이트 코드(클래스)들을 명령어 단위로 읽어 실행시킵니다. 최초의 JVM은 인터프리터 방식을 사용하였지만 속도가 느리다는 단점을 보안하기 위해 JIT 컴파일러 방식을 통해 이 점을 보완하였습니다. JIT 컴파일러는 바이트 코드를 어셈블러(기계어)와 같은 네이티브 코드로 바꿈으로서 실행이 빠르지만 변환에 많은 비용이 발생합니다. 이러한 이유로 JVM은 코드변환을 인터프리터 방식을 사용하다 일정한 기준점을 넘어가게 되면 JIT 컴파일러 방식으로 실행합니다.
[실행순서]
- 컴파일러 방식 :
소스 코드를 컴파일러를 사용하여 기계어로 번역하는 방식입니다. 이 과정에서는 소스 코드 전체를 분석하고 번역하여 실행 가능한 파일(바이너리 또는 바이트 코드)을 생성합니다. 이렇게 생성된 실행 파일은 다른 컴퓨터에서도 실행할 수 있습니다.
===================[Main.java -> Main.class]===================
- 인터프리터 방식 :
소스 코드를 한 줄씩 해석하고 실행하는 방식입니다. 소스 코드를 실행하기 전에 미리 컴파일하지 않고, 실행 중에 한 줄씩 번역하여 실행합니다. 이 방식은 소스 코드를 빠르게 수정하고 테스트할 수 있는 장점이 있으며, 일반적으로 JVM에서는 바이트 코드를 기계어로 변환할 때 이 방식을 사용하지 않습니다. 대신, JVM은 인터프리터와 JIT(Just-In-Time) 컴파일러를 사용하여 실행합니다.
- JIT 컴파일러 방식:
JVM은 프로그램이 실행되는 동안 필요한 부분을 선택적으로 컴파일하여 네이티브 코드로 변환하는 방식입니다. 일정 시간 이상 소요되거나 빈도가 높은 코드 블록에 대해서는 JIT 컴파일러를 활성화하여 성능을 향상시킵니다.
- Garbage Collector (GC)
GC는 힙 메모리 영역에 생성된 객체들 중에서 참조되지 않은(참조가 끝난) 객체들을 탐색 후 제거하는 역활을 합니다. 이때, GC가 역활을 하는 시간은 정확히 알 수 없습니다. 후술문서에서 더 정확하게 알아보겠습니다.
- Runtime Data Area
JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역입니다. 이 영역은 크게 Method Area, Heap Area, Stack영역, PC Register, Native Method Stack으로 나눌 수 있습니다.
- Method area
모든 스레드가 공유하는 메모리 영역입니다. 메소드 영역은 Class, Interface, Method, Field, StaticVar 등의 바이트 코드를 보관합니다.
- Heap area
힙 영역은 모든 스레드가 공유하며, new 키워드로 생성된 객체와 배열(array)이 생성되는 영역입니다. 또한, 메소드 영역에 로드된 클래스만 객체생성이 가능하며 GC가 참조되지 않는 메모리를 확인하고 제고하는 영역입니다.
- Stack area
메소드 호출시마다 각각의 스택 프레임(생성된 메소드만을 위한 공간)이 생성됩니다. 그리고 메소드 안에서 사용된 값들을 저장하고, 호출된 메소드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장합니다. 마지막으로 메서드 수행이 끝나면 선입후출 형식으로 프레임을 삭제합니다.
(method1에서 method2를 호출하게 되면, Stack Area에 두 개의 스택 프레임이 쌓입니다. 호출된 순서의 역순으로 method2를 처리하고 스택에서 프레임을 제거 후 method1을 마저 처리하고 프레임을 스택에서 제거합니다)
- PC Register
PC Register는 다음 인출될 명령어의 주소를 가지고 있는 레지스터 입니다. 스레드가 시작될 때 생성되며 스레드마다 공간이 하나씩 독립적으로 존재합니다. 스레드가 어떤 부분을 무슨 명령으로 실행해야 할 지에 대한 기록을 하는 부분으로 현재 수행중인 JVM 명령의 주소를 갖습니다.
- Native Method Stack
자바 외의 언어로 작성된 네이티브 코드를 위한 메모리 영역입니다.
프로그램을 생성하기 위해 개발자는 첫째로 소스코드를 작성하고 컴파일이라는 과정을 통해 기계어 코드로 변환시킵니다(실행 가능한 프로그램). 이러한 과정을 컴파일타임(Compiletime) 이라고 부릅니다. 컴파일과정을 마친 프로그램은 사용자에 의해 실행되어 지며, 이러한 응용프로그램이 동작되어 지는 때를 런타임(Runtime) 이라고 부릅니다.
컴파일링 과정에서 발생되는 오류와 같은 문제를 컴파일타임 에러(신택스 에러, 타입체크 에러) 라고 부릅니다. 이런 경우 컴파일러는 컴파일 타임 에러를 발생시키고 문제를 일으킨 소스코드 라인을 알려줍니다.
만약 어떤 소스코드가 실행가능한 프로그램으로 컴파일이 되었을지라도, 프로그램의 실행 중에 오류가 날 수 있습니다. 이렇게 프로그램의 실행중에 발생하는 형태의 오류를 런타임 에러(0나누기 오류, NullPointException, 메모리부족 오류) 라고 합니다.
[참고문헌]
https://steady-coding.tistory.com/305
https://github.com/WeareSoft/tech-interview/blob/master/contents/java.md
https://hyuntaekhong.github.io/blog/java-basic01/
https://spaghetti-code.tistory.com/35