반응형
SMALL

JVM 구조

JVM : 자바가상머신. 자바 바이트코드를 실행시키는 주체. 운영체제 종류와 무관하게 가능
즉, 운영체제 위에서 동작하는 프로세스로 자바를 컴파일해서 얻은 바이트코드를 기계어로 바꿔서 실행시키는 역할.

1. 소스코드 -> 바이트코드

소스코드는 .java로 저장한다.이걸 자바 컴파일러(javac)가 바이트코드로 바꿔주는데 그건 .class로 저장한다.
왜? 일단 1차적으로는 코드 숨기기, byte코드로 바꾸면 문법검사같은건 안하게 되면서 실행 시간이 단축됨.
근데 이러면 소스코드 변경할때마다 또 컴파일러가 .class로 byte코드로 변경하니까 번거로움.

2. 바이트코드 -> Runtime Data Area

이렇게 변경된 byte코드(.class)파일들은  class Loader가 Runtime Data Area로 로딩시킨다.
Runtime Data Area는 5가지 영역으로 되어있는데,
1. 메소드영역 , 힙 영역 : 모든 스레드가 공유하는 영역
2. stack, PC Register, Native Method Stack : 스레드마다 하나씩 생성되는 공간
이렇게 나뉜다.

하나씩 보자.

1) method 영역

JVM이 시각될 때 생성되는 공간으로 byte코드(.class)가 여기에 저장된다.
그리고 모든 스레드가 공유하는 영역이니까 클래스의 정보, 변수 정보, static으로 선언한 공유변수가 저장되고 모든 스레드가 공유한다.

2) heap 영역

동적으로 생성된 객체가 저장되는 영역, 즉 new 연산으로 동적으로 생성된 인스턴스(클래스가 객체가 된 것)가 여기에 저장된다.
클래스의 객체, 배열 등 쉽게 소멸되지 않는 데이터가 있다.

heap 영역은 가비지 콜렉터(GC)의 영역이 된다. heap도 크게 3가지로 나뉘는데, young/old/permanent 이다.

2-1) GC(가비지 콜렉터)

가비지 콜렉터란 정리되지 않는 메모리, 유효하지 않는 메모리 주소로,
예를 들어 첫 초기화 이후에 값을 또 할당했을 때 값이 덮어씌워지는데, 그 전에 선언했던 값이라던지
선언은 해서 메모리는 가지고 있는데 사용이 되지 않은 값이라던지를 자바에서는 garbage라 부른다.
메모리가 부족할 때 가비지를 메모리에서 해제시키고 공간만들어주는 것이 GC의 역할.
크게 Mark와 Sweep 과정으로 나뉘는데,
Mark는 변수나 객체를 스캔하면서 어떤 객체 참조하는지 찾는 과정(=도달성(reacheable))이고(이때 스탑함),
Sweep는 Mark가 안된 객체를 힙에서 제거하는 과정이다.
GC가 Mark and Sweep를 거치며 가비지를 구분할 때 도달성(reachable)이라는 개념이 있는데,
객체에 유효한 레퍼런스가 있는지(=객체를 참조하는지)를 말한다.

2-2) Stop The World

GC가 실행되려면 JVM이 애플리케이션 실행을 잠시 멈춰야한다. 그걸 Stop The World라고 한다.
멈추는 시간을 줄이는 것을 GC 튜닝이라고 한다.

2-3)heap 영역의 구조

Young 영역에서 발생한 GC를 Minor GC, old/permanent 영역에서 발생한 GC를 Major GC(Full GC)라고 한다.

쉽게 말해 young은 새롭게 생성된 객체가 위치해서 대부분 생성되었다가 사라지는 곳.
old는 reachable 상태가 유지되서 살아남은 객체들 모음. GC가 적게 발생함.
perm은 메소드랑 비슷하다. 클래스와 메소드 정보가 들어있는 곳.


3) Stack 영역

스택은 지역변수나 메서드의 매개변수, 임시 사용된 변수, 메서드의 정보가 저장되는 영역.
지역변수나 매개변수는 메소드가 호출이 종료되면 그 안에 있는 변수는 사라진다.
즉, 금방선언되고 금방 사라지는 애들이 여기 있다가 없어진다.

즉, class a(변수명) = new 생성자명(생성자 안에 들어갈 데이터);


여기서 a는 스택에 저장된다. 생성자명(데이터)는 heap 영역에 저장된다.
왜? 원래 자바의 레퍼런스 타입(클래스,인터페이스,배열,상수)들은 구조가 복잡하고 용량이 크다.
이것들까지 스택에 보관해서 그때그때 뽑아쓰면 비효율적이므로 heap에다가 저장 후,
얘네들 주소를 참조하는 변수(a)를 스택에다가 저장해서 불러오는 것이다.


4) PC Register 영역

스레드가 어떤부분을 어떤 명령어로 수행할지 저장하는 공간.
스레드가 시작될 때 생성되며, 현재 수행되는 JVM 명령어 주소를 저장한다.

5) Native Method Stack 영역

자바를 제외하고 다른 언어,C언어나 C++언어가 실행되는 공간.
자바 프로그램이 컴파일 된 byte코드(.class)가 아니라 실제로 실행 가능한 기계어(0101)를 실행시키는 영역.

3. Runtime Data Area -> Execution Engine 영역 : 해석할 차례

로드된 클래스 파일의 byte코드를 실행하는 곳. 여기서 컴퓨터가 이해할 수 있는 기계어로 바꾼다.
방법은 두가지.

1. 인터프리터 : 명령어 한줄한줄 해석하면서 실행.

2. JIT(just-in-time) 컴파일러 : 한줄한줄말고 런타임에 전부 한번에 실행.
여기서 해석한 것을 다시 Runtime Data Area로 가져가서 배치하고 스레드가 동기화되거나 가비지 컬렉션에 들어간다.

4. JNI(Java Native Interface) 영역

추가로 JNI는 JDK의 일부분인데, 다른 언어로 쓰여져있는 애플리케이션이나 라이브러리가 자바 가상머신과 상호작용을 할 수 있게 도와준다.
 

▼ 더 자세하게 확인해보고 싶다면?

 

일반인에게 설명하는 [자바 프로그램의 실행 과정] - 2. 자바 프로그램 실행

그동안 소스코드를 작성하고 언어를 배우며 써왔지만 실제로 어떻게 동작하는지에 대해서는 등한시한 경향이 있었다. 일반인도 이해할 수 있게, 가장 많이 쓰이는 자바 프로그램이 어떻게 동작

heannim-world.tistory.com

반응형
LIST
반응형
SMALL

그동안 소스코드를 작성하고 언어를 배우며 써왔지만 실제로 어떻게 동작하는지에 대해서는 등한시한 경향이 있었다. 일반인도 이해할 수 있게, 가장 많이 쓰이는 자바 프로그램이 어떻게 동작하는지에 대해 정리를 해보고자 한다.

지난 글에서 프로그램이 어떻게 실행되는지 살펴보았다. 저번 글을 기반으로 자바 프로그램이 어떻게 되는지 확인해보자.

▼ 지난글을 확인해보고 싶다면?

 

일반인에게 설명하는 [자바 프로그램의 실행 과정] - 1. 프로그램 실행

그동안 소스코드를 작성하고 언어를 배우며 써왔지만 실제로 어떻게 동작하는지에 대해서는 등한시한 경향이 있었다. 일반인도 이해할 수 있게, 가장 많이 쓰이는 자바 프로그램이 어떻게 동작

heannim-world.tistory.com



[ 자바 프로그램은 어떻게 실행될까? ]

코딩 →  컴파일  런타임  실행


출처 : http://www.tcpschool.com/java/java_intro_programming

자바 프로그램의 실행과정은 다음과 같다.

자바(JAVA)는 대표적인 하이브리드 타입(Hybrid Type)언어로, 소스코드 전체를 중간코드(바이트코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행해준다. 바이트코드(byte Code)로 바꾸는 컴파일 타임 환경과 이를 한 줄씩 번역하면서 실행하는 런타임 환경으로 나뉜다.

하나씩 나눠서 살펴보자.

1. JAVA 컴파일 과정

자바가 컴파일 되는 과정을 살펴보자.

자바의 컴파일 과정

먼저 자바의 소스코드, 원시코드(*.java)는 CPU가 인식하지 못하므로 기계어로 컴파일해줘야 한다. 이 때, 바로 CPU에서 인식할 수 있는 기계어로 컴파일하는 것이 아니라 JVM이라는 가상머신을 거쳐서 가기 때문에 자바 컴파일러(Java compiler)는 중간언어인 자바 바이트코드(java bytecode(*.class))로 변환해준다. JVM에서 이 자바 바이트코드(java bytecode)를 해석해주기 때문에 OS와 관계없이 JVM만 설치되어 있다면 어느 디바이스든 Java 파일을 JVM 위에서 실행할 수 있다.

 

[ 실전 지식 ]

 ▶ Java compiler는 JDK를 설치하면 bin에 존재하는 javac.exe 파일이다.(JDK에 java compiler가 포함되어있다는 말임) javac 명령어를 통해 .java를 .class로 컴파일할 수 있다. 또한 JVM 역시 JDK 디렉토리의 bin 폴더 안에 존재하는 java.exe 파일로 java 명령어를 통해 JVM을 구동할 수 있다.

▶JDK는 Java로 소프트웨어를 개발할 수 있도록 여러 기능들을 제공하는 패키지(키트)이다. JDK는 프로그램을 생성, 실행, 컴파일할 수 있다.

▼ JDK 더보기

더보기

JDK 구성

  • apt : 어노테이션 툴
  • appletviewer : 웹브라우저 없이 자바 애플릿을 실행하고 디버깅하기 위한 툴
  • javac : 자바 컴파일러. 자바 소스파일을 바이트코드로 변환
  • java : javac가 만든 클래스 파일을 해석 및 실행
  • jar : 서로 관련있는 클래스 라이브러리들과 리소스를 하나의 파일로 묶어주는 툴
  • jdb : 자바 디버깅 툴
  • JRE(Java Runtime Enviroment) : 자바 런타임 환경으로 JVM + 자바 클래스 라이브러리(Java Class Library) 등 다양한 파일들을 포함한다. 컴파일 된 Java 프로그램을 실행하려면 JRE를 설치해야한다.
  • JVM(Java Virtual Machine) : Java가 실제로 동작하는 가상 환경. 이 JVM덕분에 하나의 Java프로젝트를 개발해도 여러 환경에서 원활하게 실행시킬 수 있다.

기타 등등...

JDK 종류

  1. Java SE : Java Platform , Standard Edition
    표준 자바 플랫폼으로 표준적인 컴퓨팅 환경을 지원하기 위한 자바 가상머신 규격 및 API 집합을 포함한다.
    JavaEE, JavaME는 구체적인 목적에 따라 자바 SE를 기반으로 API를 추가하거나 자바 가상머신 규격 및 API의 일부를 택하여 정의된다.
  2. Java EE : Java Platform , Enterprise Edition
    JavaSE에 웹 어플리케이션 서버에서 동작하는 기능을 추가한 플랫폼
    이 스펙에 따라 제품을 구현한 것을 웹 어플리케이션 서버(WAS)라 한다. ex. tomcat

WAS가 무엇인지 잘 모르겠다면 여기를 들어가보자!
WAS 와 웹서버의 차이점을 이해하기 쉽게 알려준다!
감사합니다 작성자님 ☺️

  1. Java ME : Java Platform , Micro Edition
    제한된 자원을 가진 휴대전화, PDA 등에서 Java 프로그래밍 언어를 지원하기 위해 만든 플랫폼 중 하나이다.

결론

  • JavaSE는 Java가 어떠한 문법적인 구성을 가졌는지와 같은 것들을 나타내는 명세표이다. JavaSE를 기반으로 특정 기능을 구현하기 위한 JavaEE, JavaME 플랫폼도 있다.
  • JDK는 JavaSE와 같은 규격을 토대로 만들어진 소프트웨어 패키지이다. 이는 Java를 개발 및 실행하는 데 필요한 툴들을 제공한다.
  • JRE가 Java를 실행하는데 필요한 바로 그 툴이다.

출처 :https://velog.io/@shelly/JAVA-JDK%EB%9E%80 

 

바이트코드와 바이너리 코드는 다르다. Byte Code JVM이 이해할 수 있는 언어로 변환된 자바 소스코드를 의미한다. 자바 컴파일러에 의해 변환된 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불리고 있다. 바이트 코드는 VM이 이해하는 코드이고, CPU가 이해할 수 있으려면 바이너리 코드(=0101 이진코드)로 변환해줘야한다. 모든 기계어는 0과 1로 이루어진 이진코드(바이너리 코드)로 이루어졌을 뿐이다. 바이너리 코드라는 알파벳으로 되어있을 뿐 CPU가 알아들으려면 이해할 수 있게 또다시 번역을 해줘야한다. JVM이 이제 일한다.

 

2. JAVA 런타임 과정

컴파일 과정을 거쳐 .java 파일은 중간 코드인 자바 바이트 코드(.class) 파일로 변환해줬다. 이제 런타임을 통해 자바 바이트 코드(.class)를 컴퓨터가 이해할 수 있는 기계어로 번역해주자.

자바가 런타임 되는 과정을 살펴보기 위해 JVM을 좀 더 까보자.

JVM 구성 요소

JVM에서는 어떤 일이 벌어질까?

1) 클래스 로더(Class Loader)

클래스 로더(Class Loader) JVM 내로 클래스 파일(*.class)를 동적으로 읽어서 메모리에 로드되어 JVM에 링크되게 한다. 컴파일된 .class 파일은 '로딩(Loading)', '링킹(Linking)', '초기화(Initializing)' 단계를 거쳐 JVM에서 사용할 수 있게 된다.

클래스 로더(Class Loader) 더 자세히 보기

더보기
클래스 로더 과정

Loading

우선 클래스 로더는 .class 파일을 읽고, 그 내용에 따라 적절한 바이너리 데이터를 만들고 메소드 영역에 저장하는 동작을 수행한다. 이 과정에서 .class 파일이 JVM 스펙에 맞는지 확인하고, Java Version을 확인한다.

Linking

  1. Verify
    읽은 클래스의 바이너리 데이터가 유효한 것인지 확인해야한다. .class 파일 형식이 유효한지 여러가지 체크를 한 다음 믿을 수 있는 .class 파일 데이터인 경우에 진행한다.
  2. Prepare
    클래스의 static 변수와 기본값에 필요한 메모리 공간을 준비한다.
  3. Resolution
    선택적으로 진행되는 과정으로 사용하는 환경에 따라 동작 유무가 정해진다. 이 과정에서 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 힙 메모리 영역에 있는 인스턴스에 대한 레퍼런스로 교체해준다. Constant Pool의 심볼릭 레퍼런스를 다이렉트 레퍼런스, 즉 실제 메모리 주소 값으로 변경해주는 작업을 한다.

Initializing

링크 단계의 Prepare 단계에서 확보한 메모리 영역에 클래스의 static 값들을 할당한다. 그리고 SuperClass 초기화와 해당 클래스의 초기화를 진행한다.

...일단 그렇다고 한다.

출처 :https://hbase.tistory.com/174

2) 실행 엔진(Execution)

실행 엔진(Execution) 클래스를 실행시킨다. 클래스 로더가 JVM 런타임 데이터 영역(Runtime Data Area)에 바이트 코드를 배치시키면 이것은 실행 엔진에 의해 실행된다. 실행엔진은 바이트코드를 기계어로 변경한다.

바이트 코드는 실시간 번역기(인터프리터) 또는 JIT 컴파일러에 의해 기계가 읽을 수 있는 바이너리 코드로 변환된다.

인터프리터(interpreter)는 한줄씩(명령어 단위로) 읽어서 번역 후 그때그때 실행한다.

만약 여러번 반복되는 소스라면 매번 한줄씩 번역하는건 불필요할 것이다. JIT 컴파일러(just-in-time compliation)는 이 때 사용되어 반복되는 소스를 한번에 변환해준다. 인터프리터 방식의 단점을 보완하기 위해 도입되었다.(VM 가상머신 안에 있음)

JIT 컴파일러(just-in-time compliation) 더 자세히 보기

더보기

JIT 컴파일러(just-in-time compliation)는 한번 바이트 코드 전체를 컴파일해서 기계어(바이너리코드)로 변환하고 나면 그 이후에는 다시 번역하지 않고 이미 번역해놓은 기계어를 직접 실행한다. 이 번역된(컴파일된) 기계어는 캐시에 보관한다. 그래서 한번 컴파일한 코드는 다시 수행하게 되면 빠르게 수행할 수 있게 되는 것이다. 당연히 한줄씩 번역하는것보다는 바이트 코드를 전부 컴파일하는게 느리기 때문에, JVM가 내부적으로 해당 메소드를 얼마나 자주 사용하는지 체크하고 있다가 적절한 시점에 바이트 코드 전체를 컴파일하는 JIT 컴파일러를 수행한다.

 

가비지 콜렉터(Garbage Collerctor) 더이상 사용되지 않는 인스턴스를 찾아 메모리에서 삭제한다. 불필요한 걸 청소해주는 청소부로 메모리를 관리해준다.

▼ 가비지 콜렉터(Garbage Collerctor) 더 자세히 보기

더보기
프로그램을 개발 하다 보면 유효하지 않은 메모리인 가바지(Garbage)가 발생하게 된다. 예를 들어, C언어를 이용하면 free()라는 함수를 통해 직접 메모리를 해제해주어야 한다. 하지만 Java나 Kotlin을 이용해 개발을 하다 보면 개발자가 메모리를 직접 해제해주는 일이 없다. 그 이유는 JVM의 가비지 컬렉터가 불필요한 메모리를 알아서 정리해주기 때문이다.

가비지 컬렉션은 영어로 Garbeage Collection으로 줄여서 GC라고도 부른다. 가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스를 말한다.

JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 완벽하게 관리하지 않아도 되어 오롯이 개발에만 집중할 수 있다는 장점이 있다.

단점은 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없고, 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생한다.

출처 :https://mangkyu.tistory.com/118
https://coding-factory.tistory.com/829 

3) 런타임 데이터 영역(Runtime Data Area)

런타임 데이터 영역(Runtime Data Area)  JVM이 프로그램을 수행하기 위해 OS에서 할당받은 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다. 한마디로 자바의 메모리 구조.

런타임 데이터 영역(Runtime Data Area)

▼ 내가 기초적인 컴퓨터 구조도 모른다면? 메모리도 잘 모르겠는데?

 

보통 WAS의 성능에 문제가 생겼을 때 런타임 데이터 영역(Runtime Data Area)이 원인이 된다.(메모리 누수나 GC 이슈)

PC Register  JVM stack   Native Method stack  Heap  Method Area

좌측 3개의 영역은 Thread별로 생성되고 우측 2개의 영역은 모든 Thread가 공유(=JVM이 시작할 때 단 하나만 생성)한다.

▼ 스레드(Thread)가 뭐여?

더보기

프로그램은 하드디스크 등의 보조기억장치에 저장된 실행코드를 뜻하고, 프로세스(process)프로그램을 구동하여 메모리 상에 실행되는 작업단위이다. 하나의 프로그램을 여러번 구동하면 여러개의 프로세스가 메모리(RAM) 상에서 실행되는 것. 이 프로세스(process)도 뜯어보면 데이터와 메모리 등의 자원, 그리고 스레드로 구성된다.

 

스레드(Thread)프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미한다. 프로세스 내부에 있는 CPU 수행 단위로, 모든 프로세스는 한 개 이상의 스레드가 존재하여 작업을 수행한다. 프로세스 내부에 여러개의 스레드가 구성된다면 자기들끼리 할당된 메모리와 자원을 공유한다.

프로세스와 같이 실행, 준비, 대기 등의 실행상태를 가지며, 실행상태가 변할때마다 스레드 문맥교환(context switching)을 수행한다. 각각의 스레드별로 자신만의 스택과 레지스터를 가지고 있다.

각각의 스레드별로 자신만의 스택과 레지스터를 가지고 있다.

CPU는 한개의 프로세스만을 실행할 수 있다. 하지만 멀티스레딩(Multi Thread)을 거친다면 하나의 프로세스 내에서 여러 스레드가 동시에 작업을 수행하는 것이 가능하다. 한 프로세스 내에서 프로세스 내의 주소공간이나 자원 공유가 가능하기 때문에 독립적인 작업 수행을 가능하게 하며, 아주 짧은 시간동안 여러 작업을 번갈아가면서 수행할 수 있게 해주니 동시에 여러 작업이 수행되는 것처럼 보인다. 이 손발을 스레드(Thread)가 가능하게 해준다.

 

JVM 단위에서의 런타임 데이터 영역

JVM단위는 JVM이 시작될 때 단 하나만 생성되며, 모든 스레드들이 공유한다.

Heap

힙 영역은 모든 클래스 인스턴스와 Array객체같이 긴 생명주기를 가진 데이터들이 저장되는 공간이다.
Heap의 메모리 해제는 오직 Garbage Collector 의해 수행된다.
또한 모든 스레드들이 공유하는 영역이라 Race Condition을 유발할 수 있다.

Method Area

메서드 영역은 클래스 로더에 의해 로드 된 클래스 정보를 맨 처음 메모리 공간에 올릴 때, 초기화 되는 대상을 저장하는 공간이다. 런타임 상수 풀 그리고 그림에는 안나와 있지만 필드, 메서드, 생성자, 데이터 등의 코드 내용으로 이루어져 있다.

Runtime Constant Pool

런타임 상수풀은 메서드 영역에 클래스가 정보가 로드될 때 생성된다.
런타임 상수 풀에는 런타임에 해석되는 메서드와 필드의 참조 등 여러 종류의 상수가 저장되어있다.

Thread 단위에서의 런타임 데이터 영역

PC Register

PC Register는 스레드가 생성될 때마다 생기는 공간으로 스레드가 현재 실행중인 명령을 저장하는 역할을 한다.

JVM Stack

JVM Stack은 JVM Frame을 저장하는 역할을 한다.
Stack의 메모리 크기는 고정크기와 가변크기가 있다.

JVM Frame

JVM Frame은 메소드가 호출될 때마다 생성되며, 메소드의 상태정보를 저장한다.
Frame읜 구성요소는 총 3가지로 다음과 같다.

  • Local Variables
  • Operand Stack
  • Constant Pool Reference

Local Variables

Local Variables의 크기는 컴파일 타임에 결정되며, 자바 바이트 코드에서 Code속성에 locals로 표현된다.

long, double은 2개의 슬롯을 차지하지만,
boolean, byte, char, short, int float, reference, retrunAddress는 1개의 슬롯을 차지한다.
메서드가 호출될 때 해당 메서드의 파라미터 값은 Local Variables를 통해 넘겨진다.

만약 호출되는 메서드가 클래스 메서드일 경우에는 메서드의 첫번째 파라미터는 Local Variables의 0번째 index에 저장되면 이후 차례대로 저장된다.

반대로 메서드가 인스턴스 메서드일 경우에는 this가 Local Variables의 0번째 index에 저장되고 이후 메서드의 파라미터가 차례대로 저장된다.

Operand Stack

Operand Stack의 크기 또한 Local Variables처럼 컴파일 타임에 결정되며, 자바 바이트 코드에서 Code속성에 stack으로 표현된다.

Operand Stack은 프레임이 생성될 당시에는 비어있으며, 메소드 내 계산 과정 등 모든 과정에서 이용된다.

Constant Pool Reference

Constant Pool Reference 영역은 해당 프레임과 대응되는 메소드가 속한 클래스 단위의 런타임 상수풀에 대한 참조를 의미한다.

Native Method Stack

Native Method Stack은 자바가 아닌 다른 언어로 작성된 네이티브 메서드를 지원하기 위한 스택이다.

 

▼ JVM 구조 요약

 

JVM 구조

JVM 구조 JVM : 자바가상머신. 자바 바이트코드를 실행시키는 주체. 운영체제 종류와 무관하게 가능 즉, 운영체제 위에서 동작하는 프로세스로 자바를 컴파일해서 얻은 바이트코드를 기계

heannim-world.tistory.com

 

반응형
LIST
반응형
SMALL

그동안 소스코드를 작성하고 언어를 배우며 써왔지만 실제로 어떻게 동작하는지에 대해서는 등한시한 경향이 있었다. 일반인도 이해할 수 있게, 가장 많이 쓰이는 자바 프로그램이 어떻게 동작하는지에 대해 정리를 해보고자 한다.

자바 역시 프로그램이다. 그러니 가장 기초적인 질문부터 시작해보자.


[ 프로그램은 어떻게 실행될까? ]

설계 →  코딩 번역 실행


1. 프로그램 설계

어떠한 문제를 해결하기 위해, 또는 어떤 기능을 제공하는 프로그램을 만든다고 하면 내부에 논리적 설계가 들어가게 된다. 즉, 어떤 순서와 논리와 구조로 이 기능이 구현되는지를 짜는데, 그것을 알고리즘이라고 한다. 

▼ 내가 컴퓨터 구조도 잘 모르겠다면?

 

컴퓨터 구조와 메모리(스택, 힙, 메모리 계층 구조)

아아주 기초적인 지식으로 컴퓨터에 대한 이야기부터 해보자. 컴퓨터는 여러 장치를 합쳐놓은 기계다. 본체 내부에도 여러 장치를 합쳐놓았고, 모니터, 키보드, 스피커, 마우스 등의 구성요소도

heannim-world.tistory.com

2. 프로그램 코딩

어떻게 만들지에 대한 논리적 설계를 짜면, 이걸 프로그래밍 언어로 각 언어에 맞게 프로그램을 작성한다. 프로그래밍 언어는 JAVA, Python, C++ 등이 있다. 이 때 프로그래머가 각 언어의 맞는 기능을 썼는지, 설계한대로 잘 만들고 있는지, 오타는 안냈는지 확인해야 한다.  오타 같은 구문에러(syntax error) 는 프로그래밍 언어가 안돌아가면서(실행실패) 오류 메시지를 발생시키기도 하지만, 논리적인 에러(logical error) 의 경우 구문 에러는 아니라서 돌아는 가는데 원하는 설계대로 동작이 안될 수 있다. (ex.아버지 가방에 들어가신다. -> 논리적 에러는 잘못된 동작을 하게 만들수도 있다.)

3. 프로그램 번역

여기서부터는 눈에 보이지 않는 동작이다. 에러 없이 원하는 설계대로 프로그램을 작성했다면, 프로그래밍 언어의 번역 프로그램이 자동으로 동작하기 때문이다. 프로그램 번역은 쉽게 말해서 우리가 쓴 말을 컴퓨터가 알아들을 수 있게 해석해주는 것이다. 컴퓨터는 무슨 말을 쓸까?

1) 기계어(Machine Language)

저급 기계어 (low-level machine language)라고 불리는 기계어(Machine Language)컴퓨터가 알아들을 수 있는 언어로 0과 1로 구성되어 있다. 컴퓨터는 0과 1로 이루어진 언어체계로 말을 알아듣는다. 0과 1로 이루어진 언어체계를 이진코드 또는 바이너리코드(binary code)라고 부른다. 컴퓨터는(=CPU는) 바이너리 코드(binary code) 로 만들어진 기계어(Machine Language)로 쓰인 명령어를 수행한다. 사람이 아무리 컴퓨터에 명령하려고 해도 알아들을 수 있어야 시키는대로 할테니까 말이다. (ex.미국인(=컴퓨터)은 알파벳(=바이너리 코드)으로 만들어진 영어(=기계어)를 쓴다)

2) 어셈블리어(Assembly Language)

우리는 어떻게 바이너리코드(binary code) 를 쓰는 컴퓨터와 소통할 수 있을까? 우리가 외국인과 대화할 수 있는 이유는 [안녕] 이라는 한글이 [Hello]와 같은 의미라는 것을 알기 때문이다. 이와 같이 0과 1로 되어있는 기계어의 숫자를 사람이 이해할 수 있는 언어와 매치하여 만든 언어를 어셈블리어(Assembly Language)라고 한다.


  
//x86 계열 CPU의 기계어 명령
10110000 01100001

위는 x86 계열 CPU의 기계어 명령이고, 이것을 어셈블리어로 옮겨쓰면 다음과 같다.


  
//어셈블리어로 변환
mov al, 061h

기계어보다는 익숙한 언어로 변경된다.
어셈블리어(Assembly Language)는 사람이 알아들을 수 있게 만들었으니 반대로 CPU가 이해하기 위해서는 다시 기계어로 바꿔줘야 한다. 어셈블리어를 기계어로 바꿔주는 프로그램을 er을 붙여서 어셈블러(assambler)라고 한다.


  
// 예시) NASM x86 어셈블리어로 구현한 헬로 월드 프로그램
adosseg
.model small
.stack 100h
.data
hello_message db 'Hello, World!',0dh,0ah,'$'
.code
main proc
mov ax, @data
mov ds, ax
mov ah, 9
mov dx, offset hello_message
int 21h
mov ax, 4C00h
int 21h
main endp
end main

3) 고급 프로그래밍 언어 (high-level programing language)

컴퓨터와의 소통은 이렇게 시작했다. 현재는 어셈블리어(Assembly Language)보다 더 발전된 언어 체계로 프로그래밍을 할 수 있다. 고급 프로그래밍 언어 (high-level programing language)사람이 이해하기 쉽게 작성된 언어 체계로, 저급 프로그래밍 언어보다 가독성이 높고 다루기 간단하다는 장점이 있다.  C, C++, C#, JAVA 등을 고급 프로그래밍 언어라고 한다.

※ 여기서 저급과 고급은 좋고 나쁨이 아니라 저급일수록 기계어 문법과 유사하고 고급일수록 사람의 언어와 유사한 것이다.


  
//고급 프로그래밍 언어 예제(java)
int num1 = 8, num2 = 4;
result = num1 + num2;
System.out.println("+ 연산자에 의한 결과 : "+ result);
// + 연산자에 의한 결과 : 12

어셈블리어보다 이해하기 쉽다. 그 이유는 사람이 쓰는 수학적 논리 구성과 인간의 언어로 구성되어있기 때문이다.

4) 빌드(Build)

빌드 과정

고급 프로그래밍 언어와 같은 각각의 언어 문법에 맞게 명령들을 기술한 파일들을 원시파일(source File)이라고 하며, 간단하게는 소스코드라고 부른다. 소스로 작성한 명령들을 CPU가 알아들을 수 있게 기계어로 번역하는 작업컴파일(Compile)이라고 부르고, 고급 프로그래밍 언어로 작성된 명령어를 기계어로 변경하는 프로그램 컴파일러(Compiler)라고 부른다.

컴파일(Compile)은 어떻게 이루어질까? 원시파일(source File)로 작성된 내용은 기계어 코드로 바뀌면서 그 결과를 목적파일(Object File)에 저장된다. 컴파일러는 원시파일을 해석해서 목적파일로 바꾸는 프로그램이다. 하지만 목적파일만 있다고 바로 실행할 수 있는건 아니다. 운영체제가 요구하는 코드가 추가로 있어야 실행할 수 있는 것이다.

목적파일을 실행가능파일(Executable File)로 바꾸기 위해서는 운영체제 요건에 맞게 형태가 바뀌고 StartUp이라는 실행코드가 있어야 한다. 이러한 처리를 링크(Link)라고 하며, 목적파일을 링크 처리해서 실행파일로 바꿔주는 프로그램을 링커(Linker)라고 한다.

우리가 만든 소스코드를 컴퓨터가 실행할 수 있도록 번역해주는 과정, 이 전 과정을 빌드(Build)라고 부른다.

최근 개발툴들은 그 자체에 소스 편집기, 컴파일러, 링커를 모두 내장하고 있어서 한번에 소스를 실행파일로 바꿀 수 있다. (하지만 아직 일부 리눅스, 유닉스 환경에서는 이 과정을 프로그래머가 거쳐야 실행파일로 만들 수 있다.) 여기에 디버거, 프로파일러, 개발편의 기능까지 제공하기 때문에 우리는 이러한 개발 툴들을 통합 개발 환경(IDE)라고 부른다. 컴파일러는 소스파일을 목적파일로 변환하는 프로그램을 의미하지만 요즘엔 개발 환경 자체를 컴파일러라고 부르기도 한다.

빌드(Build)는 소스를 실행파일로 바꾸는 과정이다. 그렇다면 빌드는 어떤 방식으로 진행될까?

[ 빌드 방식 종류 ]

Type Process
컴파일(Compile) 소스코드 전체를 기계어로 번역
인터프리터(InterPreted) 소스코드를 한 줄씩 번역하면서 실행
하이브리드(Hybrid) 소스코드 전체를 중간코드(바이트코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행

참고 : https://st-lab.tistory.com/176 (글이 너무 좋아서 직접 들어가서 읽어보는 것을 추천. 프로그래밍 빌드 과정 설명중에 가장 좋은 포스팅인듯.)

1. Compile Type : 통번역

컴파일 타입(Compile Type)소스코드 전체를 기계어로 한번에 번역해서 실행파일로 만들어준다. 대표적으로 C, C++, Go 언어가 있다.

컴파일러를 이용한 빌드 과정

언어별로 약간의 과정이 추가 또는 생략될 수 있지만 대부분 위와 같은 과정을 거치며 실행파일로 만든다.

전처리(preprocessing)main 소스가 시작되기 전에 소스코드에 포함된 매크로나 지시자 같은 것을 포함시켜준다. 예를 들어 C언어나 C++ 언어에서 #으로 시작하는 구문들(#include, #define 등)과 같은 것들을 처리해준다. 책을 번역하기 전에 저자 이름과 인용 등을 먼저 처리해서 나중에 일일히 찾아보지 않게끔 먼저 정리해준다고 생각하면 좋다.

컴파일(Compilation)은 원시파일(source File)로 작성된 내용을 기계어 코드로 바뀌면서 그 결과를 목적파일(Object File)에 저장하는 것으로 위에서 말했는데, 사실 원시파일 -> 어셈블리어(저수준언어) -> 목적파일로 가기도 한다. 대표적으로 C, C++ 언어의 경우 컴파일 과정에서 소스코드(=원시파일)을 저수준 언어로 번역하니 상황마다 어셈블(assemble)과정을 거치기도, 안거치기도 한다. (gcc, g++, Visual C++ 과 같은 컴파일러들은 저수준언어로 번역해준다) 

어셈블(assemble)저수준언어(어셈블리어)를 기계어로 번역하여 목적파일(Object File)로 번역해준다. 여기서는 목적이라고 하지만 '객체'로 생각하면 이해가 편하다. 컴퓨터도 내부에서 각 책의 챕터별로 번역하기 때문에 각각의 목적파일들이 생기는 것이라고 볼 수 있다.

링크(linking)는 컴파일과 어셈블을 통해 생긴 각각의 목적파일들을 하나로 연결해준다. 각 챕터를 엮어서 하나의 책으로 엮는 것.(위에서 말한 것처럼 운영체제 요건에 맞게 형태가 바뀌고 StartUp이라는 실행코드가 있어야 한다.)

이 과정을 한번에 단계를 밟아 처리하는게 컴파일 방식이다.

컴파일 타입(Compile Type) : 통번역
장점 빌드 완료한 실행가능한 파일은 실행속도가 빠르다.(다 끝냈으니까)
매번 번역할 필요없이 실행파일만 실행하면 된다.
단점 만약 프로그램을 수정하면 처음부터 다시 통번역해야한다.
플랫폼에 매우 의존적이다.
(이 말은 Mac과 Window와 64비트, 32비트 별로 설치파일이 다른 이유이기도 한데, CPU하나에 1:1로 매칭되는 어셈블리어이기 때문에 해당 플랫폼 전용 번역본이라 수정되거나 OS를 변경할 경우 다시 조건에 맞게 형태를 바꾸고 처음부터 다시 빌드해줘야 한다.)

 

2. Interpreted Type : 통역

인터프리트 타입(Interpreted Type) 소스코드를 한 줄씩 번역하면서 실행해준다. 대표적으로 자바스크립트, Ruby 언어가 있다.

목적파일을 만드는게 아니라 곧바로 번역해서 그때그때 실행한다

소스코드의 한 명령 세트마다 기계어로 번역하면서 바로바로 실행해주는 프로그램(또는 환경)인터프리터(Interpreter)라고 한다. 실행파일을 안만든다기보다는 소스코드 자체가 실행가능한 파일로 바뀌는 것. 얘는 각 OS에 맞는 번역가만 세우면 다 번역해주기 때문에 플랫폼에서 독립적이다.

인터프리트 타입(Interpreted Type) : 통역
장점 컴파일 과정 없이 바로 실행하므로 수정, 디버깅에 유리하다. 즉, 개발 속도가 빠르다.
(바꿔도 또 그 부분만 번역해서 실행하면 되기 때문.)
각 플랫폼에 지원하는 인터프리터만 있으면 실행이 가능하기 떄문에 플랫폼에 독립적이다.
단점 빌드가 되어있는 컴파일 언어 프로그램보다는 실행시간이 느리다. 컴파일 언어 프로그램은 이미 다 되어있는거 실행만 하면 되지만 인터프리트 프로그램은 번역 후 실행을 반복하기 때문.
(하지만 요즘이야 하드웨어 스펙이 높아져서 사람들이 체감할만큼 차이가 있지도 않다)
코드를 열면 내부가 다 보이기 때문에 보안에 좋지 않다.

 

3. Hybrid Type : 영어(Byte Code)로 1차 번역 후 통역

하이브리드 타입(Hybrid Type) 소스코드 전체를 중간코드(바이트코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행해준다. 대표적으로 JAVA 언어가 있다.

바이트코드로 컴파일 후 가상머신에서 한 명령여씩 번역해서 그때그때 실행한다.

프랑스어, 힌두어를 할 줄 아는 사람을 데려와서 그때그때 통역하는 것도 방법이지만 영어(=바이트코드)는 어디서든 대부분 쓰기 때문에 조금 번거롭더라도 중간 번역과정을 거쳐두면 그 다음부터 재사용하기 쉽고 다른 언어로도 번역하기 쉽다.

바이트 코드(Byte Code)일종의 중간 언어이다. VM(Vertual Machine : 가상머신)이 중요한데, VM이라는 프로그램이 바이트코드를 기계어로 바꿔준다. 이 VM만 각각의 OS에 맞게 깔려있다면 가상머신 안에 있는 인터프리터(해석기)가 바이트코드를 해석해서 실행한다.

가장 유명한 VM은 JVM(Java Virtual Machine)이다. VM이 해당 운영체제에 맞게 지원만 해준다면 플랫폼에 독립적으로 실행할 수 있다는 장점이 있다. 인터프리터 방식과 같은 원리인 것이다. (물론 VM이 인터프리트(=해석)만 하는 것이 아니라 최근에는 컴파일 방식과 혼용하여 구현되어 있다. 대표적으로 JIT(Just-in-Time) 이 있음.)

하이브리드 타입(Hybrid Type) : 1차 번역 후 통역
장점 각 플랫폼에 지원하는 가상머신(VM)이 있다면 실행가능하기 때문에 플랫폼에 독립적이다.
단점 컴파일 언어처럼 하드웨어를 직접 제어하는 작업은 불가능하다.

 뭐가 인터프리터 언어이고, 뭐가 하이브리드 언어인지는 중요하지 않다. 이런 방식들이 있다는 것이 중요하다. 각 언어는 서로의 것을 기반으로 섞이는 경우가 많기 때문에 이를 나눌 필요는 없다.

4. 프로그램 실행

번역을 마치고 실행가능한 파일을 컴퓨터가 실행시킨다. 처음 설계했던 도면대로, 그리고 그 내용으로 코딩한 내용대로, 컴퓨터가 차례로 실행한다.

▼ 2탄 보러가기

 

일반인에게 설명하는 [자바 프로그램의 실행 과정] - 2. 자바 프로그램 실행

그동안 소스코드를 작성하고 언어를 배우며 써왔지만 실제로 어떻게 동작하는지에 대해서는 등한시한 경향이 있었다. 일반인도 이해할 수 있게, 가장 많이 쓰이는 자바 프로그램이 어떻게 동작

heannim-world.tistory.com

 

반응형
LIST
반응형
SMALL

+ Recent posts

반응형
LIST