"Write Once, Run Anywhere"
- 플랫폼 독립적인 자바의 철학
https://velog.io/@ariul-dev/%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Java-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95
해당 글이 너무 깔끔하게 설명되어 있어 이 글을 보는 것도 추천합니다.
해당 글은 가톨릭대학교 GDSC 자바팀의 해커톤 제출물을 블로그에 재정리한 것 입니다.
함께 활동하신 자바팀 수고하셨습니다!
저자
이승원, 장주은, 정승원, 최민수, 한준희

JDK(Java Development Kit): 자바 개발 환경으로 자바 어플리케이션을 개발하기 위해 필요한 도구를 제공(javac, javap 등..) JDK는 JRE + 개발 도구라고 볼 수 있다.
JRE(Java Runtime Environment): 자바를 실행하기 위한 환경이며, JVM, 자바 클래스 라이브러리, 기타 실행용 파일을 포함
JVM(Java Virtual Machine): 자바 가상 머신
💡 # 용어정리
javac : java compiler 자바 컴파일러로서 자바 소스 파일을 자바 바이트 코드 파일로 변환
javap : 자바 바이트 코드 파일을 사람이 읽기 쉬운 형태로 역어셈블링 해주는 프로그램

해당 그림으로 전체적인 상황을 파악하며 밑의 자세한 내용을 확인하면 된다.
런타임 이전(컴파일 타임)에 자바 소스 코드(.java)를 컴파일러(javac)를 통해 바이트(.class)코드로 변환하는 과정

위 사진이 바로 바이트 코드이다. 바이트 코드는 자체는 바이너리 파일이다. 즉 인간이 파악하기 어려운 이진수로 구성되어 있다. 왼쪽에 offset 8비트(1바이트)가 명령어이다. 명령어가 256(2^8)개를 모두 사용하지는 않지만, 총 1바이트를 사용하기에 바이트코드라고 부른다. 그외에 오른쪽 16바이트는 명령어로 조작할 대상의 포인터나 데이터를 담는다. 바이트 코드는 symbolic reference를 사용한다.
💡 # Symbolic Reference vs Direct Reference
Symbolic Reference : 심볼릭 참조는 참조값을 우리가 작성하면서 사용한 class, field, method의 이름을 참조로 사용하는 것을 말한다.
Direct Reference : 직접 참조는 참조값을 실제 물리 메모리주소로 사용하는 것은 말한다. ex) c의 포인터
💡 # 기계어 vs 어셈블리어
기계어: 기계가 직접 이해할 수 있는 이진수를 의미한다. 또한 하드웨어에 종속적인 것이 특징이다. (즉 CPU 명령어 체계에 따라 달라진다)
어셈블리어: 기계어는 이진수로 되어있기에 인간이 보기에 어렵다. 이를 극복하고자 인간이 읽고 쓰기 쉽도록 1대1 대응하는 언어로 작성한 것이 어셈블리어다. 기계어와 1대1 대응하기에 마찬가지로 하드웨어 종속적이다. 이를 기계어로 번역해주는 것을 어셈블러라고 한다.
그렇다면 자바 바이트코드는 어느쪽일까?
CPU가 직접 알아들을 수 없기에 기계어는 아니며, 어셈블리어에 가깝다고 볼 수 있다. 그러나 자바 바이트 코드는 자바 철학에 따라 하드웨어 종속적이지 않음을 기억해야한다.

만약 바이트코드를 역어셈블러(javap)로 해석해서 보면 이런식으로 볼 수 있다.

인텔리제이와 같은 IDE에서도 .class파일을 열면 이런식으로 역컴파일된 파일을 보여주는데, 알아두어야 할 것은 실제 .class파일은 위에서 보았듯 바이너리 파일로 완전히 다르게 생겼다. (바이트 코드가 저렇게 생겼다고 착각하지 말 것!)
JVM이 컴파일 된 바이트 코드를 읽고 메모리에 로드를 수행하는 과정
즉 자바 프로그램을 실행하기 위해 필요한 클래스와 객체들을 메모리(Run Time Data Area)에 옮기는 것이다.

클래스 로더는 크게 3가지로 역할을 맡는다.
Loading: 클래스 파일을 탑재
Linking: 클래스 파일을 사용하기 위해 검증 + 기본값으로 초기화
Initailization: 정적(static) 필드의 값들을 코드 상에서 정의한 값으로 초기화

계층적 (Hierarchical)
코드를 구현하다 보면 중복을 줄이기 위해 상속을 사용하게 된다.
부모 Class를 가진 자식 Class처럼 ClassLoader도 계층적으로 생성이 가능하다.
클래스 로더는 아래와 같은 계층 구조를 가지고 있다.

가시성 (Visibility)
A class 가 상위 ClassLoader이고 B와 C라는 class가 하위 ClassLoader
A class는 부모이므로 B와 C의 데이터 사용X
같은 부모를 가진 B와 C 서로 데이터 사용X
B와 C는 부모 A의 데이터 사용O

위임형 로드 요청 (Delegation)
ClassLoader1의 자식 = ClassLoader2
ClassLoader2의 자식 = ClassLoader3
부모 클래스가 우선권을 가지고 있으므로 ClassLoader3 → ClassLoader2 → ClassLoader1 순서로 요청 가능
언로드 불가 (Unload Impossibility)
ClassLoader에는 Class 언로딩(Unloading) 기능이 없다.
그렇기에 언로딩(Unloading)을 하기 위해선 ClassLoader 자체를 삭제하고, ClassLoader을 다시 생성하는 방법이 있다.


Heap Area: JVM당 하나만 존재(JVM과 생명주기를 함께함)하며 모든 스레드가 공유. Java로 구성된 객체와 JRE 클래스 들이 올라감. 문자열에 대한 정보를 가진 String Pool, 실제 데이터를 가진 인스턴스, 배열등이 여기에 저장. 해당 영역이 가진 데이터는 모든 JVM 스택 영역(위 그림에서 Stack)에서 참조되어 스레드 사이에 공유.
String Constant Pool: Java String은 불변객체로써 여기에 생성된 문자열을 저장하고 같은 값이면 공유함
Method Area(자바 8부터는 Meta space): JVM당 하나만 존재(JVM과 생명주기를 함께함)하며 스레드가 모두 공유. 단. 영역자체는 JVM과 생명주기를 함께하지면 Class들은 동적으로 클래스 로더에 의해 로딩되고 초기화 됌. 인스턴스 생성을 위한 객체 구조, 생성자, 필드 등이 저장, 런타임 상수풀과 정적(static)변수, 메서드 데이터와 같은 클래스 데이터가 여기에 저장됌.
Class Constant Pool: 클래스 파일의 상수(static) 값이 저장되는 곳. 컴파일 타임에 생성됌. 심볼릭 참조를 사용하며 컴파일 타임에 바이트코드에 기록됌.
Runtime Constant Pool (클래스와 생명주기를 함께함)
(Tread와 생명주기를 함께 함)
💡 # 용어정리
인터프리터: 코드 명령어를 하나씩 읽어서 해석하고 실행 (JVM의 기본 해석 방법)
컴파일러 : 코드파일 전체를 읽고 한번에 해석된 파일을 내놓는 방법
JIT(Just-In-Time) 컴파일러: 인터프리터의 단점을 보완하고자 자주 실행하는 명령어(HotSpot이라는 표현을 씀)인 경우 실시간으로 컴파일을 추가 진행해 미리 기계어로 바꿔줌 (JVM의 속도향상을 위한 보조 해석 방법)
자바의 JVM에서는 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주어 메모리 누수를 방지해주고 메모리를 청소해준다.
동작 과정
💡 # 메모리 단편화란?
위 이미지와 같이 메모리를 사용 후 해제하게 되면 메모리 사이에 빈 공간이 생기게 된다. 이렇게 되면 총 12K의 여유 메모리가 있음에도 5K가 넘는 작업은 실행 할 수 없다. 이런 현상을 메모리 단편화라고 한다. 이를 해결하기 위해서는 사용중인 메모리를 연속으로 모으고 나머지 공간을 온전히 사용할 수 있도록 해주어야 한다.
💡 # 용어정리
최적화 : 프로그램을 더 빠르고 효율적으로 실행하기 위해 다양한 방법을 사용하는 것
동적 인라이닝 : 자주 호출되는 메서드를 인라인으로 변환하여 함수호출 오버헤드를 줄임
프로파일 기반 최적화 : 애플리케이션 실행 중에 수집된 실행 데이터를 바탕으로 최적화 수행
네이티브 메서드 : 자바가 아닌 다른 프로그래밍 언어로 작성된 메서드
JNI : Java Native Interface
스레드 : 프로그램 내에서 실행되는 작은 단위의 작업
경량 스레드 : 운영체제의 커널이 아닌 사용자 수준에서 관리되는 스레드
온디맨드 : 실제로 발생할 때 처리
💡 # 추가내용
JIT 컴파일러 : Just In Time으로, Java 프로그램은 인터프리터 방식으로 실행되어 다른 언어보다 조금 느리기에 이를 극복하기 위해 JVM에는 JIT컴파일러가 포함되어 있음. 이는 바이트코드를 실행 시점에 기계어로 변환하여 실행 속도를 크게 향상 시킴.
객체의 생애 주기 : 객체가 생성되어 사용되고, 더 이상 필요 없게 되면 가비지 컬렉션에 의해 메모리에서 해제되는 전체 과정
https://ko.wikipedia.org/wiki/자바(프로그래밍언어)
https://velog.io/@sgwon1996/JAVA의-동작-원리와-JVM-구조https://adjh54.tistory.com/279#2. 클래스 로딩(Class Loading)-1
https://adjh54.tistory.com/280
https://velog.io/@ariul-dev/차근차근-알아보는-Java-프로그램-실행-과정