Java/Java 문법

[JAVA] 지역변수 vs 전역변수 (feat. java8 JVM Static Object Heap Area)

민돌v 2022. 9. 26. 16:34
728x90

 

자바 지역변수 vs 전역변수 .. 이 글을 읽고있는 당신의 선택은!?!?

궁금증의 시작

 

 

📗 고민의 시작은 이러했습니다.

단 하나의 메소드에서만 사용되는 지역변수를 전역변수로 뺀다면 메모리 낭비가 이루어지지않을까 ??

➡️ 전역변수로 지정해줄때의 장점은 뭐지? 어떤상황에서 전역변수가 더 유용하게 사용되는거지??

 

📝 요약

더보기
  1.  java 8 이후 static object 는 jvm 힙 메모리 영역에서 관리된다. 고로 garbage collector 의 영향을 받느다.
  2. 지역변수 vs 전역변수의 선택은 사용성에 집중하자. 
    • 메모리를 아끼기위해 지역변수로 선언했지만, 해당 메소드에 많은 생성과 파괴가 일어나면 io 자원을 많이 잡아먹게된다.
    • 이런 상황에서 전역변수는 유용하다. 잘생각해보자!
  3. 자바 8 이후 static object는 메모리의 큰 영향이 가지 않을거같다는 예상이 든다

 

 

[목차]

  1. 지역변수 vs 전역변수
  2. 각각의 장단점과, 유용하게 사용되는 경우
  3. java 8 이후 jvm 구조의 변화
  4. static object 의 가비지콜랙터 동작 시점 (GC)

 


 

1.  지역변수 vs 전역변수

📙 지역변수로 선언했을 시 장점

지역변수의 목적

  • 지역변수는 선언된 필드 즉, 괄호({ }) 안에서만 생명주기를 같습니다.
  • 생성된 필드가 종료될 때 지역변수에 할당된 메모리는 해제되는 것 이죠
  • 따라서 메모리 이슈를 걱정하지 않아도 되고, 알지 못하는 어딘가에서 값이 변경될 걱정을 하지 않아도 됩니다.

지역변수의 단점

  • 단점이라기 보단, 당연한건데 지역변수는 계속해서 메모리를 할당해주고 해제해줍니다.
  • OS의 자원을 지속적으로 사용한다는 것 입니다.

 

 

📗 전역변수로 선언시 장점

전역변수의 목적

  • static 메모리 영역에 미리 데이터를 올려놓아 어디서든 새로 객체를 만들지 않고(혹은 선언하지않고) 이미 선언된 객체를 사용할 수 있도록 하는게 전역변수입니다.
  • 여기서의 KeyPoint 는 "메모리를 미리 올려놓고, 해제하지 않는다." 라고 생각합니다.

 

전역변수를 지양하라고 하는 이유

  • 구글링을 해보면 보통 전역변수를 지양하라고 하는데 아래의 2가지 이유때문이라는 생각이 들었습니다.
    1. 전역적으로 어디서든 접근이 가능하기 때문에 어디서 변경이 일어나는지 알 수 없다
    2. 메모리 누수로 이어질 수 있다.

 

전역변수의 문제점 개선

1. 전역변수의 제어

  • 이 문제는 다양한 방법과 관점으로 해결이 가능할 것 같습니다.

 

(1) 정말 전역적으로 상수를 담아 어디서든 접근해서 값을 사용할 때

  • [1] final 을 사용한다면 상수에서는 불변에 가깝게 사용이 가능합니다. 
  • 따라서 값이 변경될 두려움에 느낄 필요가 없습니다.
@Test
public void test_final_primitive_variables() {
	final int x = 1;
	//x = 3; //한번 assign되면 변경할 수 없음.
}

(2) 객체에 상수를 담을 때

  • 객체에 상수를 담는다면, 객체를 final 로 선언한다고 하여도 final immutable 하지 않기 때문에 객체의 필드 변경까지는 막을 수 없습니다.
@Test
public void test_final_reference_variables() {
	final Pet pet = new Pet();
	//pet = new Pet(); //다른 객체로 변경할수 없음

	pet.setWeight(3); //객체 필드는 변경할 수 있음
}
  • 이럴때 물론 Pet 객체의 모든 필드에 final 을 붙여 문제를 해결할 수 있지만, 객체가 생성되는 것 까지는 막을 수 없습니다.
  • 불필요한 메모리 낭비로 이루어지고 이를 [2]추상클래스(abstrac)로 막을수 있습니다.
  • 추상클래스 자세한 내용

 

(3) 클래스 내부에서만 사용하는 전역변수

  •  하나의 클래스 내부에서만 사용하기위해
  • 저는 private static final 을 사용하여 재할당과 무한한 접근을 차단해서 사용합니다.

 

 

2. 메모리 누수

제가 고민했던 가장 큰 부분입니다.

  • 아무리 작은 바이트가 올라간다고 해도, 위 3가지 경우에 static을 남발하게 된다면 헤비한 프로젝트에서는 메모리 이슈가 터져버리지 않을까...?? 했는데
  • 이런 고민이 jvm 에서 static object heap 영역으로 가면서 어는정도 해소가 되었습니다.

 

 

📘 그렇다면 지역변수와 전역변수를 선택하는 기준

 

🔥 지역변수 vs 전역변수의 선택은 사용성에 집중하자. 

  • 사실 static 을 선언해도 많은 메모리를 잡아먹지는 않습니다.
  • 객체나 메소드를 static 으로 선언해도 모든 부분이 메모리에 올라가는 것이 명령어만 Static Area 저장되기 때문에 static 으로 메모리 누수가 일어날 일은 많이 없을거라고 생각합니다.

🔥 Java 8 이후 jvm gc

  • 아래에서 공부해볼 거지만, java 8 이후 static object 가 불변 영역에서 힙 영역으로 관리되는 저장위치가 변경되었습니다.
  • 즉, 자바에서 정적 메모리들의 누수를 잡아보겠다는 의지? 가 보입니다..ㅎㅎ

즉!!

  • 메모리를 아끼기위해 지역변수로 선언했지만, 해당 메소드에 많은 생성과 파괴가 일어나면 io 자원을 많이 잡아먹게된다.
  • 이런 상황에서 전역변수는 유용하다. 잘생각해보자!

 

 

 


2. java 8 이후 jvm

 

JVM 의 구조

https://steady-coding.tistory.com/305

 

JVM의 구조는 위 사진과 같습니다.

(1) 정적 변수 및 상수등과 같은 바이트 코드가 보관되는 Method Area

(2) 객체와 배열등의 주소값들이 저장되는 Heap Area

(3) primitive(원시타입) 변수들이 저장되는 Stack Area

 


JAVA 8 이전


📌 자바 8 이전에는 Static Object (정적 객체) 들은 Heap 영역에서 Permanent Generation 영역에 저장이되어, GC의 대상이 되지 않았습니다.

https://steady-coding.tistory.com/603

 

 

📌 "하지만 자바 8 이후부터 JVM 에서 Static Object 가 GC의 대상이 됩니다" 이게 무슨말일까요?? (한참 고민했습니다..ㅎ)

 

 

JAVA 8 이후


자바 8이후의 힙 영역을 보면, Permanent 영역(JVM 에 의해 크기를 강제함) 이 사라지고, MetatSpace가 생겼습니다.

MetaSpace 라고 하는 네이티브 메모리(OS에서 크기를 유동적으로 조절) 에서 Permanent 에서 관리하고있던 정보를 대부분 들고있도록 변경이 되었습니다.

https://steady-coding.tistory.com/603

 

대부분이라는 말이 있듯이 전부 MetaSpace에서 관리하는 것은 아니고 Static Object가 일반 힙 영역에서 관리되도록 변경되었습니다.

따라서 Static (정적) 임에도 불구하고 Garbage Collection 의 대상이 되는거죠

 

 

왜 Permanent 영역이 사라지고 MetaSpace 가 생길걸가요?


간단하게말해 OOM( “java.lang.OutOfMemoryError: PermGen space” ) 에러를 만나지 않도록 개선한 것 입니다.

  • 만약에 정적으로 선언된 Collection 객체가 있다면, 이 객체가 메모리를 어디까지 쌓아갈지 모르고 드믄 케이스로 OOM 이 발생하는 것이죠
  • 또한 Heap 영역은 JVM에 의해 관리되는 영역이고, Native 메모리, 즉 메타스페이스는 OS 레벨에서 관리하는 영역으로 구분됩니다.

📌 Metaspace 가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 된 것입니다. 

 

 


👏🏻 3. Static Object GC

그럼 Static Object 의 GC 시점이 언제일까요

위의 내용이 담긴 jdk 문서를 읽었을 때, static object를 사용해야하는 이유를 고민했습니다.

  1. static object 도 똑같이 GC의 대상이 된다면, 굳이 정적 키워드를 사용해아하는 필요성이 있는가?
  2. 일반 객체와의 차이점은 무엇인가??

 

 

그저 감사감사... 한번사수는 영원한 사수!?

 

퇴사하신 전 사수분에게 여쭤보니 ㅋㅋㅋㅋ 그렇다고 합니다.

정보의 출처는 "JVM PerFormance Optimizing 및 성능분석 사례" 도서 입니다.

 

 

GC 의 과정

  1. 객체가 생성되면 Eden 영역에 생성되고
  2. 시간이 지남에 따라, Survivor1 or 2 영역으로 이동됩니다. (그저 분산?)
  3. Survivor1 / Survivor2를 왔다 갔다 하는 과정에서 오랫동안 살아남은 객체들은 Old 영역으로 이동합니다.
  4. 보통 Old 영역은 Young 영역보다 크게 할당하며, 이러한 이유로 Old 영역에서 발생하는 Major GC는 young 영역에서 발생하는 Minor GC 보다 적게 일어납니다.
  5. 객체의 크기가 아주 큰 경우(survivor 영역이 감당하지 못할 떄) 바로 Old 영역에 가기도 합니다.

 

 

Permanet 가 MetaSpace 가 되면서 바뀐점

공식문서에 나온 문장은 아래와 같습니다.

The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap. Hotspot will explicitly allocate and free the native memory for the class meta-data.

 

해석하자면 class meta-data는 native memory로 이동된 Metaspace에 저장하고 permanent에 저장했던 interned strings와 static 변수는 heap 영역으로 보낸다는 의미가 된다고 합니다. 

 

뭔가 좀 헷갈리는데,  https://8iggy.tistory.com/229 이분이 해석을 해놓은 내용이고, 맞는지 아닌지는 저는 아직 잘모르겠습니다.. 혹시 몰라 가져왔으니 참고만..!!

 

더보기

해석하자면 class meta-data는 native memory로 이동된 Metaspace에 저장하고 permanent에 저장했던 interned strings와 static 변수는 heap 영역으로 보낸다는 의미가 된다. Java8부터는 static 변수를 heap영역에서 관리함은 gc 대상이 될 수 있음을 의미한다. 으레 다들 설명하듯 PermGen에 속한 Method area가 클래스 변수를 저장한다고 알고 있다면 이해하기 쉽지 않다. static 변수는 클래스 변수로 명시적 null 선언이 되지 않으면 gc되어서는 안되는 변수다. Method area가 클래스 변수를 저장한다고 이해하는 시점에서 오해가 발생한다 Method area는 class의 meat-data를 저장할 뿐 실질적인 객체와 데이터는 Method area 바깥의 PermGen에 저장됨을 알아야 한다.

class meta-data가 metaspace로 이동하고 기존에 perment 영역에 저장되던 static object는 heap영역에 저장되도록 변경되었다고 설명하는데 이는 reference는 여전히 metaspace에서 관리됨을 의미하기에 참조를 잃은 static object는 GC의 대상이 될 수 있으나 reference가 살아있다면 GC의 대상이 되지 않음을 의미한다.

따라서, metaspace는 여전히 static object에 대한 reference를 보관하며 애매하게 heap에 걸쳐지지 않고 non-heap(native memory)로 이관되며 static 변수(primitive type, interned string)는 heap 영역으로 옮겨짐에 따라 GC의 대상이 될 수 있게끔 조치한 것이다.

이 내용을 언급하는 이유는 클래스 변수 및 객체의 저장위치와 클래스 메타 정보의 위치가 Method 영역이 속한 ParmGen으로부터 Heap과 메모리로 서로 분리되었다는 점을 의미하기 때문이다.

 

 

 

 

 

 

 

 

 

 


*참고

반응형