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 메서드로 변경해주는거 같다
- 잘못된 부분이 있으면 지적 부탁드립니다.
'Java > 개념' 카테고리의 다른 글
Java 8 ZonedDateTime vs OffsetDateTime 어떤 상황에서 쓰는게 적합한가? (0) | 2022.08.19 |
---|---|
Java 8 LocalDateTime vs Instant 어떤 상황에서 쓰는게 적합한가? (0) | 2022.08.09 |
JVM 동적 클래스 로딩 (0) | 2021.02.24 |
[Java] Java 8에 추가된 LocalDate, LocalTime, LocalDateTime (0) | 2020.11.02 |
[Java] Date, Calendar클래스가 왜 Deprecated됐는지? (0) | 2020.11.02 |