[Java] 자바 메모리 모델과 object 클래스

33467 단어 JavaJava

자바 가상머신(JVM)


  • 자바 가상머신은 운영체제 위에서 실행되는 하나의 프로그램이다.
  • 자바 프로그램은 자바 가상머신 위에서 실행되는 프로그램이다.
  • 자바 프로그램 실행에 필요한 메모리 공간은 운영체제가 할당해준다.
  • 자바 가상머신은 운영체제가 할당해 주는 메모리 공간을 효율적으로 사용해 자바 프로그램을 실행하게 된다.
  • 하나의 자바 가상머신은 하나의 자바 프로그램을 실행한다.





자바 가상머신의 메모리 모델


자바 가상머신은 메모리 공간 활용의 효율성을 높이기 위해 메모리 공간을 3개의 영역으로 구분했다.

  • 메소드 영역 | 메소드의 바이트코드, static 변수 저장
  • 스택 영역 | 지역변수, 매개변수 저장
  • 힙 영역 | 인스턴스 저장



메소드 영역 (Method Area)

  • 메소드의 바이트코드, 클래스(static) 변수를 저장하는 메모리 영역
    (자바 가상머신에 의해 실행이 가능한 코드를 바이트코드라 함)
  • 특정 클래스의 정보가 메모리 공간에 올려질 때 채워지는 영역이다.
  • 영구 저장 ➡ 프로그램이 종료될 때까지 유지된다.
class Boy {
	static int age = 0;
	public void Run() {...}
}
Class MyMain {
	public static void main(String[] agrs) {
		Boy b = new Boy(); // Boy 인스턴스 생성
		Boy.age += 10; // 클래스 변수에 접근
    }
}





스택 영역 (Stack Area)

  • 지역 변수, 매개 변수를 저장하는 메모리 영역
  • 임시 저장 ➡ 변수가 선언되는 순간 스택에 할당되었다가 자신이 할당된 영역을 벗어나면 소멸된다.
    ( 지역 변수와 매개 변수 모두 중괄호 지역 내에서만 유효한 변수들로, 중괄호를 벗어나면 바로 소멸되는 특성을 가진다.
    이러한 특성의 데이터 저장을 위한 영역이 스택이다! )


public static void main(String[] args) {
	int num1 = 10;
	int num2 = 20;
	adder(num1, num2);
	System.out.println("프로그램 종료");
}
public static void adder(int n1, int n2) {
	int result = n1 + n2;
	return result;
}
  • 스택 영역 : (args, num1, num2) , (n1, n2, result)
    • main에 의한 할당 ➡ (args, num1, num2)
    • adder에 의한 할당 ➡ (n1, n2, result)
    • adder 메소드를 빠져나오면 그 안에 할당 된 매개변수(n1, n2)와 지역변수(result)가 스택에서 전부 소멸된다.





힙 영역 (Heap Area)

  • 인스턴스를 저장하는 메모리 영역
  • 가상머신에 의해 가비지 컬렉션(Garbage Collection)이 일어나는 메모리 공간이다.
  • 그 누구도 참조하지 않는 인스턴스는 가비지 컬렉션의 대상이 되어 소멸된다.
    ( 이때 인스턴스가 가비지 컬렉션의 대상이 되었다고 바로 소멸 되는 것은 아니다. 가비지 컬렉션의 빈번한 실행은 시스텐에 부담이기 때문에 성능에 영향을 미치지 않도록 가비지 컬렉션의 실행 타이밍은 별도의 알고리즘을 기반으로 계산되어 수행된다. )


🔎 인스턴스를 스택이 아닌 힙이라는 별도의 영역에 할당하는 이유가 뭘까 ?

  • 인스턴스의 소멸 시점과 방법이 지역변수와 다르기 때문이다 !

public static void simple() {
	String str1 = new String("First String")
	String str2 = new String("Second String")
	...
    str1 = null; // 참조관계 소멸
    str2 = null; // 참조관계 소멸
}
  • str1, str2는 참조변수이자 지역변수로 스택에 할당
    • simple 메소드를 벗어나면 소멸된다.
  • String 인스턴스는 힙에 할당
    • 소멸시기를 가상머신이 결정한다.
    • 해당 인스턴스가 아무런 참조변수도 참조하지 않는 상태라면 가비지 컬렉션의 대상(소멸의 대상)이 되어 가상머신에 의해 소멸된다.











object 클래스의 메소드


finalize 메소드 (인스턴스 소멸)

  • protected void finalize() throws Throwable
  • 아무도 참조하지 않는 인스턴스가 가비지 컬렉션에 의해 소멸되기 전 자동으로 호출되는 메소드이다.
  • 인스턴스 소멸 시 반드시 실행해야 할 코드가 있다면 이 메소드를 오버라이딩 해서 실행 할 수 있다.



🔎 관련 예제

package ch19;

/* object클래스의 메소드 (finalize)
 * 아무도 참조하지 않는 인스턴스가 가비지 컬렉션에 의해 소멸되기 전 자동으로 호출되는 메소드
 * 인스턴스 소멸 시 반드시 실행해야 할 코드가 있다면 이 메소드를 오버라이딩 해서 실행 할 수 있음
 * 
 */

class Person {
	String name;
	
	public Person(String name) {
		this.name = name;
	}
	
	// finalize 메소드 오버라이딩
	@Override	
	protected void finalize() throws Throwable {	
		super.finalize();  // 상위 클래스의 finalize 메소드 호출
		System.out.println("소멸 : " + name);
	}
}

public class ObjectFinalizeMethod {
	public static void main(String[] args) {
		Person p1 = new Person("anne");
		Person p2 = new Person("wuga");
		p1 = null;	// 참조대상을 가비지 컬렉션의 대상으로 만듦
		p2 = null;	// 참조대상을 가비지 컬렉션의 대상으로 만듦
//		System.gc();	// 가비지 컬렉션의 수행을 요청 (명령이 아닌 요청)
//		System.runFinalization();	// 소멸이 보류된 인스턴스의 finalize 메소드 호출을 요청 (명령이 아닌 요청)
		
		System.out.println("프로그램 종료");
	}
}


// 실행 결과
프로그램 종료
  • 두 인스턴스를 가비지 컬렉션의 대상이 되게 했음에도 실행 결과에서 finalize 메소드를 호출한 결과가 나타나지 않는다.
    • 이유 ➡ 가비지 컬렉션은 자주 일어나지 않으며, 소멸할 인스턴스가 생겨도 가비지 컬렉션으로 바로 이어지지 않기 때문
  • 프로그램이 종료되면 프로그램을 위해 할당된 메모리 전부가 소멸되기 때문에 finalize 메소드의 호출이 생략될 가능성도 있다.
  • 이때, 두 메소드의 호출을 통해 finalize 메소드의 호출을 어느정도 보장받을 수있다.
    ( 하지만, 가상머신은 매우 합리적인 방법으로 가비지 컬렉션을 수행하므로, 특별한 상황이 아니라면 가비지 컬렉션 동작에 영향을 미치는 호출을 삼 가하는 것이 좋다. )
    • System.gc();
    • System.runFinalization();







equals 메소드 (인스턴스 비교)

  • 인스턴스의 내용 비교를 위한 기능을 equals 메소드에 담아 정의한다.
class Inum {
	private int num;
	
	public Inum(int num) {
		this.num = num;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(this.num == ((Inum)obj).num)
			return true;
		else
			return false;
	}
}

class ObjectEqualityMethod {
	public static void main(String[] args) {
		Inum num1 = new Inum(10);
		Inum num2 = new Inum(15);
		Inum num3 = new Inum(10);
		
		if (num1.equals(num2))
			System.out.println("num1, num2 내용이 동일합니다.");
		else
			System.out.println("num1, num2 내용이 다릅니다.");
		
		if (num1.equals(num3))
			System.out.println("num1. num2 내용이 동일합니다.");
		else
			System.out.println("num1, num3 내용이 다릅니다.");
	}
}


// 실행 결과
num1, num2 내용이 다릅니다.
num1. num2 내용이 동일합니다.



🔎 String 클래스의 equals 메소드

  • String 클래스는 인스턴스의 내용을 비교 하는 형태로 equals 메소드를 오버라이딩 하고 있다.
    • 문자열 인스턴스의 참조 값을 비교할 땐 ==연산자 사용
    • 문자열 인스턴스의 내용을 비교할 땐 equals메소드 사용
package ch19;

/* String 클래스의 Equals 메소드
 * ==연산자는 참조변수의 참조값을 비교
 * equals메소드는 인스턴스의 내용을 비교 
 */

public class StringEqualityMethod {
	public static void main(String[] args) {
		String str1 = new String("Simple");
		String str2 = new String("Simple");
		
		// 참조 값을 비교
		if (str1 == str2)
			System.out.println("str1, str2 참조 값이 동일");
		else
			System.out.println("str1, str2 참조 값이 다름");
		
		// 두 인스턴스의 내용을 비교
		if (str1.equals(str2))
			System.out.println("str1, str2 내용이 동일");
		else
			System.out.println("str1, str2 내용이 다름");
	}
}


// 실행 결과
str1, str2 참조 값이 다름
str1, str2 내용이 동일







clone 메소드 (인스턴스 복사)

  • protected Object clone() throws CloneNotSupportedException
  • object 클래스에 정의되어 있는 clone 메소드가 호출되면 인스턴스의 복사가 이뤄진다.
  • 단, clonable 인터페이스를 구현한 인스턴스를 대상으로만 이 메소드를 호출할 수 있다.
    • interface Cloneable
    • cloneable 인터페이스는 구현해야 할 추상 메소드가 없는 마커 인터페이스이다. ( clone 메소드의 호출이 허용된다는 표식일 뿐이다. )
    • cloneable 인터페이스를 구현하지 않은 클래스의 인스턴스를 대상으로 clone 메소드를 호출하면 CloneNotSupportedException 예외가 발생
class Point implements Cloneable {  // Point가 Cloneable 인터페이스를 구현
	private int xPos;
	private int yPos;
	
	public Point (int x, int y) {
		xPos = x;
		yPos = y;
	}
	
	public void showPostion() {
		System.out.printf("[%d, %d]", xPos, yPos);
		System.out.println();
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

public class ObjectCloneMethod {
	public static void main(String[] args) {
		Point p1 = new Point(3,5);
		Point p2;
		
		try {
			p2 = (Point)p1.clone();
			p1.showPostion();
			p2.showPostion();
		}
		catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}
}


// 실행 결과
[3, 5]
[3, 5]
  • clone 메소드를 호출하기 위해서는 오버라이딩을 통해 접근 수준 지시자를 protected에서 public으로 바꿔줘야 한다.
    • 이유 : clone메소드는 object클래스의 메소드로 java.lang 패키지에 있기 때문에 public으로 접근을 열어줘야 하기 때문이다.





🔎 얕은 복사 (Shallow Copy)

  • 주소값을 복사한다는 의미
    ( 즉, 참조하고 있는 실제값은 같다 )



🔎 깊은 복사 (Deep Copy)

  • 실제값을 새로운 메모리 공간에 복사하는 것을 의미
    ( 즉, 실제값이 다르다 )



🔎 String 인스턴스 대상의 깊은 복사

  • String은 문자열의 수정이 불가능 하므로, 깊은 복사의 대상에서 제외된다.
  • String 인스턴스의 내용을 이루는 문자열은 인스턴스 생성 시 결정되고, 한번 결정되면 변경이 불가능하기 때문에 서로 다른 인스턴스가 하나의 String 인스턴스를 공유해도 문제가 되지 않는다.



🔎 clone 메소드의 반환형 수정

  • 오버라이딩 과정에서 반환형을 수정해줄 수 있다.
class AAA {
	public AAA method() {...}  // 반환형이 자신이 속한 AAA클래스 형이다. 
    

class ZZZ extends AAA {
	@Override
	public ZZZ methoe() {...}  // 반환형이 자신이 속한 ZZZ 클래서 형이다.
}
  • 이때 하위 클래스의 이름이 ZZZ인 경우 반환형은 ZZZ로만 수정가능하다.











참고
열혈 자바 ch19 (자바의 메모리 모델과 object 클래스)

좋은 웹페이지 즐겨찾기