[20240110 TIL] 팀스터디 - JAVA의 메모리 영역

Haizel·2024년 1월 10일
2

01. Intro


✔️ JVM (Java Virtual Machine)

JVM(Java Virtual Machine, 자바 가상 머신)은 Java의 Bytecode를 해석하고 실행하는 역할을 한다.

  • JVM만 설치하면 운영체제(OS)에 관계 없이 java파일을 실행시킬 수 있다.


✔️ Java의 변수 종류

변수는 크게 네 종류로, 변수의 선언된 위치에 따라 클래스변수, 인스턴스변수, 지역변수, 매개변수로 나뉜다.

public class Variable {
	public static int age = 20; // 클래스 변수 (전역 변수)
	
	int height = 60; // 인스턴스 변수 (전역 변수)
	
	public static void main(String[] args) { // 매개변수 (파라미터)
		int size = 50; // 지역 변수
	}}


02. Java의 메모리 영역


✔️ Static (Method) 영역

클래스 변수(Static 변수), 생성자(Constructor), 메소드(Method), Class의 정보 등을 저장하는 공간

  • JVM이 동작하여 Class가 로딩될 때 생성된다.
  • Method(Static) 영역에 있는 것은 어느곳에서나 접근 가능
  • Method(Static) 영역의 데이터는 JVM이 종료 시(프로그램이 종료 시) 메모리에서 해제 된다.
    • 즉 프로그램이 종료되기 전까진, 해당 영역의 데이터는 어디서든 사용 가능하다.
    • 하지만, 무분별하게 사용할 경우 메모리 부족 현상이 발생할 수 있다.

✔️ Stack 영역

메소드 내에서 정의하는 기본 자료형(primitive Type)에 해당하는 지역변수의 데이터 값이 저장되는 공간
• int, double, byte, long, boolean 등

  • 메소드가 호출될 때 스택 영역에 스택 프레임이 생기고, 그 안에 메소드를 호출
  • 메소드가 호출될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
  • Stack 은 후입선출 LIFO(Last-In-First-Out) 의 특성을 가지며, 스코프(Scope) 의 범위를 벗어나면 스택 메모리에서 사라진다.

스택 프레임(stack frame)

: 하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임(Stack Frame)이라고 한다.

  • 하나의 메서드 당 하나의 스택 프레임이 필요하며, 메서드를 호출하기 직전 스택프레임을 자바 Stack 영역에 생성한 후 메서드를 호출하게 된다.
  • 스택 프레임에 쌓이는 데이터는 메서드의 매개변수, 지역 변수, 리턴값 등이 있다.

✔️ Heap 영역

인스턴스(객체), 배열 등 참조형(Reference Type) 데이터 객체의 실제 데이터가 저장되는 공간이다.
• String, array, enum, class, interface, Object 등


new 키워드로 인스턴스를 생성할 때 Heap 영역에는 생성된 객체가 저장되고, Stack 영역에는 생성된 객체에 대한 주소 값(Reference)이 저장된다.
Heap 영역에 있는 데이터를 가리키는 레퍼런스 변수는 Stack에 저장된다.

  • JVM이 관리하는 프로그램에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역
  • Heap 영역에 보관된 메모리는 호출이 끝나더라도 삭제되지 않고 유지된다.
    • 그러다 어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게 되면 GC(가비지 컬렉터)에 의해 메모리에서 청소된다.
  • Stack은 스레드 개수마다 각각 생성되지만, Heap은 스래드에 관계 없이 단 하나의 Heap 영역만 존재

03. 코드로 보기


예제 코드

```java
public class Main {
   public static int s = 10;

   public static void main(String[] args) {
      int a = 5;
      int b = 5;
      int result1 = a + b + Main.s;
      System.out.println(result1); // 20

      Counter sub = new Counter();
      twice(sub);
      int result2 = sub.get();
      System.out.println(result2); // 100
   }

   public static void twice(Counter c) {
       c.plus(10);
       c.plus(20);
   }
}

class Counter {
   public int state = 50;
   public final int count = 20;

   public int get() {
      return state + count;
   }

   public void plus(int n) {
       state += n;
   }
}
```

Method 영역 적재

클래스 변수(static)와 메소드(method)는 무조건 Method 영역에 적재된다.

💡 이때 일반 인스턴스 변수인 Counter 클래스의 변수 statecount는 final 키워드가 붙었지만, 클래스 변수(static)나 메소드(method)가 아니므로 메서드 영역에 들어가지 않는다.

② Stack 영역 적재

Class Main의 메인 메서드(public static void main(String[] agrs)가 실행되면 스택 영역에 스택 프레임이 쌓이고 지역 변수와 매개 변수가 담긴다.

③ 생성자 호출 - Heap 영역 적재

생성자 new Counter()를 호출하면 Heap 영역에 Counter 클래스 인스턴스 변수들이 저장된다.

그리고 Stack 영역의 지역변수 sub에 주소값으로 연결되게 된다.

④ 새 메소드 실행 - Stack 영역 적재

twice(sub) 메소드를 실행하면 Stack 영역에 새로운 스택 프레임이 쌓인다.

Arguments로 클래스를 전달했기 때문에 twice()의 매개 변수 c는 주소값으로 같은 힙 영역을 가리키게 된다.

⑤ 객체의 메소드 실행 → state 값 변경

  • 객체 Counter에 정의된 plus() 메소드를 호출해 실행하면 → 메소드이기 때문에 Stack 영역에 새로운 스택 프레임이 생성된다.
  • 다만 여기서 this 라는 암묵적인 변수가 자동 생성되게 되는데, 이 this 변수는 자동으로 Heap 영역에 있는 Counter 객체를 가리키게 된다.
    • 따라서 plus() 메소드 안의 코드 state += n 가 동작하면서 Heap 영역에 있는 인스턴스 변수 state 의 값이 변하게 된다.

⑥ 메서드 실행 → 지역 변수 추가

❶ 실행 후 종료된 plus()스택 프레임은 Stack 영역에서 제거된다.

❷ 그 후 sub 객체 변수의 메소드인 get() 을 호출하면 Stack 영역에 새로운 스택 프레임이 생기고, this 변수가 Heap 영역의 객체를 가리키게 된다. 그리고 Heap 영역의 변수를 반환한다.

❸ 실행이 종료된 get() 스택 프레임이 Stack 영역에서 제거되고, main 스택 프레임에 result2 지역 변수가 추가된다.

💡 TIP!


호출되는 메서드가 파라미터로 객체값을 전달받아 객체의 상태를 변경하게 되면, 메서드 종료(스택 제거) 이후에도 Heap 영역에 있는 객체의 상태는 쭉 유지된다.

⑦ 모든 Stack 프레임 제거

Stack 영역은 메서드의 끝을 알려주는 닫는 중괄호( } )를 만나면 자동으로 메모리에서 제거된다.

❗️그러나 Heap 영역에는 여전히 객체 데이터가 메모리에 남아있다.

⑧ 코드 실행 종료 + 가비지 컬렉터(GC)

코드 실행이 모두 끝나면 Method(Static)영역이 비워진다.

❗️ 가비지 컬렉터(GC)는 Heap 영역에 남아있는 더 이상 참조되지 않는 객체를 식별해 청소하는 역할을 한다.

04. Heap과 Stack 메모리의 차이점


변수 name은 Stack 영역에 저장되며, 해당 변수의 주소는 Heap 영역에 저장되어 ‘Mark’를 가리킨다.


  1. 힙 메모리는 애플리케이션의 모든 부분에서 사용되며, 반면에 스택 메모리는 하나의 스레드가 실행될 때 사용

    → 따라서 힙 과 메서드 공간에 저장된 객체는 어디서든지 접근이 가능하지만, 스택 메모리는 다른 스레드가 접근할 수 없다.

  2. 언제든지 객체가 생성되면 항상 힙 공간에 저장되며, 스택 메모리는 힙 공간에 있는 객체를 참조만 한다.

    → 즉, 스택 메모리는 primitive 타입의 지역변수와 힙 공간에 있는 객체 참조 변수만 갖고 있다.

  3. 스택메모리의 생명주기는 매우 짧으며, 힙 메모리는 애플리케이션의 시작부터 끝까지 살아남는다.

  4. 자바 코드를 실행할때 따로 -Xms-Xmx 옵션을 사용하면 힙 메모리의 초기 사이즈와 최대 사이즈를 조절할 수 있다.

  5. 스택 메모리가 가득차면 자바에서는 java.lang.StackOverFlowError를 발생하고, 힙 메모리가 가득차면 java.lang.OutOfMemoryError : Java Heap Space에러를 발생

  6. 스택 메모리 사이즈는 힙 메모리와 비교했을 때 매우 적다. 하지만 스택 메모리는 간단한 메모리 할당 방법(LIFO)를 사용하므로 힙 메모리보다 빠르다.

profile
한입 크기로 베어먹는 개발지식 🍰

0개의 댓글

관련 채용 정보