JAVA,객체지향

JAVA /기본 가비지 컬렉터 튜닝

25G 2023. 9. 6. 17:00

기본 가비지 컬렉터 튜닝

비록 가비지 컬렉터 알고리즘 마다 힙을 처리하는 방식은 다르지만 기본적인 환경 설정 매개변수는 공유합니다.

힙 크기 정하기

가비지 컬렉터의 첫번째 기본 튜닝은 애플리케이션 힙 크기 입니다. 이는 힙의 제너레이션의 크기에 영향을 주는 고급 튜닝입니다.

대부분의 성능 문제에서 힙크기를 결정하는 것은 균형의 문제 입니다. 힙이 너무 작다면 프로그램 가비지 컬렉터를 수행하는 시간이 너무 길고 애플리케이션 로직을 수행하는데 충분한 시간을 쓸 수 없습니다.

하지만 단순히 매우 큰 힙을 쓰는것도 올바르지 않습니다. 가비지컬렉터 중단 시간은 힙의 크기에 좌우 되므로 힙의 크기가 늘어난다면 중단 시간도 늘어납니다.

즉 중단은 덜 일어나지만 한번 일어나면 크게 지연될 수 있습니다.

그리고 매우 큰 힙을 사용하는건 또 다른 위험이 있을 수 있습니다. 컴퓨터 운영체제는 머신의 물리 메모리를 관리하기 위해 가상 메모리를 사용합니다.

이와 같은 경우에 물리 메모리를 다 쓴다면 운영체제는 스와핑을 통해 프로그램의 비활성 영역을 디스크로 쓸 것입니다. 그리고 이 부분이 다시 필요하다면 운영체제는 디스크에서 RAM으로 가져올 것입니다.

만약 풀 가비지 컬렉터가 일어나는 경우에 스와핑이 일어난다고 가정하면 이 중단 시간은 몇배나 더 커질 수 있습니다.
백그라운드 스레드가 힙을 살펴보며 지나갈 때 디스크에서 메인 메모리로 데이터 복사가 늦어지면서 오래 대기하게 된다면 비용이 비싼 풀 가비지컬렉터를 겪습니다.

그러므로 힙 크기 설정에서 첫 번째 규칙은 머신 내의 물리적인 메모리의 크기보다 더 큰 힙을 지정하지 않는 것입니다. JVM이 여러 개 실행되고 있다면 모든 힙의 총합은 물리적인 메모리보다 항상 작아야 합니다.

그리고 다른 애플리케이션을 위한 메모리 뿐 아니라 JVM 자체를 위한 공간 일부를 남겨 놔야 합니다. 일반적으로 OS 프로파일을 위해서 적어도 1GB 메모리 여분은 필요합니다.

힙의 크기는 두가지 값인 초기값(-XmsN)과 최대 값(-XmsN)에 의해 제어됩니다. 이에 대한 디폴트는 운영체제나 시스템 RAM의크기 와 사용중인 JVM에 따라 다릅니다.

JVM은 가용 시스템 자원을 기반으로 힙에 대한 합리적인 디폴트 초기 값을 찾고 애플리케이션에 메모리가 더 필요하다면 힙사이즈를 알아서 올립니다.
즉 힙의 초기 크기와 최대 크기가 있으므로 JVM은 가비지 컬렉션이 너무 많이 일어난다면 알아서 힙을ㅈ ㅣ속적으로 늘립니다.
만약 그럼에도 가비지 컬렉션 시간이 많다면 -Xms 플래그를 통해 힙크기를 늘릴 필요가 있습니다. 대플리케이션에서 필요로 하는 힙의 크기를 정확하게 알고 있다면 초기 값과 최대 값을 똑같이 하도록 적용한다면 힙 크기의 재조정을 할 필요가 없으므로 가비지 컬렉터는 약간 더 효율적이 됩니다.

제너레이션 크기 정하기

일단 힙 크기가 결정되면 그 다음 사항은 영 제너레이션에 힙을 얼마나 할당할지 올드 제너레이션에 얼마나 할당할지 결정해야 합니다.
영 제너레이션이 비교적 더 크다면 덜 자주 수집되고 더 적은 객체가 올드 제너레이션으로 갈 것입니다. 그러므로 풀 가비지컬렉터가 더 자주 일어납니다.

여기서도 둘 사이에 균형을 유지하는 것이 관건입니다.

가비지 컬렉터 알고리즘 마다 각자 다른 방식으로 균형을 유지하지만 모든 가비지컬렉터 알고리즘은 제너레이션 크기를 정하는데 공통적인 플래그를 사용합니다.

제너레이션의 크기를 튜닝하기 위한 커멘드 라인 플래그는 모두 영 제너레이션 크기를 조정합니다. 올드 제너레이션은 영 제너레이션을 뺀 나머지로 할당됩니다.

플래그 종류

  • -XX:NewRatio=N

    • 올드 제너레이션과 영 제너레이션의 비율 설정
  • -XX:NewSize=N

    • 영 제너레이션의 초기 크기 설정
  • -XX:MaxNewSize=N

    • 영 제너레이션의 최대 크기 설정
  • -XmnN

    • NewSize와 MaxNewSize에 동일한 값을 설정

영 제너레이션은 디폴트 값 2 인 NewRatio로 설정됩니다. 이를 바탕으로 초기 영 제너레이션의 크기를 정하는 방적식이 사용됩니다.

  • 초기 영 제너레이션 크기 = 초기 힙 크기 / (1 + NewRatio)

영 제너레이션의 크기는 NewSize로 설정할 수 있다고 했습니다. NewSize로 값을 세팅하면 NewRatio 방정식으로 나온 값보다 우선으로 선택됩니다.

병렬성 제어하기

시리얼 컬렉터를 제외한 모든 가비지컬렉터 알고리즘은 여러개의 스레드를 사용합니다. 이 스레드의 개수는 -XX:ParallelGCThread=N 플래그로 제어됩니다.
이 플래그의 값은 다음 동작에서 사용되는 스레드의 개수에 영향을 미칩니다.

  • -XX:+UseParallelGC를 사용할 때 영 제너레이션 컬렉션

  • -XX:+UseParallelOldGC를 사용할 때 올드 제너레이션 컬렉션

  • -XX:+UseParNewGC를 사용할 때 영 제너레이션 컬렉션

  • -XX:+UseG1GC를 사용할 때 영 제너레이션 컬렉션

  • CMS의 모든 애플리케이션 스레드 중단 단계 (풀 GC 제외)

  • G1의 모든 애플리케이션 스레드 중단 단계 (풀 GC 제외)

이 GC 동작은 애플리케이션 스레드가 실행되는 걸 막습니다. 그러므로 JVM은 중지 시간을 최소화 하기 위해 가능한 많은 CPU 자원을 사용해 이를 극복하려고 합니다.

기본적으로 JVM은 한 머신내의 각 CPU당 한 스레드씩 8개를 기본으로 합니다. 그리고 임계치에 다다르면 JVM은 CPU 한 개마다 5/8씩 신규 스레드를 추가합니다.

해당 방정식

  • ParallelGCThreads = 8 + ((N - 8) * 5 / 8)

128-CPU에서 가비지 컬렉터 스레드는 83개가 될것입니다. 8개의 CPU가 달린 머신에서 8개의 스레드는 좀 많을 수 있습니다. 오히려 4~6개의 스레드가 더 적합할 수 있습니다.

머신에 한개 이상의 JVM이 실행되고 있다면 가비지컬렉터 스레드 개수는 제한하는게 낫습니다.

예를 들어 JVM 4개가 실행되고 있는 16개의 CPU에서는 한 JVM당 가비지컬렉터 스레드 개수는 13개가 됩니다. 그러므로 가비지컬렉터 작업할 땐 52개의 가비지컬렉트 스레드가 경쟁적으로 CPU를 얻으려고 할 것입니다.
이런 경우에는 비효율적이기 때문에 JVM당 가비지 컬렉터 개수를 4개로 조정하는 것이 낫습니다.