읽기 전

  • 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다.
  • 공부하면서 배운 점을 정리한 글입니다.
  • 자바 플랫폼의 3대 요소 중에서 JVM과 관련된 내용만 작성할 예정입니다.

한 줄 요약 : 생성자 메소드 호출 이전 - 객체, 이후 - 인스턴스

이 글을 작성하게 된 이유

자바를 공부하면서 객체를 공부했는데, 부족한 부분들을 조금 더 이해한 것 같아서 공부한 내용을 정리해보려고 합니다.

변수

변수는 데이터를 저장하기 위한 메모리 공간의 이름을 뜻합니다.

 

그리고 메모리 공간은 이름을 저장할 공간이 있어야 하기 때문에 자료형(Data Type)이라는 것을 함께 정합니다.

 

즉, 자료형은 변수의 크기와 변수에 저장될 데이터의 종류를 결정하는 역할을 합니다.

 

그래서 자료형과 변수명을 정해서 변수를 선언하면

자료형에 해당하는 크기를 가지는 변수명으로 된 공간을 메모리에 만들게 됩니다.

 

자료형에 대한 자세한 내용은 사용법 이후에 작성하겠습니다.

 

변수의 기본적인 사용법은 아래와 같은 형태로 사용합니다.

public class Variable {
	public static void main(String[] args) {
		
		// int = 자료형 / age = 변수명
		int age; // 1. 변수의 선언
		age = 1; // 2. 변수 초기화(값 저장, 값 할당)
		System.out.println(age); // 3. 변수 사용(출력문 사용 / 확인)
        
		int age = 1; // 1. 변수의 선언 +  2. 변수 초기화(값 저장, 값 할당)
		System.out.println(age); // 3. 변수 사용(출력문 사용 / 확인)
	}
}

 

이 과정에서 변수 테이블(Symbol Table)이라는 걸 함께 만들어서 변수명과 메모리의 어느 위치에 저장되어 있는지에 대한 번지(주소 / address)를 key-value 형태로 저장합니다.

 

예를 들자면 다음과 같습니다.

  1. a, b, c라는 이름을 가진 변수 선언
  2. 변수명 a, b, c와 각 변수의 임의의 번지를 변수 테이블에 저장합니다.

그렇다면 왜 변수 테이블을 만들까요?

 

그 이유는 변수의 이름만으로 쉽게 관리하기 위함입니다. 

 

그리고 검색 시 해당하는 변수가 없다면 Cannot resolve symbol 혹은 Cannot find symbol 에러가 발생합니다.

 

위의 과정을 정리하면 다음과 같이 진행됩니다.

  1. 변수 선언
  2. 메모리에 변수의 이름과 값을 가진 기억 공간 생성
  3. 메모리는 번지로 이루어져 있기 떄문에 기억 공간에 대한 번지가 할당된다.
  4. 변수 테이블에 변수 이름과 번지 저장

하지만 이렇게 선언만 하게 된다면 해당 변수에는 아무런 값이 존재하지 않습니다.

 

그래서 우리는 대입 연산자(=)를 사용해서 우항의 데이터를 좌항의 변수에 할당(저장, 대입)합니다.

 

여기서 좌항은 대입 연산자를 기준으로 왼쪽을, 우항은 오른쪽을 뜻합니다.

 

그리고 우항에는 값, 변수, 수식, 메소드 호출문 등 다양한 형태가 들어갈 수 있습니다.

public class Variable {
	public static void main(String[] args) {
		
		int a;
		int b = 2;
		int c = 4;

		a = 1; // 값 할당 -> a = 1
		a = b // 변수 할당 -> a = 2 ( b = 2 )
		a = b * 2 // 수식(연산자 사용) -> a = 4 ( b = 2 -> 2 * 2 )
		a = sum(b, c); // 메소드 호출문 -> a = 6 (메소드가 있다고 가정)
	}
}

연산자와 관련된 내용은 아래를 참조해주세요.

[JAVA] 연산자


자료형은 유형을 크게 나누면 기본 자료형 객체 자료형(참조 자료형)이 있다고 생각하면 편하지 않나 싶습니다.

위 표는 기본 자료형으로, 컴파일러에서 기본적으로 제공해주는 자료형입니다.

 

관련 내용 중에서 데이터 범위와 같은 내용은 생략했습니다.

 

기본 자료형도 중요한 내용이지만 자바는 객체지향 프로그래밍 언어이기 때문에 객체 자료형을 더 많이 사용합니다.

 

객체 자료형은 보통 필요에 의해서 새롭게 만들어 사용하는 클래스나 기본적으로 자바가 제공해주는 클래스 모두를 포함합니다.

 

public class Variable {
	public static void main(String[] args) {	
		int a; (기본 자료형)
		Variable2 var = new Variable2(); (객체 자료형)	
	}
}

public class Variable2 { ... }

위와 같은 형태처럼 기본 자료형을 사용하는 경우와

다른 클래스 혹은 인터페이스를 사용해서 만드는 객체 자료형을 사용하는 경우로 나뉩니다.

 

그리고 추가로 기본 자료형은 객체 자료형과 다르기 때문에 자바에서는 Wrapper 클래스라는 걸 이용해서 기본 자료형도 객체 자료형처럼 사용할 수 있도록 만들어놓았습니다.

위의 표는 Wrapper Class(포장 클래스) 예시입니다.

 

실제로는 위의 예시처럼 new를 사용하는 방법을 사용하지는 않고 보통 컴파일러가 자동으로 해줍니다.

 

그렇다면 왜 기본 자료형을 포장 클래스를 통해 객체 자료형으로 만드는 것일까요?

 

그건 기본 자료형을 다른 객체 자료형처럼 Object를 상속받아서 사용하기 위함입니다.

 

Object는 모든 클래스의 root 클래스로 최상위 클래스입니다. 그래서 Object 클래스의 기능들을 사용하기 위해서 포장 클래스를 만들었다는 정도만 이해해도 괜찮을 것 같습니다.

 

여기에는 상속과 관련된 내용이 포함되어 있는데 그 내용은 다음번에 작성하겠습니다.

 

포장 클래스를 사용하는 예를 하나 들자면 페이지 번호를 뜻하는 page라는 변수를 사용한다고 했을 때 

해당 변수의 자료형을 Long으로 했을 때와 long으로 하는 경우에는 약간의 차이가 존재합니다.

 

가장 큰 차이는 Long 타입은 객체 타입이기 때문에 null 값을 사용할 수 있는 반면에 long 타입은 기본 자료형이기 때문에 null 값을 이용할 수 없습니다.

ps. 그래서 가끔 기본 자료형으로 값을 처리할 때 null 문제가 있을 때 자료형을 포장 클래스로 변경하면 동작하는 경우가 있습니다.


객체와 인스턴스

지금부터 객체와 인스턴스에 대해서 조금 더 자세히 이야기해보겠습니다.

 

객체는 상태 정보(필드, 멤버 변수, 속성) + 행위 정보(메소드, 함수)로 정보를 하나의 구조에 모아놓은 것입니다.

 

기본 자료형에 해당하는 변수는 본인의 번지에 하나의 값만을 넣습니다.

 

하지만 다양한 자료를 넣고 싶을 때 우리는 객체 자료형을 사용합니다.

 

객체 자료형 안 데이터의 자료형이 동일하다면 우리는 배열을 사용하게 되고, 아니라면 객체에 담아서 사용합니다.

 

예를 들면 다음과 같습니다.

기본 자료형은 int a = 10; 과 같은 형태로 변수를 생성합니다.

객체 자료형은 new라는 예약어를 통해서 변수를 생성합니다.

ps. 예약어는 이미 자바가 사용하는 있는 단어입니다.

public class Variable {
	public static void main(String[] args) {
		
		// 기본 자료형
		int age = 10; 
		        
		// 객체 자료형
		Animal ani = new Animal();
        
		// 배열
		int[] a = { ... };
        
		//문자열
		String str = "abcd";
	}
}

public Class Animal {
	public String cat;
}

 

위와 같은 형태로 변수를 저장하면 기본 자료형과 객체 자료형은 각기 다른 메모리 영역에 저장됩니다.

 

우리가 메모리 사용 영역을 사용하려면 각 영역의 이름을 알아야 합니다.

 

메모리 사용 영역(Runtime Data Area)에는 메소드 영역, 힙 영역, 스택 영역, 문자열이 존재합니다.

 

각 영역에 대해서 먼저 간단히 이야기해보겠습니다.

1. 메소드 영역 : JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역으로 코드에서 사용되는 클래스들을 클래스 로더로 읽어 클래스별로 정적 필드와 상수, 메소드 코드, 생성자 코드 등을 분류해서 저장합니다.

 

2. 힙 영역 : 객체와 배열이 생성되는 영역으로 스택 영역의 변수나 다른 객체의 필드에서 참조합니다. 만일 참조하는 변수나 필드가 없다면 의미 없는 객체가 되기 때문에 JVM이 쓰레기로 취급하고 GC(Garbage Collector)를 실행시켜 자동으로 제거합니다.

 

3. 스택 영역 : 메소드를 호출할 때마다 프레임을 추가하고 메소드가 종료되면 해당 프레임을 제거하는 동작을 수행합니다. 프레임 내부에는 로컬 변수 스택이 있는데 기본 타입 변수와 객체 타입 변수가 추가(push)되거나 제거(pop)됩니다. 스택 영역에 변수가 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장(할당)될 때입니다. 변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거됩니다.

 

4. String constant pool : String 클래스의 저장 영역

 

참고로 1.7에서 1.8로 바뀌면서 저장영역이 변경되었습니다.

관련 내용은 아래 링크를 참조하면 됩니다.

https://johngrib.github.io/wiki/java8-why-permgen-removed/

 

public class Variable {
	public static void main(String[] args) {
		
		Animal ani; // 변수 생성
		ani = new Animal(); // 객체 생성
		// 생성자 메소드 호출 시점에 따라 이전이면 객체, 이후라면 인스턴스라고 부릅니다.

		//non-static
		ani.say(); // 야옹
		
		//static
		Animal.meow(); //야옹~~~~~
	}
}

public Class Animal {
	public String cat;
    
	public void say() {
		System.out.println("야옹");
	}
    
	public static void meow() { 
		System.out.println("야옹~~~~~");
	}
    
	// 기본 생성자, 보통은 생략되어 있습니다.
	public Animal() { super(); }
}

ps. 접근 제어자에 대한 설명은 다음에 하겠습니다.

 

저는 위에 있는 Animal이라는 클래스를 이용하겠습니다.

JVM이 실행 클래스를 static 유무에 따라 어떻게 다르게 진행하는지에 대해서 알아보겠습니다.

먼저 static class 입니다. ( meow() 메소드 )

 

1.  Animal 클래스를 현재 디렉토리에서 찾는다.

2. 찾으면 클래스 내부에 있는 static 키워드가 있는 메소드( main(), meow() )를 메모리에 로딩한다.

3. 메소드 영역에 static zone에서 main() 메소드를 실행(호출, 시작)한다.

3-1. 메소드 영역에서 meow() 메소드를 호출한다.

4. main() 메소드의 호출 정보가 스택 영역에 들어간다(push).

4-1. stack 영역에 meow 호출 정보도 저장된 후 main -> meow 순서로 실행된다.

5. 스택 영역이 비어 있으면 종료된다.

 

그리고 다음으로는 static 키워드 없이 new를 통해 실행하는 클래스의 절차에 대해서 알아보겠습니다.

non-static class ( say() 메소드 )

1.  Animal 클래스를 현재 디렉토리에서 찾는다.

2. 찾으면 클래스 내부에 있는 static 키워드가 있는 메소드( main() )를 메모리에 로딩한다.

3. 메소드 영역에 static zone에서 main() 메소드를 실행(호출, 시작)한다.

4. main() 메소드의 호출 정보가 스택 영역에 들어간다(push).

5. 스택 영역에 저장된 main 영역에 ani라는 이름으로 저장된 변수를 통해 heap영역에 저장된 객체를 찾는다.

6. 해당하는 메소드를 메소드 영역의 non-static zone에서 찾아와서 사용한다.

7. 스택 영역이 비어 있으면 종료된다.

 

스태틱 여부에 따라서 3-1.과 5.처럼 전혀 다른 방식으로 사용됩니다.

 

마지막으로 String 클래스를 사용하면 문자열 상수(객체)가 생성되는 메모리 영역에 저장됩니다.

 

이곳에 저장되는 값은 재활용이 가능합니다.


큰 흐름으로 정리하기 위해서 배열, 접근 제어자, 생성자 등 자세한 내용들이 빠진 것들이 있습니다.

 

이후에 관련 내용들은 추가로 정리하려고 합니다.

 

부족한 부분이 많겠지만 긴 글 읽어주셔서 감사합니다.