ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java off-heap 메모리 알아보기
    Language/Java 2023. 12. 22. 19:01
    728x90
    반응형

    - 목차

     

    소개.

    Java 로 작성된 프로그램은 JVM 이라는 가상 머신 위에서 동작합니다.

    OpenJDK, Correto, Zulu 등에 해당하는 여러 JDK 는 JVM 이라는 Java 가상 머신을 생성하게 되는데요.

    Java 프로그램이 실행될 때마다 JVM 은 생성되고,

    JVM 이 전적으로 Java 프로그램을 매니징하게 됩니다.

    ByteCode 를 실행하고 실행에 필요한 메모리를 관리하는데 Heap, Stack, Class 등으로 표현되는 여러 메모리를 관리합니다.

    그리고 이러한 메모리들은 Garbage Collector 의 모니터링 대상이 되구요.

    Java 프로그램에서 생성하는 여러 객체들은 Garbar Collector 의 대상이 되므로

    프로그래머의 관리 대상에서 제외되는 편리함이 있습니다.

     

    오늘 소개할 off-heap memory 는 JVM 의 바깥에 위치하는 메모리로써

    운영체제로부터 할당받는 프로세스의 메모리 영역입니다.

    이번 글에서는 off-heap memory 가 무엇인지 알아보고 관련한 여러 예시들을 살펴볼 예정입니다.

     

    Off-Heap Memory.

    먼저 JVM 의 메모리 구조에 대한 이미지 하나를 공유하려고 합니다.

    아래 이미지는 Apache Flink 라는 스트림 프로세싱 프레임워크와 관련된 메모리 구조 이미지입니다.

     

    https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/memory/mem_setup_jobmanager/

     

    Java 프로그램이 실행되어 Java 프로세스가 되면 이는 크게 Process 와 JVM 영역으로 나눌 수 있습니다.

    그러니깐 OS 관점의 Process 와 그 내부에 JVM 이 위치하게 됩니다.

    JVM 은 하나의 가상 머신으로써 Java Byte Code 를 실행하게 되는데, JVM 은 그 내부에 메모리 영역을 추가적으로 두게 됩니다.

    마치 VMWare 같은 가상 머신이 가상의 하드웨어를 만드는 것처럼 JVM 도 ByteCode 의 실행을 위한 메모리 영역을 구축합니다.

    그렇다보니 Process 가 가지는 메모리 영역과 JVM 이 가지는 메모리 영역이 공존하게 됩니다.

    ( 사실 JVM 의 메모리 영역은 Process 의  메모리 영역 내부에 저장되긴 하지만. )

    그리고 Java 프로그램은 딱 JVM 의 메모리 영역만을 사용하도록 제한됩니다.

     

    Off-Heap Memory 는 이러한 JVM 메모리 영역 외의 Process 의 메모리 영역을 뜻합니다.

    그리고 Off-Heap Memory 를 사용한다는 의미는 Process 의 메모리를 사용한다는 뜻이 됩니다.

    Object object = new Object(); 와 같은 일반적인 방식으로 Off-Heap 메모리를 사용할 수는 없습니다.

    그래서 Java 에서는 java.nio.ByteBuffer 라는 클래스를 활용하여 Off-Heap Memory 를 사용하게 됩니다.

     

     

    Runtime memory.

    아래 코드는 Java 프로그램에서 현재 사용하는 JVM 메모리 상태를 파악하는 간단한 코드 예시입니다.

    public class TestByteBuffer {
      public static void main (String[] args) {
        Runtime runtime = Runtime.getRuntime();
        System.out.println("maxMemory is " + runtime.maxMemory() / 1024 / 1024 + "mb");
        System.out.println("freeMemory is " + runtime.freeMemory() / 1024 / 1024 + "mb");
        System.out.println("totalMemory is " + runtime.totalMemory() / 1024 / 1024 + "mb");
      }
    }
    javac TestByteBuffer.java
    java TestByteBuffer
    maxMemory is 8192mb
    freeMemory is 509mb
    totalMemory is 512mb

     

     

    위 코드를 실행하게 되면 위처럼 maxMemory, freeMemory, totalMemory 를 알 수 있습니다.

    totalMemory 는 현재 Java 프로그램이 사용할 수 있는 총 메모리 용량입니다.

    freeMemory 는 현재 남아있는 여유분의 메모리 용량이구요.

    maxMemory 는 Java 프로그램이 사용할 수 있는 메모리 제한입니다.

     

    몇 가지 예시를 더 들어보겠습니다.

     

    메모리를 계속 사용한다면 어떻게 될까 ?

    List 자료구조에 100000000 개의 Object 객체를 생성 후 할당합니다.

    List 자료구조에 의해서 Object 객체들은 Referencing 되므로 GC 되지 않습니다.

    따라서 메모리 사용량이 증가하겠죠 ?

    import java.util.ArrayList;
    import java.util.List;
    
    public class TestByteBuffer {
      public static void main (String[] args) {
    
        Runtime runtime = Runtime.getRuntime();
        List<Object> list = new ArrayList<>();
    
        System.out.println("initial maxMemory is " + runtime.maxMemory() / 1024 / 1024 + "mb");
        System.out.println("initial freeMemory is " + runtime.freeMemory() / 1024 / 1024 + "mb");
        System.out.println("initial totalMemory is " + runtime.totalMemory() / 1024 / 1024 + "mb");
    
        for (long i = 0; i < 10000000; i++) {
          list.add(new Object());
        }
    
        System.out.println("final maxMemory is " + runtime.maxMemory() / 1024 / 1024 + "mb");
        System.out.println("final freeMemory is " + runtime.freeMemory() / 1024 / 1024 + "mb");
        System.out.println("final totalMemory is " + runtime.totalMemory() / 1024 / 1024 + "mb");
    
      }
    }

     

    initial maxMemory is 8192mb
    initial freeMemory is 509mb
    initial totalMemory is 512mb
    final maxMemory is 8192mb
    final freeMemory is 3010mb
    final totalMemory is 5798mb

     

    위 결과처럼 totalMemory 는 512mb 에서 5798mb 로 늘어나게 됩니다.

    그러다가 maxMemory 를 초과하게 되면 OutOfMemory 상태가 됩니다.

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at java.base/java.util.Arrays.copyOf(Arrays.java:3720)
            at java.base/java.util.Arrays.copyOf(Arrays.java:3689)
            at java.base/java.util.ArrayList.grow(ArrayList.java:238)
            at java.base/java.util.ArrayList.grow(ArrayList.java:243)
            at java.base/java.util.ArrayList.add(ArrayList.java:486)
            at java.base/java.util.ArrayList.add(ArrayList.java:499)
            at TestByteBuffer.main(TestByteBuffer.java:15)

     

    MaxMemory 수정하기.

    아래와 같은 방식으로 maxMemory 값을 수정할 수 있습니다.

    기본 Max Memory 가 8gb 인 8192mb 상태인데요.

    아래처럼 128mb 로 변경하게 되면 약간의 메모리 할당으로도 프로그램이 종료될 겁니다.

    javac TestByteBuffer.java
    java -Xmx128m TestByteBuffer

     

    java.nio.ByteBuffer

    java.nio.ByteBuffer 는 Java 프로그램의 객체들을 Off-Heap Memory 로 저장할 수 있도록 하는 Java Package & Class 입니다.

    java.nio 패키지의 ByteBuffer 클래스를 통해서 Off-Heap Memory 즉 Process 의 메모리에 접근할 수 있게 됩니다.

    아래는 관련된 예시 코드 입니다.

     

    import java.nio.ByteBuffer;
    
    public class TestByteBuffer {
      public static void main (String[] args) {
    
        Runtime runtime = Runtime.getRuntime();
        System.out.println("initial maxMemory is " + runtime.maxMemory() / 1024 / 1024 + "mb");
        System.out.println("initial freeMemory is " + runtime.freeMemory() / 1024 / 1024 + "mb");
        System.out.println("initial totalMemory is " + runtime.totalMemory() / 1024 / 1024 + "mb");
    
        ByteBuffer byteBuffer = ByteBuffer.allocate(8 * 100000000);
    
        for (long i = 0; i < 100000000; i++) {
          byteBuffer.putLong(new Long(i));
        }
    
        System.out.println("final maxMemory is " + runtime.maxMemory() / 1024 / 1024 + "mb");
        System.out.println("final freeMemory is " + runtime.freeMemory() / 1024 / 1024 + "mb");
        System.out.println("final totalMemory is " + runtime.totalMemory() / 1024 / 1024 + "mb");
        byteBuffer.flip();
    
        System.out.println("first value is " + byteBuffer.getLong());
        System.out.println("last value is " + byteBuffer.getLong(8 * 100000000 - 8));
    
      }
    }
    initial maxMemory is 8192mb
    initial freeMemory is 509mb
    initial totalMemory is 512mb
    final maxMemory is 8192mb
    final freeMemory is 492mb
    final totalMemory is 1276mb
    first value is 0
    last value is 99999999

     

    10000000개의 Long 객체를 생성하지만, JVM 의 메모리에는 큰 변경이 없습니다.

    10000000개의 Long 객체들은 Serialized 되어 Off-Heap Memory 에 저장되기 때문입니다.

    이러한 방식으로 JVM 의 메모리 사용량을 줄일 수 있습니다.

    반응형

    'Language > Java' 카테고리의 다른 글

    [Java NIO] NIO 는 어떻게 동작할까 ? (DMA, Kernel Space)  (0) 2024.01.31
    Jackson 으로 JSON 다루기  (0) 2024.01.22
    Java Thread Pool 알아보기  (0) 2023.09.30
    Java Future 알아보기  (0) 2023.09.30
    java Annotation 이해하기  (0) 2023.09.25
Designed by Tistory.