자바(Java) 강의

16. 자바 레퍼런스(Reference)와 Null

삐멜 2019. 5. 2. 14:22

이 포스트에서는 null을 이해하기 위한 최소한의 정보(JVM 메모리)에 대해 설명하고 null에 대해 설명하도록 한다. 예상 독자는 자바를 막 배우기 시작한 개발자들이므로 가상메모리를 실제메모리라 간주하고 설명한다.

이전 포스트

목표

  • 변수와 오브젝트
  • 스택과 힙, 변수와 오브젝트
  • null이란 무엇인가?
  • null 할당
  • Null Pointer Exception


변수와 오브젝트

이전 포스트중 자바의 오브젝트에서 변수를 선언하고 그 변수의 형(타입)에 해당하는 오브젝트를 생성해 할당 해 주었다. 다시말해

String myString;

위와 같은 코드의 뜻은 '나는 myString이라는 변수를 선언할 건데, 이 변수는 String 형을 담을 변수다'라는 뜻이었다.

new String("Hello World!");

위의 문법은 오브젝트를 생성하는 문법이었다. 어떤 형(타입)의 오브젝트를 생성했는가? String 타입의 오브젝트를 생성하고 'Hello World'로 초기화 했다. 생성된 오브젝트를 변수에 어떻게 할당했는가?

String myString = new String("Hello World!");

바로 위처럼 '='를 이용해 할당했다. 이제 실행시 실제 메모리상에서 일어나는 일에 대해 이야기 해 보자.

스택과 힙, 변수와 오브젝트

여러분이 작성한 자바 프로그램은 JVM(Java Virtual Machine)이라는 프로그램이 실행시켜준다. 여러분이 IDE에서 프로그램 실행 버튼을 누를때 마다, IDE는 JVM을 실행시키고, 이 JVM에게 여러분이 작성한 파일을 실행해 달라고 요청한다. 그렇다면 JVM이 하는 일은 무엇인가?

JVM은 여러분의 프로그램을 실행시키기 위해 여러가지 일을 하는데 그 중 하나가 바로 메모리 관리이다. 여기서 말하는 메모리는 RAM(Random Access Memory)를 말한다. 컴퓨터 살때 8GB RAM을 사라 마라 할 때의 그 RAM이다. 메모리를 왜 관리해야 하는가? 메모리는 8GB, 16GB등으로 한정적이다. 우리는 무한히 메모리를 사용할 수 없고 그럴 필요도 없다. 필요할 때 사용하고, 필요 없어지면 다른 프로그램 또는 다른 시점에서 사용할 수 있도록 놓아주면 되는것이다. 지금까지 우리는 필요할 때 잘 사용해 왔다. 무슨뜻인가? 

String myString = new String("Hello World!");

이렇게 변수를 선언하는것, 오브젝트를 '생성'하는 것이 바로 JVM에게 '나 메모리가 필요하니 이 문법대로 알아서 생성해줘'라고 말하는것과 같은 것이다. 위의 문법이 실행되는 순간 JVM이 알아서 얼마만큼의 공간을 할당해야하는지, 어디에 할당해야하는지 계산하고 할당 해 준다. 

또 우리는 필요가 없어지면 사용하던 메모리 공간을 놓아주어야 한다. 이 작업을 언제 했는가? 안했다. 이 작업은 GrabageCollection이라는 애가 해주는데 이는 다른 포스트에서 설명하도록 하겠다. 아무튼 여기서 중요한 것은 JVM이 메모리를 관리해 준다는 것이다. 

참고! RAM(Random Access Memory)의 각 메모리 공간은 주소를 가진다. Random Access라는게 무슨뜻인가? 배열처럼 인덱스로 접근할수 있는걸 Random Access라고 한다. (index 1로 접근했다가 5로 접근했다가..) RAM 메모리의 세계에서는 이 인덱스가 바로 '메모리 주소'이다.  

JVM은 한 프로그램이 실행시 사용할 수 있는 전체 메모리를 목적에 따라 여러개의 다른 공간으로 나눈다. 그 중 하나가 스택(Stack)과 힙(Heap)이다. 스택이라는 메모리는 메서드를 실행시키기 위해 사용한다. 예를들어 여러분이 메서드 내에서 변수를 선언하면, 이 변수는 스택에 공간이 만들어진다. 힙은 오브젝트가 사는 공간이다. 여러분이 new 키워드를 이용해 새 오브젝트를 만들어 달라고 하면 JVM은 이를 힙(Heap)라고 부르는 공간에 만든다. 

null이란 무엇인가?

여러분이 아래처럼 myString을 선언하면 JVM이 스택에 자리를 만들고 '이제부터 여기는 myString이 쓸거야'하고 찜 해 놓는다. 위의 그림을 해석하면 메모리의 0x03 주소번지의 공간은 이제부터 myString이라는 애가 사용한다는 것이다.

참고! 지금은 위처럼 메모리를 나눠서 그렸지만 실제 하드웨어 메모리는 저렇지 않을 수 있다. 위는 그저 JVM이 임의로 나눠놓은 것을 도식화 한 것이다. 두 메모리는 하드웨어에서는 같은 RAM이다. (따라서 같은 색임.)

String myString; 

하지만 현재 myString에는 아무것도 없다. 이렇게 변수에 아무것도 할당되지 않았을 때, 우리는 '그 변수의 값은 null이다'라고 한다. null은 아무것도 없음을 의미한다. 따라서 현재 myString의 값은 null이다. 

이번엔 String myString아래에 새 오브젝트를 만들어보자. 할당은 하지 말고 그냥 오브젝트 생성만 해보자.

String myString;
new String("Hello World!");

위와 같은 코드는 메모리 상 아래와 같을 것이다.

JVM은 힙(Heap)의 어떤 공간(그림에서 0x1F번지)에 String 오브젝트를 생성할 것이다. 하지만 myString과 새로 생성된 오브젝트 사이에는 아직 어떤 관계도 없다. 따라서 myString은 아직도 null이다.

myString이 언제 null에서 벗어나는가? 바로 myString에 어떤 값을 할당할 때이다. 지금 myString은 String 형만 할당할 수 있으므로, new String을 할당해보자. 

String myString = new String("Hello World!");

위처럼 '='를 이용해 할당하면 실제 메모리 세계에서는 어떤 일이 일어날까?

바로 위와같은 일이 일어난다. myString에 새로 만든 오브젝트가 들어있는 주소를 넣는 것이다. 이 주소를 자바에서는 '레퍼런스(Reference)'라고 부른다. 우리가 myString을 사용할 때 마다, JVM은 myString의 주소를 보고, 아 0x1F를 찾아가면 오브젝트가 나오는구나, 하고 0x1F를 찾아서 우리가 시키는 일(메서드콜, 값 가져오기 등등)을 한다는 뜻이다.

이를 간략하게 보통 화살표로 표시한다.

참고! 자료형에 따라 스택에 선언된 변수에 주소가 들어갈 수도, 값이 들어갈 수도 있다. 예를들어 int, double, 등 기본 자료형은 보통 숫자가 바로 들어가고, new 키워드로 오브젝트를 생성해야 하는것들은 주소가 들어간다.

null 할당

실제로 null이라는 녀석을 할당할 수도 있다.

String myString = null;

이렇게 하면 myString에 있던 주소는 사라지고 null이 들어간다.

Null Pointer Exception

그렇다면 정말 myString에는 null이 있는걸까?

public class Main {
public static void main(String[] args) {
String myString = null;
System.out.println(myString.length());
}
} Exception in thread "main" java.lang.NullPointerException at Main.main(Main.java:4)

myString에 null이 있고 실제 오브젝트가 없기때문에 String 형이 제공하는 함수를 사용할 수 없다. 만약 null인데 사용하려 하는경우 NullPointerException이라는 예외가 발생한다. '예외'는 다음 포스트에서 설명하도록 하겠다.

이 포스트에서는 정말 null을 이해하기 위한 최소 지식에 대해 설명했다. 따라서 간략하게 설명한 것도, 이해를 돕기 위해 두리뭉실하게 설명한것도 있음을 유의하라. 어쩌면 'null이란 아무것도 없음을 의미한다'하고 간단히 끝낼 수 있는 포스트이기도 하지만 변수와 오브젝트, null사이의 관계를 이해하는데 조금 더 도움이 될 수 있도록 설명을 덧붙였다.


생각할거리

public class MyClass {
String myString;
}

메서드 안에서 생성되는 변수는 스택이라는 공간에 할당된다고 했다. 그렇다면 위처럼 어떤 클래스의 멤버변수는 이 클래스의 오브젝트가 생성될 때 어디에 어떻게 선언/생성되는걸까? 

위에서 오브젝트 생성시 그 오브젝트의 크기를 알아서 계산해 그만큼 메모리를 할당한다고 했다. 오브젝트의 크기라는게 뭔가? 어떻게 계산할까? 바로 위의 질문과 어떤 관계가 있을까?

다음 포스트 : 17. 예외처리 (Exception, try-catch ) (1)