Java/개념

Java 메모리 최적화가 어떻게 되는지 과정

TheWing 2021. 2. 25. 00:13

Java 메모리 최적화가 어떻게 되는지 과정

메모리 최적화 목적

  • 예를들어 아래 예제에서 Penguin객체가 하나가 아닌 Penguin[100]과 같이 요소가 100개인 배열이라고 할 때 힙영역에 생기는 Penguin 객체는 100개가되고 test() 메서드도 각 Penguin 객체에 따라서 힙에 100개가 만들어져야한다. 그렇지만 객체 멤버 메서드는 각 객체별로 달라지는 것이 아니다. 객체 멤버 메서드에서 사용하는 객체 멤버 속성의 값만 다를 뿐이고 똑같은 객체 멤버 메서드인 test() 메서드를 힙 영역에 100개나 만든다는 것은 심각한 메모리 낭비이다.
  • 이를 해결하기 위해 JVM은 지능적으로 객체 멤버 메서드 test()를 스태틱 영역에 단 하나만 보유하게된다. 눈에는 보이지 않지만test() 메서드를 호출할 때 객체 자신을 나타내는 this 객체 참조 변수를 넘기게 된다.

아래 예제와 같이 코드를 구현을 하고 메모리 최적화가 어떻게 되는지 예상을 해보겠다

예제

package kr.javaPractice.JavaOOP.TMemory;

class Penguin {
    void test(String name) {
        System.out.println("Test");
    }
}

public class Driver {
    public static void main(String[] args) {
        Penguin pororo = new Penguin();
        Penguin pororo1 = new Penguin();
        Penguin pororo2 = new Penguin();

        pororo.test();
        pororo1.test();
        pororo2.test();
    }
}
  • 이렇게 구현하였을때 예상 되는 T메모리의 구조는 예상하였던것은 아래 그림과 같다.

  • 바이트 코드를 보도록 하자

Driver

// class version 55.0 (55)
// access flags 0x21
public class kr/javaPractice/JavaOOP/TMemory/Driver {

  // compiled from: Driver.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 15 L0 // 15번째 라인
    ALOAD 0 // local variable 0에 있는 것을 stack에 올린다
    INVOKESPECIAL java/lang/Object.<init> ()V //Java.lang.Object instance method 를 호출후 결과를 stack에 올린다
    RETURN //결과값 반환
   L1
    LOCALVARIABLE this Lkr/javaPractice/JavaOOP/TMemory/Driver; L0 L1 0 //지역 변수 저장
    MAXSTACK = 1 
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 17 L0 
    NEW kr/javaPractice/JavaOOP/TMemory/Penguin //Penguin 객체 생성
    DUP //stack의 top에 값을 복사
    INVOKESPECIAL kr/javaPractice/JavaOOP/TMemory/Penguin.<init> ()V //인스턴스 메소드를 호출하고 결과를 스택에 올린다
    ASTORE 1 // Local Variable 1에 저장
   L1
    LINENUMBER 18 L1
    NEW kr/javaPractice/JavaOOP/TMemory/Penguin
    DUP
    INVOKESPECIAL kr/javaPractice/JavaOOP/TMemory/Penguin.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 19 L2
    NEW kr/javaPractice/JavaOOP/TMemory/Penguin
    DUP
    INVOKESPECIAL kr/javaPractice/JavaOOP/TMemory/Penguin.<init> ()V
    ASTORE 3
   L3
    LINENUMBER 21 L3
    ALOAD 1 //Local Variable 1에 있는 값을 스택에 올리기
    INVOKEVIRTUAL kr/javaPractice/JavaOOP/TMemory/Penguin.test ()V //가상 메소드를 호출하고 결과를 스택에 올린다
   L4
    LINENUMBER 22 L4
    ALOAD 2
    INVOKEVIRTUAL kr/javaPractice/JavaOOP/TMemory/Penguin.test ()V
   L5
    LINENUMBER 23 L5
    ALOAD 3
    INVOKEVIRTUAL kr/javaPractice/JavaOOP/TMemory/Penguin.test ()V
   L6
    LINENUMBER 27 L6
    RETURN
   L7
    LOCALVARIABLE args [Ljava/lang/String; L0 L7 0
    LOCALVARIABLE pororo Lkr/javaPractice/JavaOOP/TMemory/Penguin; L1 L7 1
    LOCALVARIABLE pororo1 Lkr/javaPractice/JavaOOP/TMemory/Penguin; L2 L7 2
    LOCALVARIABLE pororo2 Lkr/javaPractice/JavaOOP/TMemory/Penguin; L3 L7 3
    MAXSTACK = 2
    MAXLOCALS = 4
}

Penguin

// class version 55.0 (55)
// access flags 0x20
class kr/javaPractice/JavaOOP/TMemory/Penguin {

  // compiled from: Driver.java

  // access flags 0x0
  <init>()V
   L0
    LINENUMBER 5 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lkr/javaPractice/JavaOOP/TMemory/Penguin; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x0
  test()V
   L0
    LINENUMBER 7 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; //static 필드 값을 클래스로 부터 가져온다
    LDC "Test" //상수 풀에서부터 #index에 해당하는 상수를 가져와서 올린다
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE this Lkr/javaPractice/JavaOOP/TMemory/Penguin; L0 L2 0 //지역변수 (Penguin this)가 생긴다
    MAXSTACK = 2
    MAXLOCALS = 1
}
  • LOCALVARIABLE this Lkr/javaPractice/JavaOOP/TMemory/Penguin; L0 L2
    • 지역변수 this로 인해서 static 영역에 차지하게 된다

Driver.class파일

package kr.javaPractice.JavaOOP.TMemory;

public class Driver {
    public Driver() { /* compiled code */ }

    public static void main(java.lang.String[] args) { /* compiled code */ }
}

Penguin.class 파일

package kr.javaPractice.JavaOOP.TMemory;

class Penguin {
    Penguin() { /* compiled code */ }

    void test(java.lang.String name) { /* compiled code */ }
}
  • Penguin.class 파일을 보면 void test(String name) 이렇게 컴파일 되어있다
  • 바이트코드와 클래스파일 해석하면 heap에 penguin,penguin1,penguin2이 생성되었고 static test메서드에 매개변수인 (Penguin this) 로 사용하게 된다.
  • 실제 T메모리 구조는 아래와 같다

  • 이렇게 스택프레임에 쌓여서 사용하게된다
    • Penguin.test(pororo)
    • Penguin.test(pororo1)
    • Penguin.test(pororo2)
  • heap 영역에는 test() 가 static으로 변경되었다.

코드로 구현해 보자

class Penguin {
    static void test(Penguin this) {
        System.out.println("Test");
    }
}

public class Driver {
    public static void main(String[] args) {
        Penguin pororo = new Penguin();
        Penguin pororo1 = new Penguin();
        Penguin pororo2 = new Penguin();
        Penguin.test(pororo);
        Penguin.test(pororo1);
        Penguin.test(pororo2);
    }
}

결론

  • JVM이 필요없는 메모리를 최적화 하기 위해 자체적으로 컴파일할때 static 메서드로 변경해주는거 같다
  • 잘못된 부분이 있으면 지적 부탁드립니다.