상세 컨텐츠

본문 제목

[JAVA] 메모리 관리

JAVA

by jeonghojin 2023. 4. 14. 23:30

본문

Java Virtual Machine(JVM)

C / C++에서는 OS 레벨의 메모리에 직접 접근하기 떄문에 free()라느 메소드를 호출하여 할당받았던 메모리를 명시적으로 해제해주어야 한다.

*그렇지 않으면 memory leak이 발생.

* 현재 실행중인 프로그램에서 memory leak이 발생하면 다른 프로그램에도 영향을 끼칠 수 있다.

 

반면, 자바의 경우 OS의 메모리 영역에 직접적을 접근하지 않고, JVM이라는 가상머신을 이용해서 간접적으로 접근한다.

*JVM (Java Virtual Machine) : C 로 쓰여진 또 다른 프로그램으로 오브젝트가 필요해지지 않는 시점에 알아서 free() 를 수행하여 메모리를 확보한다.

자바의 경우 C / C++ 처럼 메모리를 명시적으로 해제해주지 않아도 가상머신에서 알아서 처리해준다.

*gc(Garbage Collection)

* 프로그램 실행시, JVM 옵션을 주어서 OS에 요청한 사이즈 만큼의 메모리를 할당 받아서 실행하게 된다. 할당받은 이상의 메모리를 사용하게 되면 에러가 나고, 자동으로 프로그램이 종료된다. 그러므로 현재 프로세스에서 메모리 누수가 발생하더라도 현재 실행중인 프로세스만 죽게 된다. memory leak을 막아준다.


Java Stack과 Heap

1) Stack

  • 원시 타입의 데이터 값과 함꼐 할당 * 참조값 뿐만아니라 실제 값도 저장
  • Heap 영역에 생성된 Object 타입의 데이터 참조값 저장
  • 함수가 호출될 떄 사용하는 메모리로 기능 수행이 끝나면 자동으로 반환
  • LIFO(Last In First Out) 의 특성을 가지며, 스코프(Scope)의 범위를 벗어나면 스택 메모리에서 소멸 *Stack에서 pop()
  • Stack 메모리는 Thread 하나당 하나씩 할당 *스레드가 하나 생성될 경우, 스레드를 위한 Stack도 생성. 각 스레드에서 다른 스레드 영역에 접근할 수 없음. 

* 원시타입(Premitie Types) : bytes, short, int, long, double, float, boolean, char ...

* Scope : 중괄호* { , } 의 내부 범위 * 지역변수

 

 

2) Heap :

  • Object 타입의 데이터가 저장(String, Integer 등의 Object타입)
  • 애플리케이션의 모든 메모리 중 Stack을 제외한 부분
  • 자동으로 관리되지 않은 메모리 영역(CPU가 엄격하게 관리하지 않음)
  • GC(Garbage Collection)가 관리
  • Heap은 인스턴스 객체가 제거되기 전까지 스코프(Scope)와 상관없이 값이 유지된다. *Stack 메모리에 있는 값은 스코프를 벗어나면 값이 상실
  • 스레드에 상관없이 단 하나의 Heap 영역만 존재

 


Stack

public class Main {
    public static void main(String[] args) {
  
        int argument = 4;
        argument = method1(argument);
    }
    
    private static int method1(int param) {
        int temp = param * 3;
        int result = temp / 2;
        return result;
    }
}

최초에 stack 영역에 argument라는 변수명으로 공간이 할당되고, argument 변수의 타입이 원시타입이므로 stack영역에 4라는 값도 할당된다.

 

다음으로 method1 함수가 호출되고, 호출시 인자로 argument 변수를 넘겨주며 scope가 method1 함수로 이동한다.

scope가 함수 영역으로 이동하며, 기존의 argument 라는 값은 scope에서 벗어나므로 사용할 수 없다. argument값이 함수의 인자로 넘어가며 param에 복사되어 전달된다. param 또한 원시타입이므로, stack 영역에 값과 함께 할당된다.

 

함수영역 scope에서 result, temp 또한 원시타입으로 stack 영역에 값과 함꼐 할당된다.

 

함수가 종료되었을 경우, 함수 영역 scope에서 사용되었던 모든 지역변수(param, result, temp)은 stack에서 pop된다.

 

최초에 argument 변수는 4로 초기화 되었지만, 실행 결과 6이 반환되므로 argument 변수에 값이 재할당된다.

 

main 함수 종료시 stack에 있던 모든 데이터들도 pop되며 프로그램이 종료된다.

 


Heap

public class Main {
    public static void main(String[] args) {

        int exam1 = 10;
        String exam2 = "hello world";
    }
}

 

String 은 Object를 상속받아 구현되는 것이기 때문에 Heap 영역에 할당된다.

 

public static void main(String[] args) {

    String exam1 = "hello";
    exam1 = "hello java";
    exam1 = "hello java world";
}

 

String 은 불변객체(immutable)로써, 어떤 연산을 수행할 때마다 기존 오브젝트를 변경하는 것이 아니라, 새로운 오브젝트를 생성한다.

 

  • 최초에 exam1 String 변수에 hello 라는 값이 할당된다.
  • exam1에 hello java 라는 값이 새롭게 할당된다.
  • exam1에 hello java world 라는 값이 새롭게 할당된다.

 

해당 함수가 종료되면, 마지막에 새롭게 생성된 hello java world 오브젝트를 제외하고 hello, hello java 는 어느것도 참조하지 않는 상태가 되면서 garbage로 분류 된다.(Unreachable Object)

 

자바에서 Wrapper class에 해당하는 Integer, Character, Byte, Boolean, Long, Double, Float Short 클래스는 모두 Immutable이다. Heap에 있는 같은 레퍼런스하고 있는 경우라도, 새로운 연산이 적용되는 순간 새로운 오브젝트가 Heap에 새롭게 할당된다.

 

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");

        print(list);
    }

    private static void print(List<String> param) {
        String value = param.get(0);
        param.add("world");
        System.out.println(param);
    }
}

 

 

new 키워드는 Heap영역에 생성하려는 오브젝트를 저장할 수 있는 충분한 공간이 있는지 찾은 다음, 빈 List를 참조하는 list라는 로컬 변수를 스택에 할당한다.

 

list.add("hello") 는 new 키워드에 의해 heap 영역에 충분한 공간이 있는지 확인한 후, "hello"라는 문자열을 할당하게 된다.

List의 내부 인덱스에 의해 하나씩 add()된 데이터에 대한 참조값을 갖게 된다.

* "hello"는 new String("hello")와 같은 역할을 한다.

* 새롭게 생성된 문자열인 hello 을 위한 변수는 stack에 할당되지 않는다.

 

print 함수 호출이 되면, list 변수를 인자로 보내고, param 이라는 참조 변수가 stack 영역에 새롭게 할당되고, 기존에 list 변수가 참조하고 있던 heap 영역의 값을 param이라는 변수가 참조하게 된다.

*list 변수가 가지고 있던 값을 param이 가지게 됨.

*scope가 바뀜.

 

다음으로 함수 내부에서 value 라는 참보변수를 stack 영역에 할당하고, List에 있는 데이터에 접근하여 0번째 요소에 접근하여 그 참조값을 갖게 된다. 그리고 데이터를 새로 추가하고 출력하게 된다.

 

print 함수가 종료되면 모든 지역변수는 stack에서 pop되어 사라지고, heap 영역에 있는 값들은 그대로 존재하게 된다.

그리고 scope는 다시 변경되고, list가 참조하게 된다.

 

 


참고:

더보기

 

'JAVA' 카테고리의 다른 글

[JAVA] 제네릭(Generic)  (0) 2023.05.01
[JAVA] GC : Garbage Collection  (0) 2023.04.16
[Design Pattern] Facade Pattern  (0) 2023.04.06
[Design Pattern] Proxy Pattern : Remote Proxy  (0) 2023.04.02
[Design Pattern] Proxy Pattern : Protection Pattern  (0) 2023.04.01

관련글 더보기