[Java] JVM Stack & Heap

[Java] JVM Stack & Heap

“해당 포스팅은 우아한Tech[10분 테코톡] 🎅무민의 JVM Stack & Heap의 영상을 기반으로 포스팅하였습니다. 또한 설명이 너무 잘되어었어 복습하고자 글로 남깁니다. 영상을 제작해주신 우아한Tech무민님에게 감사드립니다.”

#목차

JVM(Java Virtual Machine)이란?

태초에 문제가 있었다…

  • C/C++는 컴파일 플랫폼과 타겟 플랫폼이 다를 경우, 프로그램이 동작하지 않는다. 플랫폼은 운영체제와 CPU 아키텍처의 조합이다.(플랫폼 = 운영체제(MacOS) + CPU 아키텍처(Apple M1))
  • C나 C++는 Compile하고 나면 그 기계 code가 타겟 플랫폼이 dependent(의존)하기 때문에 Compile하는 플랫폼이랑 다를 경우에는 동작할 수 없다.
  • 왜냐하면 OS마다 지원하는 System Call Interface(시스템 호출))가 다르고, CPU 아키텍쳐 마다 지원하는 ISA(Instruction Set Architecture)가 다르다.
    • System Call Interface 란 : Application이 운영체제(OS)에 있는 function(함수(기능))을 호출 하는 것을 의미한다.
    • ISA(Instruction Set Architecture) 란 : 명령어 집합 구조이며, 마이크로프로세서가 인식해서 기능을 이해하고 실행 할 수 있는 기계어 명령어를 말한다.

개발할 때는 문제가 없다

  • 동일한 플랫폼에서 컴파일과 실행을 같이 한다면, 프로그램은 아무 문제없이 동작한다.

same_os

  • macOS에서 compile하고 macOS에서 실행하게 되면 아무런 문제가 없다.
  • 보통 local에서 test 작업을 할 때 많이 사용하는 방법.
  • C/C++도 이와 같은 방법은 잘 작동한다.
  • 문제는…

배포할 때 문제가 발생한다

  • 플랫폼이 달라질 경우, 타겟 플랫폼에서 프로그램이 동작하지 않는다.

other_os

  • macOS에서 compile해서 나온 실행 파일(.exe)을 window로 실행하게 되면 실행되지 않는다.
  • 이게 문제이다… 이걸 해결하기 위해선…

크로스 컴파일(Cross Compile)

  • 타겟 플랫폼에 맞춰 컴파일하는 것을 ‘크로스 컴파일’이라 한다.

cross_compile

  • macOS에서 windows로 타겟을 잡고 컴파일을 할 수 있다.
  • 그렇게 나온 타겟 프로그램은 windows에서 실행이 가능하다.
  • 이런식으로 C/C++같은 언어들은 Cross Compile을 이용하여 타겟 프로그래밍을 작업 하였다.

JVM(Java Virtual Machine)으로 문제 해결

solving

[그림출처](https://www.researchgate.net/figure/Java-virtual-machine-task-exchange-on-a-loosely-coupled-network_fig3_334064226)
  • Java 소스 코드가 .jacac라는 Compile을 거치고 나면 Java Bytecode가 된다.
  • 이 Java Bytecode는 JVM이 설치된 플랫폼이라면 어떤 플랫폼이던 상관 없이 잘 동작하게 된다.
  • 물론, JVM이 플랫폼과 관련된 작업들을 대신 해주기 때문에 가능한 것이다.
  • 만약 배포하게 되는 곳의 OS가 linux면 linux용 JVMwindows면 windows의 JVM을 설치하게 된다면 macOS에서 build 하여 compile한 소스 코드들을 다른 OS에서 실행할 수 있게 JVM이 작업을 해준다.

WORA

Write Once, Run Anywhere(WORA)” - Sun Mycrosystems

wora

[그림출처](https://miro.medium.com/max/1400/1*8unTYz6pOhwryEb5b1S2Sg.png)
  • 즉, 당신이 코딩한 Java 코드를 Compile해서 배포하면, 어떤 플랫폼이든 다시 Compile할 필요 없이 실행시킬 수 있다. 단, 실행하려면 해당 플랫폼에 맞는 JVM을 설치되어 있어야 한다.

굳이 JVM?

  • C/C++도 Cross Compile해서 배포하면 되는데, 굳이 JVM을 사용해야 하는가? 굳이 JVM을 도입한 이유가 뭘까?
    • Java가 나오던 시기(1990년대)는 네트워크가 발전하던 시기이다.
    • 네트워크로 모든 것들이 연결 되고 다 기종의 디바이스에서 실행하기 위한 프로그램이 필요했다.
    • 그러나 C/C++은 당시에만 해도 Cross Compile을 해주는 번거러움이 발생했다.
      • 예를 들어 일반 운영체제 linux나 window가 아니라 Android, iOs, ubuntu, solaris 등등 등장하게 되어 Cross Compile에 한계가 생기기 시작하였다.
  • Java는 네트워크에 연결된 모든 디바이스에서 작동하는 것이 목적이었다.
  • 디바이스마다 운영체제나 하드웨어가 다르기 때문에, 자연스럽게 플랫폼에 의존(dependent)하지 않도록 언어를 설계했다. 그 결과가 Java Bytecode와 JVM이 등장하게 되었다.

Java의 야심

java's ambition

[그림출처](https://i.ytimg.com/vi/4Rk_zDimf2s/mqdefault.jpg)
  • Web Server에 .class파일이 있다.
    • .class파일 이란 : Java Bytecode를 담은 파일이다.
  • 해당 .class파일을 네트워크를 통해서 전달해주면 Web Browser에 JVM이 설치 되어 있어서 실행만 하면 된다.
    • 이것이 정확히 Javascript가 이런 동작을 하고 있다.
    • Javascript 정적 파일을 Web Browser에 보내주게 되면 Web Browser에 설치되어 있는 Javascript Runtime이 즉석에서 인터프리팅(interpreting)해서 실행하게 된다.
      • Compile 언어 : Java, C, C++, … -> 코드가 실행되기 전 Compiler를 거쳐서 기계어로 모두 변환되어 실행되는 프로그래밍 언어
      • Interpreter 언어 : 코드를 바로 실행하는 프로그램 또는 환경(웹 브라우저) -> Javascript(웹 브라우저), SQL(데이터베이스 언어), Python과 Ruby(자체 프로그래밍 언어)

Java 코드가 실행되기까지

compiler

  • 모든 Compiler는 위 그림과 같다.
  • Source Code가 Compiler의 여러 과정을 거쳐 Assembler가 된다.
  • AssemblerAssembly Language를 기계어 형태의 오브젝트 코드로 해석해 주는 컴퓨터 언어 번역 프로그램을 통해 기계어로 변환 된다.
  • Compile에도 Front-end, Back-end가 있다.
  • Web에서는 Backend는 크게 바뀌지 않고, Frontend가 Client종류(Web Browser)에 따라 바뀌게 된다.
  • Compile에서는 반대이다. Compile에서는 Frontend는 바뀌지 않는다.
  • 왜냐하면, Frontend가 하는게 개발자가 짠 Source Code를 분석해서 그 의미를 파악하는 것이다.
  • Abstract Syntax Tree(AST)가 추상적으로 표현하고, Intermediate Representation(IR)으로 번역하기 때문에 Frontend는 플랫폼과 아무 관련이 없다.
  • 그런데 Backend는 계속 바뀔 수 밖에 없는게 중앙 계층 표현(IR/IC)을 Assembly Language로 바꿔야 한다.
  • 그런데 이 Assembly Language라는 것은 운영체제(OS)나 기기에 dependent(의존)하기 때문에 Compiler의 구조에서 Backend만 Windows용, Linux용, MacOS용, 등…이 있는 것이고, 각 OS는 Frontend만 공유하는 것이다.
  • Compiler는 이러한 Layer Architecture로 되어있다.
  • C/C++ Compiler는 통째로 Compiler가 해준다.
  • 근데 Java에서 Frontend는 javac가 해주고, Backend는 JVM이 해준다. 결국 하는 일을 분리해 준 것이다.
  • 그래서 갖는 장점도 있다.
  • C언어는 한 번에 Compile 하고 나면 더 이상 개입할 수 없다.
  • 근데, 개발자들은 사전에 다 알 수가 없다. Runtime에서 어떤 일이 벌어질지…
  • Runtime에서만 발생하는 굉장히 소중한 정보들이 있고, 그 정보들을 이용해서 최적화를 하는 것이 JIT Compiler이다.

JVM 내부 구조

jvm

[그림출처](https://aljjabaegi.tistory.com/387)

Runtime Data Areas

jvm

[그림출처](https://www.devkuma.com/docs/jvm/memory-structure/)
  • JVM이 Java Bytecode를 실행하기 위해 사용하는 메모리 공간Runtime Data Area 라고 한다. 즉, JVM이 Java Bytecode를 실행하는 가상의 기계이다.

공유/개별 thread

per jvm

[그림출처](https://www.devkuma.com/docs/jvm/memory-structure/)
  • Method영역Heap영역모든 Thread가 공유하는 데이터 영역이다.
  • 반면, Stack영역PC Register, Native Method Stack 영역각 Thread마다 생성되는 개별 영역이다.

Method/Heap

method_heap

Method Area

  • Class Loader가 class파일을 읽어왔을때 클래스에 있는 정보들을 Parsing 해서 Method영역에 저장하는 곳.
  • 변수, 메소드, 정적 변수, Bytecode등을 저장하는 곳.

Heap Area

  • 프로그램을 실행하면서 생성한 모든 객체 Instance를 Heap에 저장하는 곳.

PC(Program Counter) Register

  • PC란 Program Counter의 줄임말이다.

pc register

  • 각 스레드는 어떤 메서드를 항상 실행하고 있다.
  • 그때, PC는 그 메서드 안에서 Bytecode 몇 번째 코드를 실행하고 있는지를 나타내는 역할을 한다.

Stack

stack

  • 스택은 스레드 별로 1개만 존재하고, 스택 프레임은 메서드가 호출될 때마다 생성된다.
  • 예를들어 스레드1에 빨간 네모칸스택이고, 아래로 성장하게 된다.
    1. 그때, 맨 위에 있는 stack framemain()메서드이다.
    2. 그리고 그 밑에 있는 stack frame은 main()메서드에서 호출한 어떠한 메서드이다
    3. 그 밑에 있는 stack frame은 그 메서드에서 호출한 또 다른 메서드이다
  • 즉, 메서드 호출 tree와 똑같다고 생각하면 된다.
  • 해당 메서드 실행이 끝나면 당연히 스택 프레임은 pop되어 스텍에서 제거 된다.

Stack Frame

  • 스택 프레임은 메서드가 호출될 때마다 새로 생겨 스택에 push 된다.
  • 스택 프레임 안에 들어가는 정보는 Local variables array, Operand stack, Frame Data를 갖는다.
  • Frame Data는 Constant Pool, 이전 스택 프레임에 대한 정보, 현재 메서드가 속한 클래스/객체에 대한 참조 등의 정보를 갖는다.
  • 즉, 쉽게 말해 Bytecode를 실행하기 위해 모두 필요한 것들이다.
  • 내 메서드가 어디에 속해있는지 등등…

Native Method Stack

Native Method Stack

  • JVM은 성능 향상 목적으로 Java Bytecode가 아닌 다른 언어로 작성된 코드를 compile하여 사용하는 경우가 있다. 그때 사용되는 메서드 이다.

Reference