본문 바로가기
Programming/Java

자바의 철학으로 보는 JVM

by peter paak 2021. 2. 19.
728x90

Java가 무엇인가라는 질문에 자바의 철학을 먼저 살펴봐야 될 것 같다.

자바의 철학은 Write Once, Run Everywhere으로 시작하여 점차 발전해왔다. 이러한 철학을 실현하기 위해 4가지의 개념을 도입했다.

  1. 자바 프로그래밍 언어
  2. 클래스 파일 포멧
  3. 자바 API (Application Programming Interface)
  4. JVM

이 순서대로 자바의 구동원리를 이해한다면 전반적인 그림을 그릴 수 있을 것이다.

우리는 Java라는 언어를 통해 .java 형식으로 개발자가 읽을 수 있는 코드를 작성한다. 작성한 코드는 JDK라는 Java를 개발하기 위한 여러 툴을 가진 곳에서 javac라는 컴파일러를 사용하여 컴퓨터가 이해할 수 있는 byte code인 .class로 컴파일된다. 우리가 흔히 아는 .exec와 같이 더블클릭해서 실행되는 파일이 아니다. .class는 단지 "JVM만이 해석 할 수 있는 binary 형태의 코드"이다. JVM이 직접 실행하는 것이 아니다. JVM이 .class를 호출하여 어떤 내용이 있는지 해석한다. 그리고 해석된 코드를 실행하기 위해서는 JRE라는 자바가 실행할 수 있는 환경에서 해석된 코드를 실행하게 된다. .class 는 JRE 내부에 있는 라이브러리라 할 수 있는 Java API와 함께 실행되게 된다.

간단히 정리한 흐름은 .java 코드 → 컴파일 → .class → 해석 → 실행하는 과정을 가지게 된다. 자바의 장점은 윈도우든 맥이든 아무데서 .java 코드를 작성하고 컴파일한 .class 파일이 이쓰면 JVM이 있는 곳 어디서든 실행시킬 수 있다. 자바의 철학인 Write Once, Run Everywhere와 매우 부합하는 매커니즘이다. 전체 흐름에서 앞에서 설명한 자바 프로그램 언어를 사용해서 클래스 파일 포멧으로 바꾸고 JVM에서 호출하고 해석한 다음 자바 API와 함께 실행하는 4가지의 개념을 모두 사용했다.

이제 앞에서 이야기한 4가지에 대해 자세히 알아보자

1. 자바 프로그래밍 언어

자바 언어는 개발자가 작성하는 .java를 총칭한다. 중요한 키워드는 두가지이다.

  1. 동적으로 실행
  2. 생산성을 극대화

동적으로 실행

자바는 Dynamic Linking을 사용한다. 바이너리 코드인 .class 는 JVM에 의해 해석되면서 실행가능한 형태로 변형 된다. 이때 내부에는 Symbolic Reference만 가지고 있다. 즉 참조값만 덩그러니 가지고 있다. .class 가 실행되어 메모리에 올라갈 때, Symbolic Link는 메모리의 실제 물리적 주소를 참조하게 된다. 물리적 주소를 Linking 하는 것을 필요할 때마다 동적으로 할 수 있어서 Dynamic Linking이라고 부른다. class 파일에 참조값만 덩그러니 있기 때문에 class 파일의 사이즈를 작게 유지할 수 있다. 참조값을 연결할 때는 데이터의 주소가 아니라 파일의 이름으로 주소를 할당하는 방법을 사용한다. 하지만 실제로 java를 컴파일해서 크기를 비교하면 class 파일이 더 크다. 컴파일 과정에서 헤더나 기타 정보들이 컴파일 되기 때문이다. 자세한 내용은 왜 .class파일은 .java보다 클까를 참조하자

생산성을 극대화

앞서 .class 파일만있으면 JRE에서 알아서 메모리의 물리주소를 동적으로 참조할 수 있다. 즉, 자바가 스스로 메모리의 관리를 하지 않기 때문에 (반드시!) GC와 같은 기술을 사용하여 주소를 해제해 준다. 이는 기존의 C언어와 같이 개발자가 직접 메모리 할당 해제를 해줘야 되는 등의 생산성이 낮은 행동들을 알아서 해주기 때문에 생산성이 높다고 할 수 있다. 요즘 나오는 언어들은 모두 지원하는 내용이지만 20년이 넘은 자바를 생각하면 당시에는 매우 생산적인 아이디어라고 생각한다.

2. 클래스 파일 포멧

자바는 컴파일러를 통해서 .java 파일을 참조값만 덩그러니 가진 적은 사이즈의 .class로 만들어서 JVM에서 실행을 한다. 여기서는 4가지의 특징을 발견할 수 있다

  1. compact
  2. bytecode
  3. platform에 독립적
  4. Network byte order

.class는 JVM만이 이해할 수 있는 binary형태의 byte code이다. 내부에 Symbolic Link만 덩그러니 가지고 있는 매우 compact한 형태로 바뀌게 된다. JVM이 있는 곳이면 윈도우든 맥이든 platform에 가리지않고 실행이 가능하다. Network Byte Order는 class 파일을 실행할 때 주소값을 할당하는 방법이다. 정확히는 CPU가 컴퓨터에서 Network를 통해 전송을 받을 때 데이터의 주소가 달라지는 것을 막아준다. 자바는 네트워크로 통신할 때 통일된 Big Endian을 사용하여 네트워크로 데이터를 전송하더라도 주소값이 달라질 걱정을 할 필요가 없다. 그래서 RMI과 같은 네트워크 통신 기능을 사용할 수 있도록 해준다. 자세한 내용은 visualvm 사용하기 편을 참고하도록 하자

3. 자바 API

자바 API는 자바를 실행하기 위해 필요한 라이브러리의 집합이라고 할수 있다. 앞서 JRE는 자바를 실행하는 환경이라고 하였는데 내부에는 JVM자바 API 그리고 Native Method를 가진다.

Java API는 Native Code를 통해 OS와 소통을 하게 된다. NativeCode는 성능상의 목적으로 OS에서 바로 실행할 수 있는 코드로 변환된 코드이다 (아마 C로 작성된 것으로 알고 있다). 정확히는 Native Code 내부의 시스템 콜을 통해 OS의 메모리나 CPU, DB와 같은 자원에 연결해서 사용될 것으로 추측한다. 자바 프로그램은 단지 자바 API의 참조값을 호출할 뿐이다. 우리가 아는 자바 Interface를 호출한다고 보면된다. JRE 덕분에 OS에 관계없이 독립된 환경에서 자바 프로그램을 실행할 수 있게 된다

4. JVM

JVM은 자바 프로그램을 위한(J) 물리적인 형태가 아닌 가상(virtual)의 소프트웨어(V)로서 어느 OS에 관계없이 실행될 수 있는 독자적인 기계(M)라고 정의할 수 있다. 특이한 점은 JVM은 어떤 구현체가 아니다. JVM은 이렇게 구동해야 한다는 상세한 정의, spec이다. 이를 구현하는 것은 여러 벤더(HotSpot)들이 JVM의 명세에 따라서 구현하는 것에 따라 달라진다. 정리하자면 JVM은 자바에 관한 모든 것들을 정의한 명세를 구현한 독자적인 인스턴스라고 할 수 있다.

JVM의 구조를 보면 다음과 같다.

class loader로 .class 파일을 호출하고 Execution Engine에 의해 해석되어 Runtime Data Area에 의해 실행된다. 필요에 따라서는 GC나 Thread 동기화도 같이 실행된다.

정리

회사에서 JVM을 다룰 일이 많아지면서 자바의 내부를 알아야 될 일이 많아졌다 (너무 기쁘다). 시중에 JVM 튜닝이라던가 최적화 책이 많지만 뭔가 기본원리를 흐름에 따라서 설명한 책을 찾는 것이 힘들었던 것 같다. 그러다 Java Performance Fundamental이라는 책을 발견하게 되었고 구현원리에 대해 아주 깊은 내용까지 다루어서 얼른 보고 싶었다. 하지만 역시 2009년에 절판이었고 여타 다른 절판된 책들과 같이 중고나라에서 조차도 찾을 수 없겠지 (그나마 도서관에 높은 확률로 절판된 책들이 있었지만 어디에서 찾을 수 없었다) 했었지만 왠일인지 알라딘에서 찾을 수 있었다 심지어 내가 사는 곳 근처에서!. 한달음에 책을 사서 읽는데 대충 훑어봤는데 너무 마음에 든다. 첫장에 저자인 김한도님께서 박태환 프로젝트에 언급한 내용이 있다. 하루에 50 페이지씩 100일동안 자바 관련 책만 읽었다는 것을 보고 역시 존버가 답이다 라는 생각밖에 들지 않았다. 느리지만 (하지만 빠르게) 꾸준히 하는 것이 중요하다. 내일도 감사한 마음으로 다음장을 넘겨야겠다

참고

728x90