[Java] 자바 메모리 모델과 object 클래스
자바 가상머신(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)
가 스택에서 전부 소멸된다.
- main에 의한 할당 ➡
힙 영역 (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 클래스)
Author And Source
이 문제에 관하여([Java] 자바 메모리 모델과 object 클래스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jollyn/Java-19저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)