programming language/Java

직렬화란 무엇인가 ? / serialVersionUID 설정 이유

공대키메라 2022. 4. 11. 17:46

1. 재미있는 직렬화 이야기

화성과 지구간의 통신

때는 2040년... 화성은 식민 행성으로 발전했다.

 

근데 지구에서 로봇을 만들었다.

 

근데 화성에서도 마침 로봇을 만들었는데 지구의 로봇보다 성능이 좋아서 이것을 사용하기로 결정했다.

 

두 행성 과학자들이 미팅 후 화성에서 지구로 로봇을 보내기로 했다.

 

그런데, 문제가 발생했다!

 

로봇 100개를 지구로 보내는 비용이 100만 달러라고 한다. 여행 기간은 무려 60일이 걸린다고 한다.

 

비용 문제로 고민한 결과, 화성의 과학자들은 이 로봇의 핵심 기술을 지구의 과학자들과 공유하기로 한다. 

 

이 핵심 기술은 로봇의 구조에 관한 것이다. 

 

화성 로봇들은 지구의 로봇들과 같은 구조로 개발됐고, 화성 과학자들은 로봇 각각 부품의 데이터를 직렬화해서 지구로 보냈다.

 

지구의 과학자들은 이 데이터를 역직렬화해서 로봇에 적용했다. 

 

이 과정은 실제 로봇이 아닌 로봇을 움직이기 위한 정보만을 교환함으로 많은 시간과 비용을 절약했다.

 

몇 개의 로봇들은 화성에서 방호 작업에 사용되었다. 

 

그래서 과학자들은 지구로 데이터를 전달하기 전에 방호 작업에 관련된 정보를 입력했다. 즉, 환경 정보를 입력했다.

 

지구에서는 역직렬화 될 때에 필요가 없기에 이러한 환경 정보를 제거했다. 지구의 환경은 화성의 환경과 다르기 때문이다.

 

하지만 이 정보는 반드시 필요한데, 로봇 클래스 구조를 알고 직렬화된 데이터를 가져도, 지구의 과학자들은 로봇이 작동할 수 있는 데이터를 역직렬화 할 수 없기 때문이다. 하지만 돈을 낸다면 이 설정법을 공유할 예정이다.

 

화성 과학자들은 돈이 지불되길 기다렸다. 지구 과학자들이 비용을 지불해서야 화성 과학자들은 지구 과학자들과 serialiversionUID를 공유했다. 

 

지구 과학자들은 마침내 로봇 클래스를 설치했고, 모든것이 잘 작동했다.


직렬화의 도움으로, 실제 우주선 대신에 신호를 사용해 데이터를 전송할 수 있었고, 많은 양의 데이터를 보내는 것은 여전히 힘들다는 것을 깨달았다.

 

직렬화 하는 그 과정이 싸고 빠르게 했지만 그래도 여전히 느렸다. 

 

그래서 다른 과학자들이 데이터 사이즈를 줄이는 방법들을 떠올렸다.

 

몇몇은 데이터를 압축하려 했고, 몇몇은 역직렬화 될 수 있도록 나타내는 다른 메커니즘을 사용하려 했다. 

 

이러한 아이디어는 XML, JSON 등으로 이어졌다.

 

출처 : https://stackoverflow.com/questions/2475448/what-is-the-need-of-serialization-in-java

 

실제 이야기와 내가 해석한 이야기는 약간의 차이만 있을 뿐, 큰 맥락에서는 동일하다. 

 

무엇보다도 매끄럽지 않은, 해석을 배제하고 말이 좀 더 되도록 수정했다.

 

이 이야기에서 보면, 직렬화는 데이터를 전송할 때 주로 사용된다. 


google에서 직렬화 관련 정보를 찾다가 아주 재미있는 글귀를 찾아서 한번 소개를 해봤다. 

2. 직렬화/역직렬화는 무엇인가?

그래서... 직렬화가 뭔데?

직렬화(Serialize)

  • 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술.
  • JVM(Java Virtual Machine 이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

역직렬화(Deserialize)

  • byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화(Deserialize)라고 부릅니다.
  • 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태.

출처 : https://nesoy.github.io/articles/2018-04/Java-Serialize

 

정리하자면...

 

자바의 직렬화는 복잡한 자바 객체를 임의적으로 읽고 쓸 수 있게 한다. 

XML과 JSON들이 텍스트 기반 format인 반면, java 직렬화는 binary format이다. 

 

직렬화를 어디서 사용할까?

서블릿 세션 (Servlet Session)

  • 세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됩니다.

캐시 (Cache)

  • Ehcache, Redis, Memcached 라이브러리 시스템을 많이 사용됩니다.

자바 RMI(Remote Method Invocation)

  • 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술.

출처 : https://nesoy.github.io/articles/2018-04/Java-Serialize

 

그러면 xml,json으로 데이터를 주고받는 대신, 이진 포맷 형태로 데이터를 주고받을 때의 장단점은 무엇인가?

 

xml/json 대신 serializable을 구현하면 생기는 장점

우선, 직렬화는 거의 거져먹기로 할 수 있다. 직렬화 메커니즘이 작동하록 객체를 많이 수정할 필요가 없다. 

다른 장점은, 이진 포맷이기에 텍스트 기반 포맷보다 훨씬 더 가볍다. 그래서 아마도 더 적은 공간을 사용할 수도 있다. 

(이점은 디스크에서 저장 공간을 보호하거나 네트워크 대역폭을 보호하는데 좋을 수 있다!)

 

xml/json 대신 serializable을 구현하면 생기는 단점

내장된 자바 직렬화의 문제점은 객체를 변경할 때 다른 직렬화 포맷이 호환되도록 만드는 것이 힘들다는 점이다(출처의 글에서는 nightMare{악몽} 이라고 표현하고있다...). 또한, xml, json을 손수 수정한다면 직렬화된 자바 객체를 수정할 수 없다. (이런 치명적인 단점이...) 같은 이유로, xml과 json은 사람이 읽기 쉽기 때문에 이진 format보다 xml, json을 디버그하기 종종 더 쉽다. 자바에 내장된 직렬화 메커니즘의 또 다른 단점은 다른 프로그래밍 언어에서 직렬화와 역직렬화가 불가능 하다는 점이다!

 

출처 : https://stackoverflow.com/questions/2475448/what-is-the-need-of-serialization-in-java

 

3. 구현 - java bulitin serialization, deserialization 활용

간단하게 어떻게 사용해야 하는지 보도록 하겠다.

Member.java

public class 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;
    }
    
    @Override
    public String toString() {
        return String.format("Member{name='%s', email='%s', age='%s'}", name, email, age);
    }
    
    public static void main(String[] args){
        Member member = new Member("김배민", "deliverykim@baemin.com", 25);
        byte[] serializedMember1;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
                oos.writeObject(member);
                // serializedMember1 -> 직렬화된 member 객체
                serializedMember1 = baos.toByteArray();
            }
        }
        // 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
        String encodedResult = Base64.getEncoder().encodeToString(serializedMember);
        
        // 직렬화 예제에서 생성된 base64 데이터
        byte[] serializedMember2 = Base64.getDecoder().decode(encodedResult);
        try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember2)) {
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            // 역직렬화된 Member 객체를 읽어온다.
            Object objectMember = ois.readObject();
            Member member = (Member) objectMember;
            System.out.println(member);
        }
  	  }
    }
}

4. serialVersionUID는 왜 필요한가?

직렬화에 대한 내용을 정리하는 이유는 바로 이 부분을 위해서이다. 

 

필자는 customizing한 exception을 봤는데 runtimeException의 경우 serialVersionUID가 있는 것을 확인했다. 

 

이거는 도대체 왜 필요한 것인가?

 

serialVersionUID도 직렬화를 할 때 사용한다.

 

Serializable을 상속하는 class의 경우 class의 versioning용도로 serialVersionUID 변수를 사용한다.

이때, serialVersionUID 값을 명시적으로 지정하지 않으면 Complier가 계산한 값을 부여하는데 Serializable Class 또는 Outer class의 변경이 있는 경우 serialVersionUID가 변하게 된다. 그렇게 될 경우의 문제는 Serialize / Deserialize시에 serialVersionUID값이 다르면 InvalidClassExceptions가 발생하여 저장된 값을 우리가 원하는 형태로 Restore할 수 없다. 

 

출처 : https://m.blog.naver.com/writer0713/220922099055

5. RuntimeException 상속 시 serialVersionUID는?

RuntimeException의 경우 Exception을 상속하고, 

 

Exception의 경우 Throwable을 상속한다. 

 

Throwable은 Serializable을 구현한다. 

 

Serializable을 구현하는 클래스들은 serialiVersionUID가 필요한데, 컴파일 시점에 직렬화 되기 위해서, 재 컴파일링 시점에 역직렬화 하기 위해서 필요하다. JVM은 직렬화된 클래스의 버전이 역직렬화된 것과 맞는지 비교 작업을 하게 된다. 

그러므로 직렬화, 역직렬화 과정에서 서로의 버전이 맞는 것을 매치해 작업해주기 위해 이것이 필요한 것이다.

 

BaseException.java

@Getter
@Setter
@NoArgsConstructor
public class MyCustomBaseException extends RuntimeException {

    private static final long serialVersionUID = -8749131736286475046L;

    public MyCustomBaseException(String message) {
        super(message);
    }

}

 

출처 : https://stackoverflow.com/questions/46599012/why-do-we-need-serialversionuid-when-extending-runtimeexception

 

그래서 이러한 연유로, serialVersionUID는 Serializable을 구현하는 경우에는 직렬화, 역직렬화를 위해서 설정해야 한다. 

 


이것으로 직렬화는 무엇인지, 역지결화는 무엇인지 알아보았다.

 

이것을 공부한 이유는 회사에서 코드를 봣는데 결국 serialVersionUID가 있길래 이것이 도대체 무엇인지 이해하기 위해 끄적여 봤다.

'programming language > Java' 카테고리의 다른 글

Map 알아보기  (0) 2022.04.13
Deque, LinkedList 와 ArrayDeque  (0) 2022.04.11
Stack, Queue, PriorityQueue  (0) 2022.04.10
List 관련 질문 답변 정리  (3) 2022.04.04
static / final / static final이란?  (2) 2022.04.03