일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 참조 타입
- 이펙티브 자바
- @Configuration
- @Bean
- 스프링 빈
- Spring Batch
- @FunctionalInterface
- @componentScan
- 가변 객체
- OSIV
- Open EntityManager In View
- Dispatcher Servlet
- Request flow
- spring boot
- 생성자 주입
- 싱글 스레드
- 컴포넌트스캔
- open session in view
- Handler Adepter
- Spring Framework
- 일괄처리
- mavenCentral
- 불변 객체
- Batch
- open-in-view
- 익명 함수
- 메서드 주입
- 빈
- 필드 주입
- View Resolver
- Today
- Total
보다 더 나은 내일의 나를 위해
JAVA의 메모리에 있는 스택과 힙 본문
📎부제. 자바의 변수 관리
개요
자바가 실행되는 환경인 JVM(Java Virtual Machine)에서는 당연하게도 메모리 공간이 있습니다. 이 메모리 공간에 오늘 알아볼 스택과 힙이 존재합니다. 먼저 스택과 메모리에 대해 간단히 알아봅시다.
- 스택
- 모든 스레드에 각각 존재한다.
- 원시 타입의 변수들이 값과 함께 저장된다.
- 참조 타입의 변수일 경우 힙에 저장된 데이터의 주소 값을 저장하게 된다.
- 변수의 스코프가 끝나면 소멸한다.
- 힙
- 모든 스레드가 힙을 공유한다.
- 참조 타입 데이터가 저장된다.
- GC(Garbage Collector)에 의해서 값이 소멸한다.
스택과 힙은 다음과 같은 특징을 가지고 있습니다. 더 자세히 알아봅시다.
원시 타입 관리
우선 자바의 원시 타입에는 int, flaot, long, double, short, byte, char, boolean이 있습니다. 만약 변수가 이 원시 타입이라면 자바는 이 변수와 변수의 값을 스택 영역에 저장하게 됩니다.
class Example {
public static void main(String[] args) {
int num = 1;
System.out.println(num);
num = 2;
System.out.println(num);
numToThree(num);
System.out.println(num);
}
public static void numToThree(int number) {
number = 3;
}
}
// 출력 결과
// 1
// 2
// 2
위 코드에서 마지막 부분에 분명 num을 numToThree함수에서 3으로 바꿨지만, 출력은 2가 나오는지 아시겠나요?
위와 같은 코드에서 자바는 다음과 같이 변수를 관리합니다.
처음 num변수를 선언하면 원시 타입이기 때문에 스택 영역에 저장됩니다.
그다음 num을 2로 수정하였으므로 스택에 저장되어있는 num의 값을 2로 수정해줍니다.
그다음에는 numToThree 함수에 num을 파라미터로 넘겨 3으로 수정합니다. 하지만 이때 numToThree 함수에서 사용하는 number 변수는 스택에 새로이 저장합니다.
number 변수에 2라는 값을 받아와 3으로 수정한 뒤 numToThree 함수는 종료하게 됩니다. 그러면 여기서 사용한 number 변수는 스택 영역에서 삭제가 되고, 결국 num 변수는 그대로 2이기 때문에 마지막 출력에서도 2를 출력하게 됩니다.
참조 타입 관리
자바의 참조 타입은 원시 타입을 제외한 타입들을 말합니다. 예를 들면 문자열, 배열, 클래스 등이 있습니다. 변수가 이 참조 타입이라면 자바는 실제 객체(데이터)는 힙 영역에 저장하고, 변수는 스택 영역에 저장해 데이터의 주소를 참조합니다.
class Example {
public static void main(String[] args) {
String name = "chan";
}
}
예를 들어 위와 같은 코드가 있다면 자바는 다음 그림과 같이 변수를 저장하게 됩니다.
String은 참조 타입이므로 변수인 name은 스택에 저장, String의 실제 정보인 chan은 힙에 저장해 name변수는 chan의 주소를 참조해 값을 가져옵니다.
그렇다면 다음 코드에서는 어떻게 작동할까요?
class Example {
public static void main(String[] args) {
List<String> animal = new ArrayList<>();
animal.add("dog ");
// 출력 2줄
animal.forEach(System.out::print);
System.out.println();
addCat(animal);
// 출력 2줄
animal.forEach(System.out::print);
System.out.println();
animal.add("rabbit");
// 출력
animal.forEach(System.out::print);
}
public static void addCat(List animal) {
animal.add("cat ");
}
}
// 출력 결과
// dog
// dog cat
// dog cat rabbit
위 코드에서는 addCat함수에서 "cat "을 넣어줬습니다. 이때 원시 타입에서 값이 안 바뀌었던 것과는 달리 "cat "이 잘 추가되었습니다.
이번엔 그림과 함께 봅시다. 이때 스택 영역은 main(), addCat() 둘 모두에 존재하지만 편의상 하나로 표현합니다.
우선 animal이란 List변수는 스택 영역에 저장됩니다. 이 변수는 힙에 저장되어있는 List의 주소 값을 참조합니다.
그 후 addCat 함수에 animal 변수의 List를 파라미터로 넘겨줍니다. 그럼 addCat의 animal 변수도 힙에 존재하는 List의 주소를 참조해 그 List에 "cat "을 추가하게 됩니다. 따라서 main 함수의 animal 변수에서도 cat 이 추가된 것을 볼 수 있습니다.
그런 다음 addCat 함수가 종료되면 스택의 animal 변수는 사라지게 됩니다.
마지막으로 List에 "rabbit"를 추가해줍니다.
이번엔 다른 예제를 살펴봅시다.
class Example {
public static void main(String[] args) {
String animal = "bird ";
System.out.println(animal);
addName(animal);
System.out.println(animal);
animal += "horse";
System.out.println(animal);
}
public static void addName(String animal) {
animal += "tiger ";
}
}
// 출력 결과
// bird
// bird
// bird horse
위 코드에서는 참조 타입인 String을 사용했음에도 원시 타입을 사용했을 때처럼 출력 결과가 나왔을까요?
우선 처음은 List를 사용했을 때와 비슷합니다.
스택 영역에 animal 변수를 추가하고, 힙 영역에 String의 정보를 저장한 뒤 animal은 String 정보의 주소 값을 참조합니다.
그다음 addName 함수로 넘어갑니다.
addName 함수에서 사용되는 animal 변수도 스택 영역에 추가해 줍니다. 이 animal 변수도 역시 힙에 존재하는 String의 주소를 참조합니다.
이후에는 우선 "bird "에 "tiger "를 더해야 합니다.
String 타입은 덧셈(+) 연산자를 사용해 문자열을 합칠 경우 문자열이 합쳐진 새로운 인스턴스를 생성합니다. 왜 이렇게 작동할까요?
그것은 String이 불변 객체(Immutable Object)이기 때문입니다. 자바에서 객체는 크게 가변 객체(Mutable Object)와 불변 객체(Immutable Object)로 나눌 수 있습니다. 불변 객체는 대표적으로 String, Boolean, Integer, Long 등이 있습니다. 또한 가변 객체는 대표적으로 ArrayList, HashMap, HashSet 등이 있습니다. 이번 포스트는 스택과 힙에 대해 알아보는 것이므로 불변 객체와 가변 객체에 대해서는 이쯤 하고 넘어가겠습니다.
따라서 "bird tiger "이란 정보를 담고 있는 String을 새로 만들어 animal 변수는 이 객체의 주소 값을 참조합니다.
이제 addName 함수가 종료되니 스택의 animal 변수는 사라지게 됩니다.
여기서도 "horse"를 더한 새로운 문자열을 만들어 이 문자열을 참조하게 됩니다.
그럼 이제 힙 영역에는 2개의 String 데이터가 공간을 차지하고 있습니다. 자바는 이 데이터들을 GC(Garbage Collector)를 사용해 제거합니다. GC는 이렇게 메모리를 최대한 낭비하지 않도록 참조되고 있지 않은 데이터를 힙 영역에서 지우는 역할을 하고 있습니다.
따라서 다음과 같은 상황에서 GC가 휩쓸고 지나가면 다음과 같은 그림이 됩니다.
긴 글 읽어주셔서 감사합니다.
혹시 조금이라도 잘못된 내용이 있다면 알려주시면 감사하겠습니다🖍
'JAVA' 카테고리의 다른 글
[JAVA] 람다와 클로저 쉽게 알기 (2) | 2022.06.21 |
---|