Programming/Java

JAVA :: 자바 메모리 구조 - 2. 힙 영역(Heap Area)과 스택 영역(Stack Area) 비교

WANJIN 2017. 5. 10. 16:57
반응형

자바 메모리 구조

 

이전 포스트에서 자바의 메모리 구조는 크게 메소드 영역, 힙 영역, 스택 영역으로 나뉘어 있다는 것과, 메소드 영역이 뭔지 살짝 알아봤다.

 

이번에는 힙 영역과 스택 영역은 뭔지, 각각 어떤 역할을 하는지,

 

좀 헷갈리니까 친절히 코드로 비교도 해볼까 한다.

 

 

2. 스택 영역(Stack Area)

스택 영역에는 지역변수와 매개변수가 저장된다.

 

그렇담 지역변수는 뭐고, 매개변수는 뭔가여.

한글로 접하니까 오히려 헷갈리는데 지역변수 = 로컬변수(local variable), 매개변수 = 파라미터(parameter)라고 들으면 아~~ 뭐야. 할 것임.

이래도 모르겠으면 로컬변수는 메소드 내에서 선언된 변수들, 매개변수는 메소드에 아규먼트로 넘겨주는 값들로 이해하고 넘어가자.

 

로컬변수와 매개변수의 특징은 이 아이들이 선언된 그 블록 안에서만 유효한 변수들이라는 점이다.

이런 친구들이 스택 영역에 저장되는 것임.

 

쉽게 말해 스택 영역에는 프로그램의 실행 과정에서 '임시로 할당'되고, 그게 끝나면 바로 소멸되는 것들이 저장된다.

 즉 => 메소드가 호출될 때마다 그 메소드의 로컬 변수를 준비하고, 메소드 호출이 끝나면 그 메소드를 위해 준비했던 모든 변수가 스택에서 제거된다.

 

 



<참고> 

밑에서 설명하겠지만, 빠른 이해를 위해 덧붙이자면 
참조변수에 저장되는 메모리주소는 스택영역에 저장되지만, 그 주소가 가리키는 메모리는 모두 힙 영역에 저장된다.

 

 

코드로 보자.

 

<스택 영역>

 

public class StackMemoryTest {
  public static void m1(int a) {
    m2(++a);
    System.out.printf("m1():%d\n", a);
  }
  
  public static void m2(int a) {
    m3(++a);
    System.out.printf("m2():%d\n", a);
  }
  
  public static void m3(int a) {
    ++a;
    System.out.printf("m3():%d\n", a);
  }
  
  public static void main(String[] args) {
    int a = 20;
    m1(a);
    System.out.printf("main():%d\n", a);
  }
}

 

 

결과:

m3(): 23
m2(): 22
m1(): 21
main(): 20 



여기서 이런 결과가 나오는 이유는, 먼저 들어간 것이 나중에 나오는 스택 구조의 특성 때문이다.

 

위 코드의 흐름을 보기 좋게 표현하면 대략 이렇다.

 

 

 실행 코드 스택 영역 메모리  출력


main() {
int a = 20; 
m1(a);
...
}



 
 main() : a = 20



 


m1(a) { m2(++a);
... }


/* a 자리에 a + 1이 놓임 */





 m1() : a = 21
 main() : a = 20
 
 
m2 (a) {
m3(++a);
...
}
 
 m2() : a = 22
 m1() : a = 21
 main() : a = 20
 
 
m3 (a) {
++a;
...
}



 m3() : a = 23
 m2() : a = 22
 m1() : a = 21
 main()  : a = 20


 
 


m3 (a) {
...
  System.out.printf("m3():%d\n", a);
}
 
 m3() : a = 23
 m2() : a = 22
 m1() : a = 21
 main()  : a = 20
 
m3(): 23
  m2(a) { 
...
 System.out.printf("m2():%d\n", a); 
 }
 
 m2() : a = 22
 m1() : a = 21
 main()  : a = 20
m3(): 23
m2(): 22
 m1(a) { 
...
 System.out.printf("m1():%d\n", a); 
 }





 m1() : a = 21
 main()  : a = 20



m3(): 23
m2(): 22
 m1(): 21 
 main() {
...
System.out.printf("main():%d\n", a);
}


 
 main()  : a = 20

m3(): 23
m2(): 22
 m1(): 21 
main(): 20

 

 

이렇게 지역변수의 값은 해당 메소드가 실행되면, 그 메소드의 스택번지에 값이 저장된다.

그리고 해당 메소드가 모두 실행되고나면, 스택에서 해당 메소드의 스택번지가 제거되는 구조이다.

 

아래의 힙 영역과 어떻게 다른지도 밑에서 설명하도록 하겠다.

 

 

 

 

 

 

 

3. 힙 영역(Heap Area)

힙 영역에는 흔히 코드에서 'new'명령을 통해 생성된 인스턴스 변수가 놓인다.

 

어떤 메소드인지는 상관이 없다.걍 new 명령으로 만드는 메모리는 모조리 힙 영역에 보관된다고 보면 된다.
스택영역에 저장되는 로컬변수, 매게변수와 달리, 

힙 영역에 보관되는 메모리는 메소드 호출이 끝나도 사라지지 않고 유지된다.

 

언제까지??

 

주소를 잃어버려 가비지가 되어 가비지 컬렉터에 의해서 지워질 때까지. 아니면 JVM이 종료될때까지.
역시 코드로 살펴보겠다.


<힙 영역>

public class HeapMemoryTest {
 
  public static int[] m1(int a) {
    int[] arr = m2(a + 1);
    arr[2] = a;
    return arr;
  }
  
  public static int[] m2(int a) {
    int[] arr = m3(a + 1);
    arr[1] = a;
    return arr;
  }
  
  public static int[] m3(int a) {
    int[] arr = new int[3];
    arr[0] = a;
    return arr;
  }
  
  
  public static void main(String[] args) {
    int[] arr = m1(100);
    for (int i = 0; i < arr.length; i++) {
      System.out.printf("%d=%d\n", i, arr[i]);
    }
  }
}

 

결과:
0=102
1=101
2=100

 

 

 실행 코드 스택 영역 메모리  힙 영역 메모리
 main() {
...
}
  
 main()


 


main() {
int[] arr = m1(100);

...
}




 m1() : a = 100
main()
 


m1(a) { int[] arr = m2(a + 1); ... }



 m2() : a = 101
 m1() : a = 100
main()
 
 
m2(a) { int[] arr = m3(a + 1); ... }


 m3() : a = 102
 m2() : a = 101
 m1() : a = 100
main()
  

 
m3(a) {
int[] arr = new int[3];

arr[0] = a;
return arr;
}



 m3() : a = 102,
arr = 7000번지
 m2() : a = 101
 m1() : a = 100
main()
 








 <7000번지>
     









m3(a) {
int[] arr = new int[3];
arr[0] = a;
return arr;
}
 
 m3() : a = 102,
arr = 7000번지
 m2() : a = 101
 m1() : a = 100
main()
 






 <7000번지>
102    








 
  
m3(a) { 
int[] arr = new int[3];
arr[0] = a;
return arr;
}


m2(a) {
  int[] arr = m3(a + 1);
arr[1] = a;
return arr;

}


 m2() : a = 101,
arr = 7000번지
 m1() : a = 100
main()
 
 <7000번지>
102    








  
  
m2(a) { 
  int[] arr = m3(a + 1); 
arr[1] = a;
return arr;

}
 
 m2() : a = 101,
arr = 7000번지
 m1() : a = 100
main()
 
 <7000번지>
102  101  








   
  
m2(a) {
 int[] arr = m3(a + 1);
arr[1] = a;
return arr;

}

m1(a) {
int[] arr = m2(a + 1);
arr[2] = a;
return arr;

}

 
  
 m1() : a = 100,
arr = 7000번지
main()
 
 <7000번지>
102  101  








    
m1(a) {
int[] arr = m2(a + 1);
arr[2] = a;
return arr;

}
 
 
 m1() : a = 100,
arr = 7000번지
main()
 
  <7000번지>
102  101  100








    
  
m1(a) {
int[] arr = m2(a + 1);
arr[2] = a;
return arr;

}
 


main() {
int[] arr = m1(100);

...
}
  
 
main() : arr = 7000번지
 
   <7000번지>
102  101  100








    


main() {
...
for (int i = 0; i < arr.length; i++) {
System.out.printf("%d=%d\n", i, arr[i]); }
}






main() : arr = 7000번지
 
   <7000번지>
102  101  100








     
결과 출력:

0=102
1=101
2=100

 

 

이미 눈치가 빠른 사람은 알아차렸겠지만

이렇게 인스턴스를 별도의 힙 영역에 할당하는 이유

 

인스턴스의 소멸방법과 소멸시점이 지역변수(스택영역에 할당되는 애들)와는 다르기 때문이다.

 

(이전 포스트에서 굳이 메모리 영역을 몇 개로 나눠서 관리하는 이유가, 일상생활에서도 용도에 따라 물건을 다른 장소에 관리하는게 편한것과 같은 맥락이라고 설명했다.)

 

 



<참고2> 


이미 알고 있으리라 믿지만.


8가지 원시타입(byte, short, int, long, float, double, char, boolean)을 제외한 그외의 타입으로 정의된 변수들은 
모조리 레퍼런스 변수, 즉 참조변수이다.


이런 참조변수들은 실행될 때마다 많은 데이터들을 스택메모리 영역에 뒀다 뺐다 하는게 매우 비효율적이므로,
힙 영역에 그 내용(진짜 값)이 저장되고, 스택 메모리에는 간단하게 그 주소만 저장이 되는 것이다.


그리고 힙 영역에는 실제 그 변수가 가리키고 있는 값들이 저장되어 있다.

 

 

 

정리

스택 영역의 지역변수들은 메소드가 호출되고 끝이 나는 프로그램의 실행 흐름에 관여하는 것들이고

힙 영역의 인스턴스들은지역변수가 참조하고 있는 실제의 값들을 가지고 있다.

 

 

 

 

 

---

 

반응형