목차
이 글은 Notion에서 작성 후 재편집한 포스트입니다.
개요
자바 시스템에서, 각자 PC의 OS마다 서로 다른 가상 메모리 주소 공간을 사용하기 때문에, 이들끼리 통신하기 위해선 직렬화 라는 작업을 진행해야 합니다.
본 포스팅에선 직렬화란 어떤건지, 어떤 방식으로 이루어지는지, 어떤 상황에서 필요한지, 장단점과 유의점에 대해 알아보도록 하겠습니다.
참고
https://parkadd.tistory.com/134
https://gyoogle.dev/blog/computer-language/Java/Serialization.html
https://go-coding.tistory.com/101
진행 과정
직렬화란?
자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바시스템에서도 사용할 수 있게 바이트 형태로 데이터를 변환하는 기술입니다.
이런 문제를 해결하기 위해선 주소값이 아닌 Byte형태로 직렬화된 객체 데이터를 전달해줘야 합니다.
언제, 어디에 사용되는가?
서블릿 세션
- 세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 된다면 직렬화되어 저장됩니다.
캐시
- Ehcache, redis, Memcached 라이브러리 시스템을 많이 사용합니다.
JAVA RMI(Remote Method Invocation)
- 원격 시스템의 메서드를 호출할 때 전달하는 메세지를 직렬화하여 사용하고, 메세지를 전달받은 원격 시스템에서는 메세지를 역직렬화하여 사용합니다.
직렬화/역직렬화의 진행방식
직렬화는 크게 직렬화와 역직렬화로 이루어집니다. (인코딩, 디코딩처럼)
public calss Member implements Serializable{
private String name;
private String email;
private int age;
public Member(String name, String email, int age){
this.name = name;
this.email = email;
this.age = age = age;
}
@Override
public String toString(){
return String.format("Member{name='%s', email='%s', age='%s'}", name,email)
}
}
Serializable 인터페이스를 사용합니다. 해당 인터페이스를 상속받은 객체는 직렬화할 수 있는 기본 조건입니다.
public static void main(String[] args){
Member member = new Memer("김배민", "deliverykim@baemin.com", 25);
byte[] serializeMember;
try(ByeArrayOutputStream baos= new ByteArrayOutputStream()){
try(ObjectOutputStream oos= new ObjectOutputStream(baos)){
oos.writeObject(member);
// serializedMember -> 직렬화된 member 객체
serializedMember = baos.toByteArray();
}
}
//바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}
java.io.ObjectOutputStream을 사용하여 직렬화를 진행합니다.
이렇게 직렬화된 데이터를 다시 역직렬화 하여 사용합니다.
public static void main(String[] args){
//직렬화 예제에서 생성된 base64 데이터
String base64Member="...생략";
byte[] serializedMember = Base64.getDecoder().decode(base64Member);
try(ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)){
try(ObjecInputStream ois = new ObjectInputStream(bais)){
//역직렬화된 member객체를 읽어온다.
Object objectMember = ois.readObject();
Member member = (Member)objectMember;
System.out.println(member);
}
}
}
이 때, 다음과 같은 조건이 있습니다.
- 직렬화 대상이 된 객체의 클래스가 클래스 패스에 의존 및 import가 되어있어야 함
- 직렬화와 역직렬화의 진행 시스템이 다를 수 있다는 것에 대한 고려가 필요함
- 자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 함
ex) private static final long serialVersionUID = 1L;
위 조건에서 serialVersionUID라는 말이 나옵니다. 이는 직렬화 버전의 고유값을 나타내는 것으로, 이 값으로 해당 클래스의 버전에 맞는지 아닌지를 판단하는 것입니다.
사실 이는 선언하지 않아도 자동으로 해시값이 할당됩니다. 직접 설정하는 이유는, 기존의 클래스 멤버 변수가 변경되면 serialVersionUID가 변경되는데, 역직렬화 시, 달라진 number로 Exception이 발생할 수 있기 때문입니다.
따라서 serialVersionUID는 직접 관리해야 클래스의 변수가 변경되어도 직렬화에 문제가 생기지 않게됩니다.
직렬화의 단점
이런 직렬화도 단점이 존재하는데요, 바로 역직렬화 시, 클래스 구조에 변경이 생긴다는 점입니다.
직렬화한 Data를 역직렬화하면 어떤 결과가 나올까요?> 결과는 java.io.InvalidClassException 이 발생합니다.
위에서 언급했던 것처럼 직렬화하는 시스템과 역직렬화하는 시스템이 다른 경우에 발생하는 문제입니다.
각 시스템에서 사용하고 있는 모델의 버전 차이가 발생했을 경우에 생기는 문제입니다.
이를 해결하기 위해서는 바로 위에서말한 serialVersionUID를 직접 관리해줘야 합니다.
이 외에도, 직렬화 데이터 사이즈의 문제가 있습니다.
{"name":"김배민","email":"deliverykim@baemin.com","age":25}
serializedMember (byte size = 146)
json (byte size = 62)
아주 간단한 코드임에도 불구하고 사이즈가 2배로 늘어난 것을 확인할 수 있습니다.
때문에 일반적인 메모리기반의 Cache에서는 Data를 저장할 수 있는 용량의 한계가 있기 때문에, Json과 같은 경량화된 형태로 직렬화 하는것도 좋은 방법입니다.
결론
위의 내용들을 정리하자면 다음과 같습니다.
- 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양합니다.
- 역질렬화시 반드시 예외가 생긴다는 것을 생각하고 개발합니다.
- 자주 변경되는 비즈니스적인 데이터를 자바 직렬화를 사용하지 않습니다.
- 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.
직렬화를 사용할때와 안할때를 적절히 구분하여 사용할 수 있도록 하는 것이 좋을 것 같습니다.
구독 및 하트는 정보 포스팅 제작에 큰 힘이됩니다♡
'PS, 언어 공부 > JAVA' 카테고리의 다른 글
[Java] 자바 가상스레드의 정의와 원리 파헤치기 (0) | 2024.06.21 |
---|---|
[Java] CompletableFuture의 개념과 동작원리, Thread, Future와의 비교 (1) | 2024.06.02 |
[JAVA] JVM 구조 및 작동원리 (2) | 2022.10.29 |
[JAVA] 가변 배열에서의 확률 뽑기 (0) | 2021.01.27 |