본문 바로가기
☕️ JAVA

자바 객체의 메모리 크기는 얼마일까? (OOPs, Java Object Header, JOL 사용하기) 🤖

by kukim 2022. 1. 14.

문득 이런 생각이 들었다. 내가 구현한 클래스의 메모리 총크기는 얼마일까?  흠......?

* 이 글은 책 소프트웨어의 품격 2장과 하단 reference를 참고했습니다. 잘못된 내용이 있다면 편하게 말씀해주세요 🙏🏻

 

목차

0. 들어가며

1. 참조의 기본 크기

2. 객체 헤더

3. 패딩

4. 예제 계산해보기

5. JOL(Java Object Layout)를 인텔리 제이에서 편하게 (플러그인 소개)


0. 들어가며

자바의 자료형(type)은 크게 두 가지 PrimitiveType(기본타입)ReferenceType(레퍼런스 타입)이 있다. 보통 기본 타입의 크기는 고정되어 있어 메모리 크기 계산이 비교적 쉽지만, 레퍼런스 타입, 객체에 대한 메모리 크기 계산은 쉽지 않다.

 

이유객체의 정확한 크기를 계산하기 위해서3가지(참조의 기본 크기, 객체 해더(object header), 패딩(padding))을 고려해야 하고, 이 3가지는 ‘자바 언어’와 ‘가상 머신(VM)’ 종류에 따라 달라진다. 아, 다 다르구나 음.. 알겠다!

 

자바 8과 VM : 오라클 표준 HotSpot(jdk10) 기준으로 위 3가지를 간단히! 설명하고 예제를 살펴본다. (아 이런게 있구나 정도로만 볼 수 있다.)

1. 참조의 기본 크기

C언어의 포인터 크기라고 볼 수 있는 자바의 ‘참조의 크기’는 언어 명세에 적혀있지 않다. 왜냐하면 VM에서 정하기 때문이다. 보통 32비트 CPU에서는 참조 크기가 32비트이고 64비트 CPU에서는 64비트이다. 또 압축된 일반 객체 포인터(compressed OOPs) 기술을 사용하면 64비트 CPU에서 참조의 크기를 32비트나 64비트 모두 사용할 수 있다. 신기하다.

 

+a) 왜 Compressed OOPs를 사용해 64비트 CPU에서 참조의 크기를 32비트로 사용할까?

먼저 참조의 크기가 32비트의 의미를 알아보자 32비트는 2^32(4,294,967,296)이고 약 42억 개의 정보를 가질 수 있다. 정보 1개는 메모리(RAM)의 1바이트 주소를 참조할 수 있고 따라서 참조 크기 32비트는 최대 약 42억 바이트, 4GB의 메모리 주소참조하고 사용할 수 있다.

참조 크기를 64비트로 정한다면 사용할 수 있는 최댓값은 2^64, 약 16EB의 엄청난 크기의 힙 주소를 사용할 수 있다. 하지만 자바에서 대부분의 데이터 타입은 참조 타입이기 때문에 참조 크기를 64비트로 사용한다면 비효율적일 수 있다. 이를 위해 Compressed OOPs를 사용해 필요에 따라 자바의 참조 크기를 변경하여 32비트 또는 64비트를 사용한다.

사실 Compressed OOPs의 32비트 실제 구현 사항은 32비트가 아니라 35비트로 구현되어있다. 따라서 기본적으로 VM에서 35비트로 참조크기를 사용하고 있고 이는 최대 2^35, 약 32GB 메모리를 사용할 수 있다. 또한 Compressed OOPs는 기본적으로 활성화되어있고, 32GB 이상 메모리 메모리를 사용한다면 VM이 자동으로 64비트로 객체 크기를 변경하여 사용한다.

 

요약 : VM(HotSpot) 기준 참조 크기 default 값 : 35비트 -> 약 32GB 힙 메모리 사용 가능, 32GB를 넘게 사용한다면 VM에서 자동으로 참조 크기를 64비트로 늘린다. 

2. 객체 헤더(Object Header)

모든 객체의 첫 메모리의 시작(정보)은 해당 객체의 메모리 레이아웃을 알려주는 객체 헤더로 시작한다. 따라서 필드가 없는 객체라도 메모리를 차지한다. 객체 헤더에 대한 구현사항은 JDK마다 다르다. JDK10, 참조의 크기 32비트 기준으로 일반 객체객체 해더 크기는 12바이트이고, 객체의 배열은 12바이트에 배열 정보를 4바이트를 추가한 16바이트이다.

 

Class Object  : 클래스 정보에 대한 포인터 (32 bits, 4byte)

Lock : 멀티쓰레드, 동기화 정보, 잠금 여부 (32bits, 4byte) 

Flag : 객체의 상태 설명(hash-code, array 여부) (32 bits, 4byte)

Size : 배열의 길이 (객체 배열만 해당, 32bits, 4byte)

으로 간략하게 살펴볼 수 있다 (참고, PPT 14page)

 

실제 구현체 소스코드는 아래와 같다.

// JDK10 "src/share/vm/oops/markOop.hpp" 중 객체 참조 크기 32bits의 객체 해더 구조와 크기

//  --------
//  hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object) // 32bit - Hash
//  JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object) // 32bit - Lock
//  PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object) // 32bit - Class Flag
//  size:32 ------------------------------------------>| (CMS free block) // 32bit - Size (array only)


// e.g
Object object = new Object();
ArrayOfObject[] arrayOfObjecta = new ArrayOfObject[n];

// object 객체해더 크기 : 12바이트
// arrayOfObject 객체 헤더 크기: 16바이트

 

객체 해더 정보는 자바 언어가 제공하는 3가지 핵심 기능에 사용된다.

리플렉션(reflection)

리플렉션을 위해서모든 객체가 자신의 타입을 알아야 한다. 따라서 객체에 클래스 참조를 저장하거나 로딩된 클래스가 담긴 테이블의 특정 요소를 가리키는 정수 형태의 식별자가 필요하다. 이러한 방식 덕분에 instanceof 연산자로 객체의 동적인 타입을 확인하거나 Object 클래스의 getClass 메서드로 객체의 (동적) 클래스 참조 정보를 얻어올 수 있다.

 

멀티 스레드(multithreading)

멀티 스레드 지원을 위해 객체에 모니터(monitor)가 할당되는데(synchronized 키워드를 통해 모니터 접근 가능) 이때 객체 헤더 정보모니터 객체의 참조를 포함한다.

 

가비지 컬렉션(garbage collection)

가비지 컬렉션에서 메모리 해제 기준을 객체가 만들어진 시점으로 각 객체를 서로 다른 세대(generation)로 나눈 값으로 정한다. 객체 헤더의 age 필드를 사용해 구분한다.

3. 패딩(Padding)

객체 전체의 크기가 CPU에서 처리하는 워드(word) 단위에 정렬되도록 빈 공간을 삽입한다. 예를 들어 User 객체의 전체 크기가 30바이트라면 패딩 2바이트를 추가하여 32바이트로 만든다.


4. 예제 계산해보기

Today 객체를 생성했다. today의 메모리 크기는 얼마인가?

JVM마다 다르겠지만 HotSpot 64-bit, COOPS 기준

- 객체 헤더의 크기는 12byte이다.

- 각 int는 4바이트이고 3개가 있으니 멤버 변수의 크기는 4 * 3 = 12byte이다.

- today의 객체의 크기 4byte

- 12 + 12 + 4 = 28byte에 만약 패딩을 추가한다면 총 32byte이다.

public class Today {
    int year = 2022;
    int month = 1;
    int day = 14;

    public static void main(String[] args) {
        Today today = new Today();
    }
}

5. JOL 패키지를 IntelliJ 플러그인에 손쉽게 사용하기

JOL(Java Object Layer)는 객체의 메모리 구조를 확인할 수 있는 패키지이다. 이를 Intellij 플러그인을 활용해서 손쉽게 사용할 수 있다.

Plugins - JOL 검색

 

설치 후 메모리 구조를 보고싶은 클래스.java 스크립트 파일에 우측 클릭하고 SHow Objet Layout 클릭
우측 네비게이션 바 JOL에서 결과를 확인할 수 있다.

위에서 계산했던 객체 헤더(12바이트)와 정수형 멤버 변수 3개(3*4바이트) = 24 바이트를 확인할 수 있다. 여기선 참조의 크기와 패딩은 계산되지 않았다.

⛓ reference

https://www.baeldung.com/jvm-compressed-oops

https://blog.fearcat.in/a?ID=00001-8ef18542-98b5-487c-aa4a-eeb46380cfae 

https://github.com/openjdk/jol

https://kukim.tistory.com/53?category=894691 

http://www.yes24.com/Product/Goods/103599789

'☕️ JAVA' 카테고리의 다른 글

최적의 시간 복잡도를 찾아서 🐠  (0) 2022.01.20
📕 소프트웨어의 품질과 그 특성들  (0) 2022.01.08
JAVA의 클래스  (0) 2021.11.01
JAVA의 제어문  (0) 2021.11.01
JAVA의 연산자  (0) 2021.11.01

댓글