Java는 무엇일까?
자바는 객체지향 프로그래밍 언어이다.
C언어에 객체지향적 기능을 추가하여 만든 C++와는 달리 처음부터 객체 지향 언어로 개발되었다.
- 프로그래밍 언어programing language이다.
- 실행환경(JRE)과 개발도구(JDK), 라이브러리(API)를 포함한다.
- 모던 프로그래밍 언어 (객체지향 언어 + 함수형 언어)
Java의 주요 특징
- 객체지향 언어이다.
- 객체를 만들기 위해 설계도인 클래스를 작성하고, 객체를 연결하여 목적이 맞는 프로그램을 만든다.
- 캡슐성, 상속화, 다형성을 지원한다.
- 하이브리드 언어이다.
- 컴파일 언어인 동시에 인터프리터 언어이다.
- 시스템에 무관한 이진 파일을 만듦으로써 컴파일 언어에 가까운 속도와 시스템 독립성을 얻을 수 있다.
- 자동 메모리 관리가 가능하다.
- 객체 생성시 자동으로 메모리 영역을 찾아 할당하고, 가비지콜렉터Garbage Collector, GC로 자동으로 사용하지 않는 객체를 제거한다.
- Multi Thread를 지원한다.
- 풍부한 라이브러리가 존재한다.
- 이식성이 높다.
- CPU 하드웨어, OS 운영체제 등 플랫폼에 독립적이다.
- 자바 가상 머신 Java Virtual Machine
JVM 자바 가상 머신
- 자바 프로그램이 실행되는 가상 컴퓨터(VM)을 말한다.
- 한 번 작성하면, 어디서든 실행이 가능하다. Write once, run anywhere
- JVM 자체는 하드웨어와 운영체제 위에서 실행되므로 플랫폼에 종속적이다.
자바 프로그램의 실행 과정
자바 컴파일러 Java compiler
- 작성한 소스 코드(.java) 를 자바 가상 머신이 이해할 수 있는 자바 바이트 코드이자 어플리케이션으로 변환한다. 이를 컴파일 Compile이라고 한다.
.java
→ Compile →.class
→ Run~
자바 가상 머신 JVM의 구성
- 클래스 로더 class loader
- Execution Engine
- 인터프리터 interpreter
- JIT 컴파일러 Just-In-Time compiler
- 가비지 컬렉터 garbage collector
- Runtime Data Area
1. 클래스 로더 class loader
- 자바는 동적 로드 (바이트 코드를 실행하는 런타임)에서 클래스를 로드하고 링크하는 특징이 있다.
- 클래스 로더는 런타임 중 JVM의 메소드 영역에 동적으로 Java 클래스를 로드하는 역할을 한다.
- 로딩, 링크, 초기화 단계로 나뉘어져 있다.
👀 클래스 로딩의 단계
- 로딩
- 링크
- 초기화
A. 로딩
- 자바 바이트 코드를 읽고 그 내용에 따라 바이너리 데이터를 만들고 메서드 영역(Method Area)에 저장한다.
- 바이너리 데이터
- 로드된 클래스를 비롯한 그의 부모 클래스 정보
- 클래스 파일과 Class, Interface, Enum 관련 여부를 구분하여 저장
- 변수나 메서드 정보
- 이 과정에서 .class 파일이 JVM 스펙에 맞는지, Java 버전이 맞는지 확인한다.
- 로딩이 끝나면 해당 클래스 타입의 객체를 생성하여 메모리의 힙 영역(Heap Area)에 저장한다.
B. 링크
- 검증 Verify
- 읽어 들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 (유효한지) 검사한다.
- 설정에 따라 성능 향상을 위해 진행하지 않도록 설정 가능하다.
- 준비 Prepare
- 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스를 나타내는 데이터 구조를 준비한다.
- static 변수, 기본값에 필요한 메모리 공간을 준비한다.
- 분석 Resolution
- 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체한다. 다이렉트 레퍼런스, 즉 실제 메모리 주소 값으로 변경해주는 작업을 한다.
C. 초기화
- 링크-Prepare 단계에서 확보한 메모리 영역에 클래스 변수들을 적절한 값으로 초기화 한다. 즉, static 필드들이 설정된 값으로 초기화한다.
- SuperClass 초기화와 해당 클래스의 초기화를 진행한다.
🤔 클래스로더의 구조
- 클래스 로더는 계층 구조로 이루어져 있다.
- 기본적으로 3가지 클래스 로더가 제공된다.
- 개발자 필요에 따라 커스텀 클래스 로더를 구현할 수 있다.
Bootstrap ClassLoader
- 최상위 우선순위를 갖는 클래스 로더. JVM 시작시 가장 최초로 실행되는 클래스 로더.
- 자바 자체의 클래스 로더와 최소한의 자바 클래스만을 로드한다.
- java.lang.Object
- Class, ClassLoader
- Java 8 : jre/lib/rt.jar 를 로드한다.
- Java 9 ~ : 더이상 /rt.jar가 존재하지 않고, /lib 내에 모듈화되어 포함되었다. 이제는 ClassLoader 내 최상위 클래스들만 로드한다.
- 네이티브 코드로 구현되어 있다.
Extension ClassLoader → Platform ClassLoader
- 확장 클래스 로더는 부트스트랩 클래스로더를 부모로 갖는 클래스 로더이다.
- 확장 자바클래스들을 로드한다.
- java.ext.dirs 환경변수에 설정된 디렉토리의 클래스 파일을 로드하고 이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext 에 있는 클래스 파일을 로드한다.
- java 8 : URLClassLoader 를 상속. jre/lib/ext 내 모든 클래스 로드
- java 9 ~ : Platform Loader로 변경되었으며, URLClassLoader가 아닌 BuiltinClassLoader를 상속한다. Inner Static 클래스로 구현되어 있다.
System ClassLoader
- 자바 프로그램 실행시 지정한 ClassPath에 있는 클래스 파일 혹은 jar에 속한 클래스들을 로드한다. == .class 확장자 파일을 로드한다.
- 개발자가 애플리케이션 구동을 위해 직접 작성한 대부분의 클래스는 여기서 로딩된다.
- Java 8 : Application ClassLoader로 불림.
- Java 9~ : 클래스패스, 모듈패스에 있는 클래스 로딩.
- URLClassLoader를 상속받아ClassLoaders 클래스의 내부 static 클래스로 구현됨
❗️ 3가지 원칙
위임 원칙 Delegation Principle
https://homoefficio.github.io/2018/10/13/Java-클래스로더-훑어보기/
- 위임 원칙은 클래스 로딩이 필요할 때 3가지 기본 클래스로더의 윗 방향으로 클래스 로딩을 위임하는 것을 말한다.
가시범위 원칙 Visibility Principle
- 가시범위 원칙은 하위 클래스로더는 상위 클래스로더가 로딩한 클래스를 볼 수 있지만, 상위 클래스로더는 하위 클래스로더가 로딩한 클래스를 볼 수 없다는 원칙이다.
유일성 원칙 Uniqueness Principle
- 유일성 원칙은 하위 클래스로더는 상위 클래스로더가 로딩한 클래스를 다시 로딩하지 않게 해서 로딩된 클래스의 유일성을 보장한다.
- 유일성을 식별하는 기준은 클래스의 **binary name**인데, toString()으로 찍다보면 가끔 보이는 java.lang.String, javax.swing.JSpinner$DefaultEditor, java.security.KeyStore$Builder$FileBuilder$1, java.net.URLClassLoader$3$1 이런 것들이 바로 binary name이다.
2-1. 인터프리터 interpreter
- JAVAC 명령으로 자바 프로그램을 중간 형태인 자바 바이트 코드로 컴파일하고, 이를 자바 인터프리터가 한 줄씩 해석하여 기계어로 번역한다.
- 바이트 코드로 쓰여진 파일의 코드를 한 줄씩 읽어내려가면서 실행하는 것이다.
🤔 왜 자바는 컴파일과 인터프리터 방식을 둘 다 하는 것일까?
- 컴파일러는 소스코드 전체를 링커등을 통해 한번에 번역하여 기계어 파일로 만들어 메모리상에 적재한다.
- 인터프리터는 소스 코드를 한 행씩 중간에 코드로 번역 후 실행한다. (보통 VM)안에서 실행된다.
- Compiler와 Interpreter의 차이점
Compiler | Interpreter |
소스코드 전체를 컴퓨터 프로세서가 실행 할 수 있도록 바로 기계어로 변환. 다른 프로그램에 의해 실행된다. | 고레벨 언어를 중간 코드(intermediate code)로 변환하고 이를 각 행마다 실행. |
일반적으로 컴파일러가 실행시간이 빠르다. | |
전체 소스 코드를 변환한 후 에러를 보고한다. | 각 행마다 실행하는 도중 에러가 보고되면 그대로 멈춘다. 보안적인 관점에서 도움 된다. |
자바에서의 compiler 역할 | 자바에서의 interpreter 역할 |
.java 파일을 .class 파일로 변환한다. (자바 소스코드를 JVM 기계어로 변환) |
자바 컴파일러에 의해 변환된 클래스 파일 내 바이트 코드를 특정 환경 기계에서 실행될 수 있도록 변환한다. |
Compile 언어 : C, C++ | Interpreter 언어 : Python |
- 자바에서는 왜 둘 다 사용할까?
- 인터프리팅으로 플랫폼에 종속되지 않는다.
- 자바 바이트코드는 컴퓨터와 프로그램 사이에 별도 버퍼 역할을 한다.
- 보안상 도움을 받을 수 있다.
- 그러나 컴파일러와 인터프리터를 모두 사용하므로 속도가 느리다.
2-2. JIT 컴파일러 Just-In-Time compiler
- JIT컴파일러는 인터프리터의 단점을 보완한다.
- JIT(Just-In-Time) 컴파일러는 런타임 시 바이트 코드를 원시 시스템 코드로 컴파일하여 Java 애플리케이션의 성능을 향상시키는 런타임 환경의 컴포넌트이다.
- JIT 컴파일러는 처음 바이트 코드를 읽을 때에는 인터프리터 방식으로 한줄 한줄 씩 읽고 반복되는 바이트 코드를 또 읽게되면 캐싱해둔 곳에서 native code(원시코드)로 컴파일한다.
- 변환된 원시코드는 인터프리터의 변환과정없이 직접적으로 사용이 가능하며(기존에는 바이트코드에서 원시코드로 변환 후 실행하였다면 JIT 컴파일러를 사용하여 변환된 원시코드는 변환하지않고 바로 실행) 이로 인해 시스템의 성능이 좋아지게 된다.
- JIT 컴파일러의 코드 최적화 방법 참고링크
2-3. 가비지 컬렉터 Garbage Collector
- Garbage Collector는 Heap 메모리 영역에 생성된 객체들 중에 참조되지 않은 객체들을 제거하는 역할을 한다. (별도로 다룰 예정)
3. Runtime Data Area
- JVM의 메모리 영역으로 Java 애플리케이션 실행시 사용되는 데이터를 적재하는 영역이다.
- 구분
- PC register
- Stack
- Native Method Stack
- Heap
- Method Area
PC register
- 스래드가 생성될때마다 생성되는 영역으로 Program Counter 즉, 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. 이것을 이용해서 여러 쓰레드를 제어한다.
- JVM은 Stacks-Base 방식으로 작동한다. CPU에 직접 Instruction을 수행하지 않고 Stack에서 Operand를 뽑아내 이를 별도의 메모리 공간에 저장하는 방식을 취한다. 이러한 메모리 공간을 PC register라고 한다.
Stack
- 지역 변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값등이 생성되는 영역이다.
- int x = 10; 이라는 소스를 작성했다면 정수값이 할당될 수 있는 메모리공간을 x라고 잡아두고 그 메모리 영역에 값이 10이 들어간다. 즉, 스택에 메모리에 이름이 x라고 붙여주고 값이 10인 메모리 공간을 만든다.
- Animal dog = new Animal(); 이라는 코드를 작성했다면 Animal dog은 스택 영역에 생성되고 new로 생성된 Animal클래스의 인스턴스는 힙 영역에 생성된다.
- 스택영역에 생성된 dog의 값으로 힙 영역의 주소값을 가지고 있다. 즉 스택 영역에 생성된 dog가 힙 영역에 생성된 객체를 참조하고 있는 것이다.
Native Method Stack
- 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역이다. 보통 C/C++ 등의 코드를 수행하기 위한 스택이다.
- JNI를 통해 표준에 가까운 방식으로 구현이 가능하다.
Heap
- 인스턴스화 된 모든 클래스 인스턴스와 배열을 저장을 하는 공간이다.
- 모든 JVM 스레드에 공유되는 공유 자원이기도 하다.
- Heap에 저장된 할당된 메모리 회수 권한은 무조건 가비지 컬렉터에 의해서만 회수가 가능하다.
Method Area
- 클래스 수준의 정보를 저장하는 공간이다.
- 타입 정보 (Interface, class)
- 필드 정보(클래스 멤버 변수의 이름, 데이터 타입, 접근 제어자 정보)
- 메서드 정보(메소드의 이름, 리턴 타입, 파라미터, 접근 제어자 정보)
- 런타임 상수 풀(상수 풀 : 문자 상수, 타입, 필드, 객체 참조)
- static 변수, final class 등
- 클래스 변수
- 모든 JVM 스레드에 공유되는 공유 자원이다.
- 사실 논리적으로 Heap 영역에 포함되는 영역이다.
- 객체 생성 후에 메소드를 실행하게 되면 해당 클래스 코드에 대한 정보를 Method Area에 저장 하게 된다.
✨ ummchicken과 함께하는 CS 스터디를 위해 쓰여진 글입니다.
내용에 오류가 있거나 보완할 부분은 댓글로 알려주시면 공부에 도움이 됩니다.
- 작성시 참고한 내용