12.1.1 클래스와 객체의 관계
객체 직렬화를 논하기 전에 우선 클래스와 객체의 관계에 대해서 알아보도록 하죠. 클래스(Class)와 객체(Object)의 관계는 클래스의 기초 개념을 학습하면서 이미 다루었던 내용입니다. 여기서는 약간 다른 측면에서 생각해 보도록 하죠.
▣ 클래스와 객체의 관계
◈ 클래스의 형정보와 객체의 메모리 사이의 관계
자바 파일을 컴파일한 후 생성된 .class 파일은 클래스의 모든 정보를 담고 있으며, 객체를 생성하기 위해서는 반드시 해당 .class 파일을 로딩해야 합니다. .class 파일은 클래스의 형정보를 담고 있으며, 형정보 없이 객체를 생성하는 방법은 존재하지 않습니다. .class에 포함된 형정보가 로딩되었다면 객체를 생성할 수 있으며 메서드 또한 호출할 수 있습니다. 그렇다면 다음과 같은 질문을 던질 수 있습니다.
▣ 질문
◈ 클래스의 형정보로 만든 객체의 메모리가 어떠한 형태로 되어 있는가?
◈ 메서드를 호출했을 때 객체의 메모리와 형정보가 어떠한 방식으로 동작하는가?
형정보를 이용해서 객체의 메모리를 만들 때 멤버 메서드와는 관계가 없습니다. 일반적으로 멤버 변수의 크기와 객체의 메모리의 크기는 같습니다. 자바에서는 메모리를 직접 접근할 수 없기 때문에 메모리의 크기를 증명할 수는 없지만 ANSI C++에서는 아주 당연한 것입니다. C++에서 객체의 메모리를 계산하기 위해서 sizeof라는 연산자를 제공하고 있습니다. 실제 이것을 테스트하기 위한 간단한 예제를 만들어 보도록 하죠.
『chap12\objtest\objmain.cpp』
ⓙ───────────────────────────────────────
/**
객체의 메모리 크기를 계산하는 예제
**/
#include <stdio.h>
class RefObj{
private:
int a;
int b;
public:
void SetData(int a, int b){
this->a = a;
this->b = b;
}
void PrintData(){
printf("a=%d b=%d\n", a, b);
}
};
int main(void){
RefObj r1;
RefObj r2;
r1.SetData(100,200);
r1.PrintData();
r2.SetData(1000,2000);
r2.PrintData();
printf("size of RefObj r1 : %d byte\n", sizeof(r1));
printf("size of RefObj r2 : %d byte\n", sizeof(r2));
return 0;
}
//㉶--------------------------------------------㉳
/***
C:\javasrc\chap12>CL.EXE objmain.cpp
C:\javasrc\chap12>objmain.exe
a=100 b=200
a=1000 b=2000
size of RefObj r1 : 8 byte
size of RefObj r2 : 8 byte
***/
───────────────────────────────────────ⓑ
▣ 참고
◈ RefObj라는 클래스는 C++의 형식의 클래스이지만 자바의 클래스와 비슷한 모양을 하고 있습니다. C++에서는 메서드의 선언과 구현을 분리시키지만, 최대한 자바와 비슷한 형식으로 구현하기 위해서 인라인(inline) 메서드 형식으로 구현하고 있습니다.
위의 예에서 RefObj형의 객체의 메모리를 생성하기 위해서 다음과 같이 메모리를 생성하고 있습니다.
▣ RefObjet형의 객체의 메모리 생성
◈ RefObj r1;
◈ RefObj r2;
표준 C++에서는 클래스형의 변수 선언은 메모리의 생성을 의미합니다. 그렇기 때문에 위의 구문은 객체의 메모리 생성을 의미합니다.
위의 예에서 sizeof 연산자를 이용해서 RefObj r1과 r2의 메모리를 계산하고 있습니다. 이 때 나타나는 메모리의 크기는 8바이트입니다. 이것은 int형 멤버 변수 2개를 가지고 있기 때문에 RefObj라는 클래스의 메모리는 8바이트가 되는 것입니다. 그렇다면 다음과 같은 결론을 내릴 수 있습니다.
▣ 객체의 메모리 크기
◈ 객체의 메모리의 크기는 멤버 변수들의 전체 메모리의 크기와 같다.
객체의 메모리는 멤버 변수의 메모리와 동일한 크기를 가지게 됩니다. 만약 RefObj r1과 r2를 이용해서 메서드를 호출한다면, r1과 r2의 메모리와 RefObj 클래스의 형정보를 조합해서 메서드를 호출하게 됩니다.
객체의 메모리와 메서드의 호출【chap12\classobject.bmp】

메서드의 형태는 클래스의 정보가 있는 부분에 있으며, 객체의 메모리는 메모리 영역에 독립적으로 존재합니다. 클래스의 형정보와 객체의 메모리를 조합해서 메서드를 호출하거나 멤버 필드에 값을 변경할 수 있습니다. 가령 메서드 내에서 멤버 변수가 사용되어진다면, r1의 멤버 변수인지 r2의 멤버 변수인지만 구분해 주면 됩니다.
▣ 참고
◈ 위의 r1과 r2의 메서드가 호출되어지고 메서드 내부에서 멤버가 사용되어질 때, r1의 멤버 변수인지 r2의 멤버 변수인지를 구분하는 방법은 의외로 간단하다. 단순히 메서드를 호출할 때 숨어있는 매개변수가 하나 있다고 생각하면 된다. 이 숨어있는 매개변수가 바로 객체의 참조값이 된다. C++로 생각하면 주소겠지만 자바에서는 참조값을 들고다닌다고 생각하면 쉽게 해결할 수 있다. 실제 메서드에서 사용된 멤버가 어느 객체의 멤버 변수인지는 첨부된 참조값을 이용해서 구분할 수 있다.
클래스의 형정보와 객체의 메모리만 있다면 언제든지 메서드를 호출할 수 있습니다. 이것은 형태와 내용의 절묘한 조합입니다.
위의 그림에서 r1과 r2의 메모리는 독립적으로 존재하면서 RefObj 클래스의 형정보를 공유하는 형식으로 메서드를 호출하게 됩니다. 위의 방식은 C++에서 이용되는 방식이지만 자바에서도 이것은 동일합니다. 형정보와 객체의 메모리는 독립적으로 존재하면서 유기적으로 동작하게 됩니다.
객체의 메모리는 프로그램이 실행되는 동안 유지되는 순간적인 메모리입니다. 이 순간적인 객체의 메모리를 지속적으로 보관할 방법에 대해서 생각해 보도록 하죠. 그렇다면 여러분은 다음과 같은 질문을 던질 것입니다.
▣ 질문
◈ 객체의 어떠한 데이터를 저장할까?
쉽게 생각해 보면 위의 RefObj r1과 r2의 메모리를 저장하면 됩니다. 그리고 다시 복원하면 될 것입니다. 하지만 단순히 r1과 r2의 메모리를 저장한다면 각각 8바이트씩 저장될 것이며, 나중에 복원할 때 8바이트의 데이터가 무엇인지 알 방법이 없습니다. 즉 객체의 메모리와 약간의 정보를 포함시켜서 저장해야 할 것입니다.
▣ 객체 저장
◈ 객체의 메모리 자체와 객체에 대한 정보를 포함시켜야 한다.
객체의 메모리와 객체에 대한 약간의 정보를 포함시켜서 객체를 저장할 수 있다고 가정하죠. 그렇다면 다시 객체로 복원하는 메커니즘이 필요할 것입니다. 객체의 메모리와 정보를 해석해서 원래의 객체로 복원하는 것을 수작업으로 할 수 있지만, 매번 저장하고 복원할 때마다 구현하는 것은 불합리한 방법입니다.
▣ 직렬화(Serialization)
◈ 객체를 저장하는 기법
▣ 역직렬화(Deseialization)
◈ 직렬화된 객체를 복원하는 기법
자바에서는 객체를 저장하고 저장된 객체를 복원하는 작업을 자동화시켰으며, 객체를 저장하는 기법을 직렬화(Serialization)라고 하며 다시 복원하는 작업을 역직렬화(Deseialization)라고 합니다.
우리가 이 장에서 하려고 하는 작업은 가상머신에 존재하는 객체의 메모리 그 자체를 저장하거나, 통째로 네트웍으로 전송하려고 하는 것입니다. 저장을 하든 네트웍으로 전송을 하든 간에, 객체는 일련의 바이트의 형태로 되어 있어야 합니다. 약속된 규칙에 의해서 객체의 메모리를 한 줄로 늘어선 바이트의 형태로 만드는 것을 객체의 직렬화(Serialization)라고 하며, 다시 객체의 형태로 복원하는 작업을 우리는 객체의 역직렬화(Deserialization)라고 합니다.