자바 메모리 할당 원리(그림으로 배워보자)


블로그를 포스팅하는데 시간이 생각보다 오래 걸려 한동안 업로드하지 못해 죄송합니다.

이번에는 이전에 만들었던 메모리 구조 그림을 이용해서 본격적으로 어떻게 그려지는지 알아보겠습니다.

먼저 코드와 최종 그림은 다음과 같습니다.

class Human extends Object {
  String name = "조정식"; 
  int age = 28; 
  double tall = 172.2; 
  char gender = '남'; 
  boolean isPretty = true; 

  void walk(){
   System.out.println(name+"이 걷습니다.");  
  }
  void eat(){
  System.out.println(name+"이 먹습니다.");  
  }
  void speak(){
  System.out.println(name+"이 말합니다.");  
  }
  public static void main(String [] args){
  Human h = new Human();
  System.out.println(h.name); 
  h.walk(); 
  h.eat();
  h.speak();
  }
}

[Human 클래스에 대한 메모리]

이제 어떻게 그려진 것인지 알아보겠습니다.
먼저, 우리가 이전에 살펴보았던 메모리의 구조입니다.
메모리의 구조는 연습을 하시든 포스팅으로 하든 계속 나올 것이기에 그림을 간단히 표시했습니다.

간단히 표시한 메모리의 구조는 다음과 같습니다.


이제  메모리 할당에 대해  객체가 어떻게 만들어지는지 그림으로 확인하겠습니다.
하나의 클래스에는 여러개의 메소드를 설정할 수 있습니다.
메인 메소드는 1개로 명령을 내리는 곳이고 나머지 메소드는 사람으로 비유하여 저는 걷다, 먹다, 말하다 메소드를 만들었습니다.



모든 클래스는 Object를 상속하기 때문에 extends Object가 포함되어있습니다.
상속에 대해서는 나중에 배우겠지만 아버지라고 생각하면 됩니다.
안써도 쓴거와 같은 의미로 class Human { 만 써도됩니다.
String int double 등 배운 데이터타입들이죠? 처음에 넣을 때 값을 넣은 것이고 주석에는 크기와 맨처음 값이 어떻게 할당되어있는지를 나타낸겁니다.



자바의 실행원리는 다음과 같습니다.
.java라는 파일을 만들고 컴파일 하면 class로 됩니다.
그리고 load하고 Bytecode Vrfier를 거쳐 Machinecode Generator로 이동해 수행합니다.
이 Machinecode Generator는 JVM이 읽는 0101...로 바꿔줍니다.

그리고 JVM의 실행순서는 다음과 같습니다.
1. class를 로드한다.
2. main을 제외한 static 멤버를 초기화한다.
3. 상속관계를 파악한다. (상속이 있을 경우 1~3 반복)
4. Main을 수행한다.

이 순서는 꼭 암기하셔야합니다. 그래야 코드를 보고 단순히 외우는게 아니라 실행 흐름을 이해할 수 있기 때문입니다.


이제 아까의 코드를 다시 보고 그림으로 설명하겠습니다.

1-1. Human이라는 class를 로드한다.
1-2. Main을 제외한 static멤버를 초기화하지만 없다.
1-3. 상속관계를 파악한다. (extends Object)
2-1. Object를 로드한적이 있는가? 없다면 로드한다. 
      (rt.jar에서 object.class를 바이트코드로 만들어서 로드한다.)
2-2. Object에서는 static멤버가 없다.
2-3. 상속관계 없다. (Object는 최종 아버지입니다.)
1-4. Main수행


main을 수행하러 들어가서 할당 연산자 new를 먼저 보게됩니다.
new는 인스턴스 영역을 새롭게 할당해달라는 뜻으로 Human을 위해 공간이 할당됩니다.

이제 new를 하고 나면 Human을 만납니다.
그럼 다시 JVM이 Human을 로드한적이 있는가? 라고 묻습니다.
우린 클래스를 불러온 맨 처음에 Human이라는 클래스를 로드한적이 있기 때문에 바로 ( ); 를 수행하게 됩니다.
( )는 생성자를 가르키는 것으로 생성자가 하는 일은 non-static멤버를 초기화합니다.
(static이 아닌 것을 초기화한다는 의미)

new를 만났을 때 메모리 그림

( )를 만났을 떄 메모리 그림


먼저, 5개의 데이터 타입의 디폴트 초기화(초기값 설정)가 이루어 집니다.
(String : 4byte null, int 4byte 0, double 8byte 0.0, char 2byte \u0000(공백 의미), boolean 1byte false) 초기값을 주고나면 생성자를 호출합니다.
생성자를 만나게 되면 non-static멤버를 초기화하여 값을 주게됩니다.
그리고 생성자 안에 있는 super( );를 통해 아버지인 Object를 만나게됩니다.)
Object는 11개의 메소드와 생성자가 있고 Object는 최종 아버지여서 생성자는 있지만 super( );가 없어서 공간이 위에서 아래로 뚜껑이 닫히는 그림으로 닫히게 됩니다.
(String과 생성자에 대한 자세한 내용은 아래에 따로 정리했습니다.)

String은 ""형태로 생성하면 String Literal Pool에 생성됩니다.
그리고 참조타입으로 값이 직접 들어가는 것이 아닌 주소값이 들어가게 됩니다.
"조정식"은 String Litral Pool에서 만들어집니다.
그리고 나머지 age, tall, gender, isPreety는 로컬변수로 Stack에서 만들어지며 값을 주고 나중에 Stack에서 사라집니다.

요약
main에서 
Human h = new Human( ); 
//new를 만나면 Instance영역에 공간을 만든다.
//Human( );에서 값을 주는 행위를 하고 생성자(super( );)로 아버지 타입을 호출하게된다.
//String을 ""형태로 값을 주면 String Literal Pool에서 따로 값을 할당하고 주소값을 참조하게된다.
//Object는 11개의 메소드와 생성자가 있지만 super( );가 없어서 뚜껑을 닫게된다.
//이렇게 만들어진 Human은 h라는 이름으로 @100주소값을 갖게된다.(편의상 @100)
//Stack영역에서 Main을 위한 공간에 있는 h는 @100번지로 Instance영역을 가르킨다,
//타입은 Human타입과 아버지인 Object인 타입 2개가 있지만, 대표타입을 Human으로 설정한다.
//
  h.walk( ); 
  h.eat( );
  h.speak( ); 
를 만나게되면 M/A영역에 생기고 Human공간과 연결된다.


[최종 그림]

최종 그림을 다시 보시고 한번 더 위에서부터 내려서 보시기 바랍니다.


[정리]

String 
String은 참조형타입으로 기본타입과 조금 다릅니다. 
(이 부분은 지금 이해가 안되시면 나중에 다시 읽어보세요.)
String 타입을 new를 이용한 것이 아닌 ""로 literal을 통해 만들 경우 String literal pool 영역으로 바로 가게됩니다. 이 영역에 있는 c-value-comparator는 문자 값을 비교하는 녀석으로 문자열이 들어오면 똑같은 literal을 찾게됩니다. 이 String Litreal Pool 영역은 이름 그대로 무조건 String을 위해서 공간을 할당하는데 같은 문자열이 있을 경우 추가적으로 객체를 생성하지 않고 똑같은 것을 참조하게 되며, 같은 문자열이 없을 경우에만 공간을 새로 할당받아서 만듭니다.

생성자
생성자는 new연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당합니다. 초기화란 reset이 아닌 데이터와 메소드를 메모리에 올리는 행위입니다.
이 생성자에는 super( );라는 메소드가 있는데 아버지의 생성자를 호출하라는 구문입니다. 따로 extends가 된게 없다면 바로 아버지가 Object를 뜻합니다.
public Object() { } 를 호출하면서 Human 공간이 그림처럼 더 커집니다. 
오브젝트에는 데이터가 없으며, 메소드가 11(toString...)와 + 생성자가 있습니다.





댓글 없음:

Powered by Blogger.