자바와 절차적/구조적 프로그래밍

uncle.ra·2023년 10월 15일
post-thumbnail

📗 들어가기 전에

이번에 스프링 입문을 위한 자바 객체 지향의 원리와 이해 책을 주제로 스터디를 시작했다.

매주 금요일에 랜덤으로 발표할 인원을 선정해서 발표하고 따로 책에 대한 내용을 서로 논의 하는 스터디이다.

이번 주는 운이 좋게도 내가 발표를 진행하게 되었다.😁
이번 주에는 Chapter02. 자바와 절차적/구조적 프로그래밍을 주제로 스터디를 했다.

📗 이 장에서 알아야할 부분

이 장에서 알아야할 내용을 나는 2가지라고 생각했다.

  1. JDK/JRE/JVM은 무엇일까?
  2. main() method를 실행하면 내부적으로 프로그램은 어떻게 동작할까?

차근차근 살펴보자.

📗 1. JDK/JRE/JVM은 무엇일까?

책에서는 아래와 같이 JDK/JRE/JVM을 소개한다.

현실 세계에서 소프트웨어, 즉 프로그램은 개발자가 개발 도구를 이용해 개발하고 운영체제를 통해 물리적 컴퓨터인 하드웨어 상에서 구동된다.
자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상의 컴퓨터인 JVM상에서 구동된다.

용어 부터 정리해보면,

  • JDK(Java Development Kit)란, 자바 개발 도구를 의미한다.
  • JRE(Java Runtime Environment)란, 자바 실행 환경을 의미한다.
  • JVM(Java Virtual Machine)란, 자바 가상 기계를 의미한다.

사실 이렇게만 봤을 때 전체적으로 감이 제대로 잡히지 않아서 조금 더 알아 봤다.

JVM/JRE/JDK

오라클 JDK17로 검색한 후에 여기에서 다운로드를 한다.

oracle-jdk-17
(필자는 M1 MacOS를 사용하기 때문에 위의 링크를 활용해서 JDK 17을 설치했다.)

다운로드를 한 이후에 JDK 17을 설치한 경로로 이동해서 열어보자.

cd /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin

jdk-17.jdk/Contents/Home/bin

위의 이미지에서 보는 것과 같이 여러 실행 파일들을 볼 수가 있다.
상단의 JDK/JRE/JVM 구조를 나타낸 그림에서 javac.exe, javadoc.exe, javap.exe를 빨간색 네모에서 확인할 수 있다.

JDK는 어떤 개발 도구들을 지원해 주는지 그 중에 3가지만 확인해보자!

  • javac.exe: Java Compiler를 실행하는 실행 파일이다. 우리가 Java언어로 작성한 소스 코드를 JVM이 읽을 수 있는 바이트 코드로 변환하는 역할을 수행한다.
javac MyFile.java
  • javadoc.exe: Java 언어로 작성된 소스 코드에 기반해서 Api문서를 생성해주는 실행 파일이다. javadoc.exe는 주석을 활용해서 HTML 형식의 Api문서를 생성하는 역할을 수행한다.
javadoc MyFile.java
  • javap.exe: javac 실행 파일을 통해 생성된 바이트 코드를 역어셈블(Disassemble)해서 보여주는 역할을 수행하는 실행 파일이다. 즉, MyFile.java 파일을 javac 실행 파일을 통해 Compile을 하면 MyFile.class 파일이 바이트 코드로 변환해서 생성해주는데 해당 파일을 읽기 쉬운 형태로 변환해서 보여주는데 javap.exe 실행파일이 사용된다.
javap MyFile

또, 파란색 네모로 표시한 java.exe는 자바 프로그램 실행기를 의미한다.
자바 실행 환경인 JRE에 포함되어 있다.

이 실행 파일은 Java 바이트 코드를 해석하고 JVM에서 해당 코드를 실행하는 역할을 수행한다.

java MyFile

위의 폴더 구조에서는 따로 JRE에 속해있는지 여부가 눈에 들어오지 않게 한 폴더 내에 존재한다. 하지만 JDK8에서는 확인이 가능하다.

cd /Library/Java/JavaVirtualMachines 

해당 경로에 JDK8을 다운받은 적이 있어서 들어가서 확인해 보면,

jdk-8.jdk/Contents/Home/bin

JRE 내부에 java.exe 실행 파일이 존재하는 것을 확인할 수 있다.

JVM은 java.exe를 통해 Java 애플리케이션을 실행하는 과정에서 내부적으로 작동한다. java.exe를 실행하면 JVM이 시작되어 Java 애플리케이션을 실행하게 된다.

위에 내용을 통해서 잠시 생각해보자.
일반적인 사용자가 JDK를 설치할 필요가 있을까?

...

답은 X이다!

JDK는 JRE를 포함해서 개발자들이 사용할 수 있는 여러 개발 도구를 지원해준다. 하지만 자바 실행 환경인 JRE만 설치하면 Java 애플리케이션을 실행 시킬 수 있기 때문에 JDK설치 까지는 불필요하다.😁

📗 2. main() method를 실행하면 내부적으로 프로그램은 어떻게 동작할까?

알아보기 전에 메모리 구조에 대해서 알아보자.

프로그램이 메모리를 사용할 때 우선 크게 2가지 영역으로 나뉜다.

  • 코드 실행 영역
  • 데이터 저장 영역

코드 실행 영역은 이름 그대로 코드를 실행하는 영역이다.
내부적으로 어떻게 동작하는지에 주요 관심사는 데이터 저장 영역이기 때문에 이 부분에 포커싱을 두고 살펴보자.

데이터 저장 영역은 다시 세 개의 영역으로 나뉜다.

  • 스태틱(Static) 영역 - 클래스들의 놀이터
  • 스택(Stack) 영역 - 메서드들의 놀이터
  • 힙(Heap) 영역 - 객체들의 놀이터

그림으로 표현해보면 아래와 같다.

data-area

이제 실행이 어떻게 되는지 차근차근 봐보자🏃🏻


public class Start {
	public static void main(String[] args) {
    	System.out.println("hello world");
    }
}

main() method를 실행시키면 내부적으로 어떻게 동작할까?

✅ 1. JRE(Java Runtime Environment)는 먼저 프로그램 안에 main() method가 있는지 여부를 확인한다. JRE는 Start Class에서 main() method를 발견하게 된다.

✅ 2. 존재 여부를 확인되면 JRE는 프로그램을 실행하기 위한 사전 준비를 시작한다. JVM에 전원을 넣어 부팅한다.

✅ 3. 부팅된 JVM은 프로그램 실행 전 전처리 과정을 수행한다.

3-1) 가장 먼저 수행하는 것은 java.lang package를 static 영역에 배치한다.
(import없이 개발자들은 System.out.println()을 사용할 수 있었던 이유)

data-area1

3-2) java.lang package를 배치하고 나서 main() method가 존재하는 start.class 파일을 static 영역에 배치한다.

data-area2

🤔 JVM 전처리 과정에서 모든 클래스 파일을 Static 영역에 배치할까?

다음으로 JVM은 개발자가 작성한 모든 클래스와 임포트 패키지 역시 스태틱 영역에 가져다 놓는다.
스프링 입문을 위한 자바 객체 지향의 원리와 이해, 43p에서 발췌

책에서는 모든 java.lang 패키지를 가져온 뒤에 모든 클래스를 스태틱 영역에 가져다 놓는다고 얘기한다.
클래스 로더에 대해서 공부했을 때, JVM은 클래스 파일을 필요할 때 동적으로 로딩한다고 이해했기 때문에 의문을 갖게 되었다.

아직 확실한 답을 구하지 못해서 따로 현재는 이렇게 이해하기로 했다.🥲
1. java.lang package를 static 영역에 배치한다.
2. main() method를 포함하는 class와 해당 class에 적혀있는 import package들을 static 영역에 배치한다.

✅ 4. method의 중괄호 '{'를 만날때마다 스택 프레임이 스택 영역에 할당된다.

main() method의 중괄호 '{'를 만나면서 아래와 같이 Main Stack Frame이 스택 영역에 생성 된다.

data-area3

✅ 5. 메모리 구조에서 변화없이 "hello world"가 console에 출력된다.

✅ 6. Main() method의 닫는 중괄호인 '}'을 만나면서 Stack 영역애서 Main Stack Frame이 소멸된다.

이미지는 아래와 같다.

data-area2

✅ 7. main() method가 끝나면 JRE는 JVM을 종료하고 JRE 자체도 메모리에서 사라진다.

조금 더 확장된 예제로 한 번 더 실행과정을 봐보자.🏃🏻

package com.example.flagstudy;

public class FactorialTest {
	public static void main(String[] args) {
		System.out.println("answer: " + factorial(2));
	}
	private static int factorial(int num) {
		if(num < 1) {
			return 1;
		}

		return factorial(num - 1) * num;
	}
}

Stack Frame에 대해서 더 이해를 해볼 수 있도록 Factorial 예제를 가져와 봤다.

Factorial은 아래와 같이 이해하면 좋다.

4! = 4 x 3 x 2 x 1 = 24
3! = 3 x 2 x 1 => 6
2! = 2 x 1 => 2

이번 예제는 2!을 재귀적으로 가져오는 예제이다.

✅ 1. JRE(Java Runtime Environment)는 먼저 프로그램 안에 main() method가 있는지 여부를 확인한다. JRE는 Start Class에서 main() method를 발견하게 된다.

✅ 2. 존재 여부를 확인되면 JRE는 프로그램을 실행하기 위한 사전 준비를 시작한다. JVM에 전원을 넣어 부팅한다.

✅ 3. 부팅된 JVM은 프로그램 실행 전 전처리 과정을 수행한다.

자세한 JVM의 전처리 과정은 위에 hello world예제를 참고하자.

따라서 아래와 같은 메모리 구조를 갖게 된다.

data-area4

✅ 4. method의 중괄호 '{'를 만날때마다 스택 프레임이 스택 영역에 할당된다.

4-1) main(String[] args) method의 '{'를 만나면서 스택 영역에 Main Stack Frame이 생성된다.

data-area5

4-2) factorial(2) method를 호출하고 factorial(int num)의 '{' 중괄호를 만나면서 스택 영역에 Factorial Stack Frame이 생성된다.

data-area6

이 때 반환값이 Local Variables의 0번째 인덱스에 할당되고, 그 다음에 파라미터인 num값이 1번째 인덱스에 할당되었다.

4-3) if문의 조건인 num < 1이 false로 되면서 아래의 factorial(2 - 1) method를 호출하게 된다. 그림은 아래와 같다.

data-area7

4-4) 다시 if문의 조건인 num < 1이 1 < 1로 false가 되면서 아래의 factorial(1 - 1) method를 호출하게 된다. 그림은 아래와 같다.

data-area8

4-5) if문의 조건인 num < 1이 0 < 1로 true가 되면서 if문의 '{'여는 중괄호를 만나면서 중첩 Stack Frame이 생성된다.

data-area9

4-6) return 값인 1을 반환하면서 중첩 Stack Frame과 가장 상위의 Factorial Stack Frame이 소멸된다.

data-area10

현재 위치의 Stack Frame의 num값인 1과 직전에 소멸된 Stack Frame의 반환값인 1을 곱한 값이 return 값에 할당된다.

4-7) 현재 3번쨰 Stack Frame이 1을 2번째 Stack Frame에 반환한다. 이후에 닫는 중괄호인 '}'를 만나면서 소멸되고,

data-area11

4-8) 2번째 Stack Frame에서도 return 값이 2*1을 할당 받는다. 이후에 닫는 중괄호 '}'를 만나면서 소멸된다. 그리고 return값인 2를 Main Stack Frame에 반환한다.

data-area12

4-9) Main Stack Frame에서 answer: 2를 console에 출력한다.

✅ 5. main() method의 닫는 중괄호인 '}'을 만나면서 Stack 영역애서 Main Stack Frame이 소멸된다.

✅ 6. main() method가 끝나면 JRE는 JVM을 종료하고 JRE 자체도 메모리에서 사라진다.

📗 마치며

  • 이번 스터디를 통해서 JDK/JRE/JVM에 대해서 이해할 수 있었고, main() method를 실행했을 때 대략적으로 어떻게 실행되는지 그리고 메모리에 어떻게 할당되는지 알 수 있었다.

  • 스터디를 같이 진행하는 동료분이 해당 내용을 블로그에 정리하면 좋겠다는 피드백을 주셨는데, 덕분에 이렇게 글을 쓸 수 있게 되어서 감사한 마음이 크다.🫠

📗 참고

0개의 댓글