본문

테크
[WhaTip] 예시코드로 GC모니터링 실습

작성일 2023년 07월 12일

[WhaTip] Garbage Collection의 개념 및 동작에 이어 이번 콘텐츠에서는 아래의 간단한 코드로 이루어진 Java 어플리케이션의 GC를 모니터링해보겠습니다.

GC 모니터링 실습하기

   
                import java.util.ArrayList;
                public class Main {
                    public static void main(String[] args) throws Throwable{
                        ArrayList buf = new ArrayList();
                        for (int i=0; true; i++){
                            if ( i>0 && i % 100000 == 0 ){
                                System.out.println(green("Current Buf.size() = "+buf.size()));
                                Thread.sleep(1000);
                            }
                            buf.add(new String("abcdefghijklmnopqrstuvwxyz"));
                        }
                    }
                    private static String green(String s){
                        return "\u001B[32m"+s+"\u001B[0m";
                    }
                }
              
   

해당 코드는 다음과 같이 동작합니다.

  1. ArrayList에 스트링 객체를 삽입 (new를 통해 메모리 할당)
  2. ArrayList의 사이즈가 100000으로 나누어 떨어질 때 현재 ArrayList 사이즈 출력

위의 예제 프로그램은 필연적으로 Minor GC 더 나아가 Full GC(Major GC)가 발생할 수밖에 없습니다. (지속적인 메모리 할당)


1. verbose:gc 옵션으로 GC 로깅하기

verbose:gc 옵션과 함께 어플리케이션을 실행 시 아래와 같이 GC에 대해 로깅을 하게 됩니다.

   
                java -verbose:gc Main
              
   
AWS CloudFormation Template
해당 로그를 file로 저장해, 확인하는 방법도 있지만, Minor GC의 경우 빈번하게 일어나고 중요하지도 않기에 Full GC(Major GC)만 따로 확인하고 처리할 수 있는 방법이 필요합니다. (알림 등)


2. GarbageCollectorMXBeans을 통한 Full GC 모니터링

Full GC 모니터링의 핵심은 두 가지입니다.

  1. Old Generation 영역을 관장하는 Garbage Collector 찾는 것
  2. 찾은 Garbage Collector의 collection 수의 증가를 감지 → Full GC


2가지의 작업을 위해 우리는 GarbageCollectorMXBeans를 사용할 수 있습니다. GarbageCollectorMXBeans는 JVM GC를 위한 관리 인터페이스입니다. 해당 인터페이스를 이용하여 다음과 같은 작업을 할 수 있습니다.


  1. MemoryManger 이름을 획득 ( Garbage Collector는 MemoryManger의 한 종류)
  2. MemoryManger가 관리하는 MemoryPool 이름 획득
  3. 발생한 GC Collection 수를 획득
  4. Collection의 수집 시간 획득 (누적, ms)
   
                import java.util.Arrays;
                import java.lang.management.ManagementFactory;
                import java.lang.management.GarbageCollectorMXBean;
                public class Main {
                    public static void main(String[] args) throws Throwable{
                        for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()){
                            System.out.println("MemoryManger : "+bean.getName());
                            System.out.println("MemoryPoolNames : "+Arrays.toString(bean.getMemoryPoolNames()));
                        }
                    }
                }
              
   
AWS CloudFormation Template

이제 OldGeneration 영역을 담당하는 MemoryManger를 찾으면 됩니다. 위의 결과만 봐서는 G1 Old Gen을 관리하는 MemoryManger가 될 것 같지만, 아래의 그림과 같이 각 GC 별로 MemoryPool의 이름이 다릅니다.

AWS CloudFormation Template

그래서 MemoryPoolName을 이용해서 OldGeneration을 관리하는 MemoryManger를 찾는 것이 아닌 Minor GC가 Major GC보다 빨리 일어나는 점을 통해서 OldGeneration을 관리하는 MemoryManger를 찾습니다.

즉 Collection가 가장 적은 MemoryManger가 OldGeneration를 관리한다고 할 수 있습니다.


해당 원리를 이용한 Full GC 모니터링을 하는 코드는 아래와 같습니다.

   
                import java.lang.management.GarbageCollectorMXBean;
                import java.lang.management.ManagementFactory;
                import java.util.Arrays;
                import java.util.HashMap;
                import java.util.Map;
                public class PrintGC{
                    static String oldGenGcName="";
                    static Map gcStatMap = new HashMap();
                    public static void print(){
                        long fullGcDelta=0;
                        for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()){
                            String beanName=bean.getName();
                            long newCount = bean.getCollectionCount();
                            if (beanName.equals(oldGenGcName)){
                                long oldCount = gcStatMap.get(beanName);
                                fullGcDelta = newCount-oldCount;
                            }
                            gcStatMap.put(beanName, newCount);
                        }
                        findOldGenGC();
                        if (fullGcDelta>0){
                            System.out.println(yellow(gcStatMap+" OldGenGC is ["+ oldGenGcName + "] FULL GC #"+fullGcDelta));
                        }
                    }
                    private static void findOldGenGC(){
                        if (oldGenGcName.equals("")==false)return ;
                        String foundName = "";
                        long minGcCount = Long.MAX_VALUE;
                        for (String gcName : gcStatMap.keySet()){
                            long gcCount = gcStatMap.get(gcName);
                            if (gcCount < minGcCount){
                                foundName = gcName;
                                minGcCount = gcCount;
                            }else if (gcCount==minGcCount){
                                foundName="";
                            }
                        }
                        if (foundName.equals("")==false){
                            System.out.println(yellow("Found OldGenGC=" + foundName));
                        }
                        oldGenGcName=foundName;
                    }
                    private static String yellow(String s){
                        return "\u001B[33m"+s+"\u001B[0m";
                    }
                }
              
   

어려워 보일 수 도 있지만 사실 원리를 간단합니다.

• findOldGenGC() : OldGeneration을 관리하는 MemoryManger를 찾음
  • Collection 개수가 가장 적은 MemoryManger를 찾음
  • Collection 개수가 가장 적은 MemoryManger가 두개 → 찾지 못함
→ Major GC와 Minor GC가 동시에 일어났을 가능성 템플릿 작성에 걸리는 시간이 오래 걸립니다.
• print() : 각 MemoryManger의 Collection 개수를 저장, FullGC가 발생했을 때 출력
  • 이미 이전에 OldGeneration을 관리하는 MemoryManger를 찾은 경우
→ OldGeneration을 관리하는 MemoryManger의 Collection 개수가 이전보다 증가했을 때 FullGC 발생했다 판단해 출력 OldGeneration을 관리하는 MemoryManger를 아직 찾은 경우 → findOldGenGC()

3. Result

위의 클래스를 맨 처음 Java 어플리케이션에 적용하면 다음과 같은 결과가 도출됩니다.

AWS CloudFormation Template
  1. 이전과 다르게 Minor GC는 출력되지 않고, Full GC만 출력이 됩니다.
  2. 추가적으로 Full GC출력 코드 부분에 알림 등을 추가하여 Full GC 조기 감지를 가능하게 할 수 있습니다.
최정민[email protected]
DevOps TeamDevOps Engineer

지금 바로
와탭을 경험해 보세요.