Spring을 사용하다보니 Serialization을 쓰는 일이 많아져서 Serialization이 무엇인지 궁금해서 기록을 위해 블로깅을 했다
🔔성격 급한 꼬레아노들을 위한 간단 정리
Java에서 직렬화란?
- 자바 시스템 내에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록
바이트 형태로 변환하는 기술
- JVM의 메모리(힙/스택)에 있는 객체 데이터를 바이트 형태로 변환하는 것
그럼 역직렬화는?
- 바이트로 변환된 데이터를 다시 객체로 변환하는 기술
- 직렬화된 바이트 형태의 데이터를 객체로 변환해 JVM에 상주시키는 것
SUID !!다음과 같은 경우 제대로된 객체를 불러올 수 없다
1. 저장하는 쪽과 불러오는 쪽의 컴파일러가 다를 경우
2. 저장하는 시기와 불러오는 시기의 클래스 내용이 다를 경우
직렬화(Serialization)
: 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변경하는 것
주된 목적은 객체를 그대로 저장하고 필요할 때 다시 생성하여 데이터로 변환하는 것이다
🤔직렬화에 대해 맛은 봤으니 그럼 왜 필요할까?
개발 언어 종류와 무관하게, 사용하는 데이터 메모리 구조는 크게 2가지로 분류할 수 있다
1️⃣값 형식 데이터
: int, float, char 등 형식 데이터는 스택에 메모리가 쌓이고 직접 접근 가능하다
2️⃣참조 형식 데이터
: 객체와 같은 참조 형식 변수를 선언하면 힙에 메모리가 할당되어, 스택에서는 이 힙 메모리를 참조하는 구조로 되어있다.
데이터의 종류는 두 가지지만, 디스크에 저장하거나 사용은 값 형식 데이터만 가능하다.
참조 형식 데이터는 실제 값이 아닌 힙에 할당된 메모리 번지 주소를 가지기 때문이다.
아직 자세한 설명은 하지 않았지만, 힙이 동적 할당 메모리라는 점을 인지하면 눈치챘을 것이다.
>> 참조 형식 데이터를 사용할 수 없는 이유
할당 값에 상관없이 프로그램 종료시 기존 할당된 메모리는 해제되고 없어지기 때문이다.
네트워크 통신도 마찬가지이다. 각 PC마다 사용하는 메모리 공간 주소가 전혀 다른데, 내가 다른 PC로 내 힙에 있는 메모리 주소를 객체 데이터로 보낸다한들 다른 PC에서는 해당 메모리 주소에 전혀 다른 값이 존재하기 때문이다.
위의 내용을 통해 값 형식 데이터는 가능하나, 참조 형식 데이터는 사용 불가능한 것을 알았다.
>> 직렬화의 필요성
직렬화 시 각 주소 값이 가지는 데이터를 모아 값 형식 데이터로 변환해준다.
직렬화가 된 데이터는 언어에 따라 텍스트나 바이너리 등의 형태가 되는데, 이런 형태가 되면 저장하거나 통신할 때 파싱이 가능한 유의미한 데이터가 되는 것이다.
(아직 용어가 미숙한 사람들을 위해 파싱이란 구문 분석을 말한다.
웹에서 원하는 데이터를 추출해 가공하기 쉬운 상태로 만드는 것이다.)
즉, 직렬화의 이유는 사용하고 있는 데이터를 파일 저장이나 데이터 통신에서 파싱가능한 유의미한 데이터를 만들기 위함
>> 데이터 직렬화의 종류
CSV, XML, JSON 직렬화
- 사람이 읽을 수 있는 형태
- 저장 공간의 효율성이 떨어지며, 파싱하는 시간이 오래 걸린다
- 데이터 양이 적을 때 주로 사용
- 최근에는 JSON 형태의 데이터 직렬화가 많다
- 모든 시스템에 사용 가능
Binary 직렬화
- 사람이 못 읽어유
- 저장 공간을 효율적으로 사용할 수 있고, 파싱하는 시간이 빠르다
- 데이터의 양이 많을 때 주로 사용
- 모든 시스템에 사용 가능
JAVA 직렬화
- Java 시스템 간의 데이터 교환이 필요할 때 사용
>> 그렇다면 자바에서 직렬화와 역직렬화는?
✔ 직렬화
: 객체를 Byte Stream으로 변환하는 것
즉, 객체에 저장된 데이터를 Stream에 쓰기 위해 연속적인 데이터로 변환하는 것
✔역직렬화
: 직렬화된 Byte 형태의 데이터를 객체로 변환해 JVM에 상주시키는 것
아래의 그림을 보면 바로 알 수 있듯이 직렬화의 반대말로, 네트워크나 영구 저장소에서 바이트 스트림을 가져와서 객체가 저장된 그 상태로 다시 변환하는 것을 말한다
위와 같은 과정을 통해 직렬화 중 생긴 바이트 스트림은 플랫폼에 독립적이다.
이말은 즉, 직렬화된 객체는 다른 플랫폼에서도 역직렬화가 가능하다는 의미이다.
모든 클래스를 직렬화할 수는 없다.
java.io.Serializable 인터페이스를 구현한 클래스의 객체만 직렬화될 수 있다
Serializable 인터페이스는 어떤 멤버변수나 메서드도 가지고 있지 않은 마커 인터페이스 marker interface다.
해당 인터페이스를 구현한 클래스가 특정 기능을 가진다 표시해두기 위해 쓰인다는 의미이다.
클래스에 비밀번호처럼 보안상 직렬화가 안되는 값이나 직렬화 될 수 없는 객체에 대한 참조가 있을 경우
'transient' 제어자를 사용한다. transient가 붙은 인스턴스 변수 값은 그 타입의 기본값으로 직렬화된다
직렬화(Serialize) 조건
java.io.Serializable 인터페이스를 상속받은 객체는 직렬화할 수 있는 기본조건
Serializable 인터페이스를 구현하는 클래스로 만든다
직렬화(Serialize) 방법
java.io.ObjectOutputStream를 사용해 직렬화를 진행
역직렬화(Deserialize) 조건
- 직렬화 대상이 된 클래스가 클래스 패스에 존재해야하며 import 되어 있어야 한다
- 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있는 것을 반드시 고려해야한다
- 자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다
역직렬화(Deserialize) 방법
java.io.ObjectInputStream을 사용해 역직렬화를 진행
SerialVersionUID?
1. serialVersionUID를 지정하지 않으면 컴파일러가 계산한 값을 부여하는데,
이는 컴파일러에 따라 할당되는 값이 다를 수 있다는 의미이다.
2. 컴파일러는 Serializable Class 또는 Outer Class를 참고하여 만들기에 클래스 변경이 있으면 serialVersionUID도 변경이 있을 수 있다
3. 위와 같이 변경이 일어나면 serialVersionUID값이 달라져 InvalidClassExceptions의 발생으로 저장된 값을 객체로 복원할 수 없다
Java 직렬화 대상 객체는 동일 SUID를 지녀야하며, 컴파일러의 선언으로 내부 클래스 구조 정보를 통해 자동 생성 시 해시값이 할당된다. 이 과정에서 클래스의 멤버 변수가 추가되거나 삭제시 SUID가 변경된다.
객체를 제대로 불러오지 못하는 경우
1. 저장하는 쪽과 불러오는 쪽의 컴파일러가 다를 경우
2. 저장하는 시기와 불러오는 시기의 클래스 내용이 다를 경우
🤔그렇다면 자바에서는 어떻게 권할까?
일반적으로 SUID를 개발자가 선언하고 관리하는 방식을 권장한다
이런 의문이 생길 수 있다.
JSON이나 CSV를 쓰면 되는거를, 굳이 자바로?
그럼 하지ㅁ
>> 자바 직렬화를 쓰는 이유
- 자바 직렬화는 자바 시스템에서 개발에 최적화
- 복잡한 데이터 구조 클래스의 객체라도 직렬화 조건만 충족되면 바로 직렬화,역직렬화가 가능
- 데이터 타입의 자동 맞춤으로 인해 역직렬화 진행 시 기존 객체처럼 바로 사용 가능
>> 자바 직렬화의 단점
- 역직렬화 시, 클래스 구조 변경 문제
- 엄격한 타입 체크 (ex : int => long)
- 용량 문제(간단한 객체의 내용도 2배 이상 용량 차이가 발생)
🤔그렇다면 직렬화를 사용함에 있어 오류가 발생하거나 주의해야 하는 경우는 언제일까?
확실한 건, 직렬화시에 자주 변경될 소지가 있는 클래스의 객체는 사용하지 않는게 좋다.
프레임워크나 라이브러리에서 제공하는 클래스의 객체의 버전업으로 인해 SerialVersionUID가 변경될 경우, 예상치 못한 오류가 발생하기 때문이다.
🤦♂️ 직렬화 사용에 있어 오류 발생이나 주의가 필요한 경우
1️⃣멤버 변수를 추가할 때
- SUID 값이 선언되면 멤버 변수가 추가하더라도 오류는 발생하지 않는다
- 재구성되는 클래스에 스트림에 없는 필드가 있으면 객체의 해당 필드가 기본값(ex : null)로 초기화된다
2️⃣ 멤버 변수를 삭제할 때
- 멤버 변수를 추가하는 것과 동일하게 오류는 발생하지 않으나 값 자체가 사라진다
3️⃣ 멤버 변수의 이름이 바뀔 때
- 멤버 변수의 이름이 바뀌게되면 역직렬화 오류는 발생하지 않으나 값이 할당되지 않는다
4️⃣ 멤버 변수의 타입이 바뀔 때
- 기존 멤버 변수의 타입 변경시 역직렬화 과정에서 ClassCastException이 발생할 수 있다
- Integer 타입을 Double 등의 primitive type 간의 변경에서도 동일하게 적용된다
5️⃣ 접근 지정자의 변경
- public, proteced 등의 접근 지정자의 변경은 직렬화에는 영향을 주지 않는다.
6️⃣ Static & transient
- static 멤버의 직렬화 후 non - static 멤버로 변경되는 경우 직렬화된 값은 무시된다
- transient 키워드는 직렬화 대상에서 제외하는 선언으로, 역직렬화 시에 transient 선언을 제외해도 값은 채워지지 않는다
자바 직렬화 요약 서머리
1. 직렬화 가능한 클래스들의 상태 확인
2. 직렬화, 역직렬화의 경우 객체들의 순서가 중요하다. ArrayList 등을 활용시 손쉽게 가능
3. SerialVersionUID 고유 번호를 관리!! 자동으로 생성해주기도 하나 직접 관리하자
참고 자료 :
https://medium.com/@lunay0ung/basics-%EC%A7%81%EB%A0%AC%ED%99%94-serialization-%EB%9E%80-feat-java-2f3eb11e9a8
https://zakelstorm.tistory.com/82
https://steady-coding.tistory.com/576
https://madplay.github.io/post/java-serialization-advanced
https://m.blog.naver.com/writer0713/220922099055
'책벌레와 벌레 그 사이 어딘가 > 개념쌓기' 카테고리의 다른 글
[개념쌓기] CORS 복습 (0) | 2023.01.20 |
---|---|
[개념쌓기] final/ finally/ finalize() (0) | 2023.01.16 |
[개념쌓기] Object class? 자바의 최상위 클래스 (0) | 2023.01.12 |
[개념쌓기] JAVA 메모리 내 메소드 (2) | 2023.01.10 |
[개념쌓기] 가비지컬렉터? JVM에서 힙? (0) | 2023.01.09 |
댓글