자바 언어의 특징
1. 운영체제에 독립적이다.
자바로 작성된 프로그램은 운영체제에 독립적이지만 JVM은 운영체제에 종속적이다. 자바 프로그램은 JVM하고만 통신하고 JVM이 자바 응용 프로그램으로부터 전달받은 명령을 해당 운영체제가 이해할 수 있도록 변환하여 전달함
2. 객체지향 언어이다.
상속, 캡슐화, 다형성이 잘 적용된 순수한 객체 지향 언어이다.
3. 자동 메모리 관리 (Garbage Collection)
가비지 컬렉터가 자동적으로 메모리를 관리해주기 때문에 프로그래머는 메모리를 따로 관리하지 않아도 된다.
4. 네트워크와 분산처리를 지원한다.
5. 멀티 쓰레드를 지원한다.
여러 쓰레드에 대한 스케줄링을 자바 인터프리터가 담당함
6. 동적 로딩을 지원한다.
실행 시에 모든 클래스가 로딩되지 않고 필요한 클래스만 로딩하여 사용할 수 있다. 일부 클래스가 변경되어도 전체 애플리케이션을 다시 컴파일 하지 않아도 되며, 애플리케이션의 변경사항이 발생해도 비교적 적은 작업만으로도 처리할 수 있다.
JVM (Java Virtual Machine)
자바를 실행하기 위한 가상 머신, 자바 바이트코드를 실행할 수 있는 주체
JVM의 구조를 살펴보면 [Class Loader, Excution Engine, Garbage Collector, Runtime Data Area]로 나뉜다.
1. Class Loader
자바에서 코드를 작성하면 .java 파일이 생성된다. -> .java 코드를 자바 컴파일러가 컴파일하면 .class 파일(바이트 코드)가 생성된다.
이렇게 생성된 클래스 파일들을 엮어서 JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area로 적재하는 역할을 한다.
자바 애플리케이션이 실행중일 때 이러한 작업이 수행됨.
2. Execution Engine (실행 엔진)
Class Loader에 의해 메모리에 적재된 클래스(바이트 코드)들을 기계어로 변경해 명령어 단위로 실행하는 역할을 함
인터프리터 방식(명령어를 하나하나 실행)과 JIT(Just-In-Time) 컴파일러를 이용하는 방식(적절한 시간에 전체 바이트 코드를 네이티브 코드로 변경해서 실행 엔진이 네이티브로 컴파일된 코드를 실행하는 방식, 성능 up!)
🐯 안드로이드 런타임 ART?
안드로이드 앱이 빌드될 때는 바이트 코드 형태로 컴파일 되는데, 이때 ART(안드로이드 런타임)이 AOT(Ahead-Of-Time) 컴파일을 수행한다. 앱이 론칭 될 때마다 ELF 버전으로 실행되므로 앱의 실행 속도가 더 빠르고 배터리 수명도 향상 되었음
AOT : 바이트 코드를 장치의 프로세서 (CPU)가 필요로 하는 네이티브 명령어 (기계어)로 일괄 변환한다.
ELF : ART를 통해 변환된 기계어 형태 (Executable and Linkable Format)
* 롤리팝(5.*) 이전의 안드로이드 버전에서는 JIT 컴파일 방법을 사용하여 달빅 가상머신에서 바이트 코드를 하나씩 기계어로 변환하여 실행하였었다.
3. Garbage Collector
GC는 Heap 메모리 영역에 생성된 객체들 중 참조되지 않는 객체들을 탐색 후 제거하는 역할을 함
GC가 역할을 하는 시간은 정확히 언제인지는 알 수 없다. 또한, GC가 수행되는 동안 GC를 수행하는 쓰레드가 아닌 다른 모든 쓰레드가 일시정지 됨. 특히 Full GC가 일어나서 수초간 모든 쓰레드가 정지한다면 장애로 이어지는 치명적인 문제가 생길 수 있음.
4. Runtime Data Area
JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역
이 영역은 [Method Area, Heap Area, Stack Area, PC Register, Native Method Stack]으로 나눌 수 있음
1) Method area (메소드 영역), Class area
- Type Information : 클래스와 인터페이스를 통칭하는 것
- Type의 전체 이름 (패키지명 + 클래스명)
- Type의 직계 하위 클래스 전체 이름
- Type의 클래스 / 인터페이스 여부
- Type의 Modifier (public, abstract, final)
- 연관된 인터페이스 이름 리스트
- Runtime Constant Pool : Type의 모든 상수 정보
- Type, Field, Method로의 모든 Symbolic Reference 정보를 포함
- Constant Pool의 Entry는 배열과 같이 인덱스 번호를 통해 접근
- Object의 접근 등 모든 참조를 위한 핵심 요소
- Field Information : Field는 인스턴스 변수를 가르킴
- Field Type
- Field Modifier (public, private, protected, static, final, volatile, transient)
- Method Information : Constructor를 포함한 모든 메소드
- Method Name
- Method Return Type
- Method Parameter 수와 Type
- Method Modifier (public, private, protected, static, final, syncronized, native, abstract)
- Method 구현 부분이 있을 경우 (abstract 또는 native가 아닐 경우)
- Method의 byteCode
- Method의 Stack Frame의 Operand Stack 및 Local variable section의 크기
- Exception Table
- Class Variable : Static 키워드로 선언된 변수
- 모든 인스턴스에 공유 되며 인스턴스가 없어도 직접 접근이 가능함
- 이 변수는 인스턴스의 것이 아니라 클래스에 속함
- 클래스를 사용하기 이전에 이 변수들은 미리 메모리를 할당받아 있는 상태가 됨
- final class 변수는 상수로 치환되어 Runtime Constant Pool에 값을 복사함
🐯 Static 변수는 Method Area의 Class Variable에 저장된다!!
기본형이 아닌 static 클래스형 변수는 레퍼런스 변수만 저장되고 실제 인스턴스는 Heap에 저장되어 있다. 그 후 이 인스턴스의 변수를 저장하기 위해 Heap에 메모리가 확보가 되고 Heap의 인스턴스가 Method Area의 어느 클래스 정보와 연결되는지 설정하게 됨
🐯 Java의 Main Method가 Static인 이유는?
JVM은 인스턴스가 없는 클래스의 main()를 호출해야되기 때문에 Static이어야 함
1) 코드를 실행하면 컴파일러가 .java 코드를 .class(byte code)로 변환함
2) 클래스 로더가 .class 파일을 메모리 영역(Runtime Data Area)에 로드함
3) Runtime Data Area중 Method Area에 class variable이 저장됨
4) JVM은 Method Area에 로드된 main()을 실행함 -> main()이라는 네이밍 자체는 관례적인 것
2) Heap area (힙 영역)
new 키워드로 생성된 객체와 배열이 생성되는 영역, 메소드 영역에 로드된 클래스만 생성이 가능하고 GC가 참조되지 않는 메모리를 확인하고 제거하는 영역. 레퍼런스 변수의 경우 Heap에 인스턴스가 저장되는 것이 아니라 포인터가 저장이 됨
3) Stack area (스택 영역)
지역 변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값등이 생성되는 영역, 메소드를 호출할 때마다 개별적으로 스택이 생성됨
Method가 호출되면 Method와 Method 정보는 스택에 쌓이고 호출이 종료될때 Stack Point에서 제거 된다. Method 종료시 메모리 공간이 사라짐
예) Person p = new Person(); 에서 Person p 는 스택 영역에, new로 생성된 Person 클래스의 인스턴스는 힙 영역에 생성
🐯 멀티 스레드 프로그램에선?
각 스레드가 자신의 스택을 가지고는 있지만 Heap 영역은 공유하기 때문에, 프로그래밍시에 Thread-safe 하지 않는 이슈에 주의하며 프로그래밍 해야 한다. Heap 영역 자체가 Thread-safe하지 않은 상태이므로 Thread safe하게 객체를 생성하기 위해서는 Immutable한 객체를 설계하는게 좋음
4) PC register (PC 레지스터)
쓰레드가 생성될 때마다 생성되는 영역으로 Program Counter 즉, 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역
이를 이용해 쓰레드를 돌아가면서 수행할 수 있게 함 (CPU의 레지스터와 다름)
JVM은 Stacks-Base 방식으로 작동하는데, JVM은 CPU에 직접 Instruction을 수행하지 않고, Stack에서 Operand를 뽑아내 이를 별도의 메모리 공간에 저장하는 방식을 취하는데, 이러한 메모리 공간을 PC Registers라고 한다.
5) Native method stack
자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역. 보통 C/C++등의 코드를 수행하기 위한 스택
🐯 쓰레드가 생성되었을 때 공유하는 영역은?
1, 2번인 메소드 영역과 힙 영역을 모든 쓰레드가 공유하고, 3, 4, 5번인 스택 영역과 PC 레지스터, Native method stack은 각각의 쓰레드마다 생성되고 공유되지 않는다.
Java 애플리케이션 VS 일반 애플리케이션
일반 어플리케이션의 코드는 OS만 거치고 하드웨어로 전달되는데 비해 Java 어플리케이션은 JVM을 한 번 더 거치기 때문에, 그리고 하드웨어에 맞게 완전히 컴파일된 상태가 아니고 실행 시에 해석되기 때문에 속도가 느리다는 단점을 가지고 있다. (바이트코드를 하드웨어의 기계어로 바로 변환해주는 JIT 컴파일러와 향상된 최적화 기술이 적용되어 속도의 격차를 많이 줄임)
위의 그림처럼 일반 애플리케이션은 OS와 바로 맞붙어 있기 때문에 OS에 종속적이다. 그래서 다른 OS에서 실행시키기 위해서 애플리케이션을 그 OS에 맞게 변경해야하지만 JAVA 애플리케이션은 JVM하고만 상호작용을 하기 때문에 OS와 하드웨어에 독립적이라 다른 OS에서도 프로그램의 변경없이 실행이 가능하다.
자바 파일의 구조
- 자바의 모든 코드는 클래스 내부에 존재해야 함. 이 클래스들이 모여 하나의 자바 애플리케이션을 구성함
- 자바 애플리케이션에는 반드시 main 메소드를 포함한 클래스 (public)가 하나는 있어야 함. (main 메소드는 자바 애플리케이션의 entry point)
- 하나의 소스파일에 하나의 클래스만을 정의하는 것이 보통이지만 둘 이상의 클래스를 정의할 수 있음.
- 소스 파일의 이름은 public class의 이름과 동일해야 함
- 소스파일 내에 public class가 없는 경우 소스 파일의 이름은 소스 파일 내의 어떤 클래스 이름으로 해도 무방.
자바프로그램의 실행과정
- 프로그램의 실행에 필요한 클래스 (*.class파일)를 로드한다.
- 클래스 파일을 검사한다. (파일 형식, 악성 코드 체크)
- 지정된 클래스에서 main(String[] args)를 호출한다.
➰ 꼬리에 꼬리를 무는
- 예정 ) Garbage Collector
- 예정 ) 자바의 Static
- 예정 ) Class Loader (계층구조, 위임모델, 세부 동작 등)
•──⋅참고 자료⋅──•
📚 남궁성, [Java의 정석], 도우출판 (2016)
📚닐스미스, [핵심만 골라 배우는 안드로이드 스튜디오 Arctic Fox&프로그래밍], 제이펍 (2021)