VisualVM을 이용한 Garbage Collection 확인

Programming Clojure 책을 보면서 Clojure 공부를 하고 있던 중, 함수형 프로그래밍 부분에서 피보나치 수열을 이용하여 재귀 프로그래밍 및 지연 연산 관련 내용을 다루면서 성능 별로 몇 가지 버전을 다루고 있길래 마침 귀에 들어온 VisualVM이라는 자바 모니터링 툴을 통해 Garbage Collection이 되는 모습을 직접 보기로 했습니다.

내용 자체는 별 것 없습니다. 그냥 '무겁게 돌아가는 걸 보니 진짜 무겁게 돌아가네'라는, 당연한 것을 확인한 수준이라... 그냥 Clojure 공부 겸 VisualVM 체험 학습 정도로만 봐주시기 바랍니다.

피보나치 수열 구하는 함수 중에 힙 메모리를 많이 잡아먹는 stack-consuming-fibo를 돌려봤습니다.

(stack-consuming-fibo 45)

나온 그래프는 아래와 같습니다. 붉은 색 박스 구간이 위 함수를 실행한 때입니다.

VisualVM 통한 GC 확인

위 그림의 아래 그래프는 힙 메모리 사용량으로, 서서히 증가하다가 일정 수준에 도달하면 GC 작업에 의해 다시 리셋되는 것을 확인할 수 있습니다. 아래 그래프는 GC 동작 자체를 나타내는 그래프입니다. 함수를 실행하면 힙 메모리 사용량이 급증하면서 수시로 GC가 실행되고 힙 메모리가 리셋되는 것을 확인할 수 있습니다. 위 예에서는 45번째 수열 값을 구하라고 했는데, 50번째 수열을 구하려 했더니 안멈추더군요 :)

참고로, Programming Clojure 책에서는 무척 큰 값 (1000000000) 을 넣어도 결과값을 얻어내던데, 제가 직접 돌렸더니 92번째 수열 값부터는 결과 값이 정수 값 범위를 벗어났다면서 튕기더군요. 이유는 모르겠습니다. Clojure 버전에 따라 다르게 동작한 것 같은데... (저는 1.4.0 또는 1.5.0을 사용하고 있습니다)

그래서 아래와 같이 명시적으로 Big Number로 지정해주면 실행됩니다. 다만 결과 값도 끝에 'N'이 붙어서 별로 안 이쁩니다.

(defn stack-consuming-fibo  [n]
  (cond
    (= n 0N) 0N
    (= n 1N) 1N
    :else  (+  (stack-consuming-fibo  (- n 1N))
              (stack-consuming-fibo  (- n 2N)))))

(defn recur-fibo  [n]
  (letfn  [(fib
             [current next n]
              (if  (zero? n)
               current
                (recur next  (+ current next)  (dec n))))]
    (fib 0N 1N n)))

(defn lazy-seq-fibo
  ([]
   (concat  [0N 1N]  (lazy-seq-fibo 0N 1N)))
   ([a b]
    (let  [n  (+ a b)]
     (lazy-seq
        (cons n  (lazy-seq-fibo b n))))))

(defn fibo  []
  (map first  (iterate  (fn  [[a b]]  [b  (+ a b)])  [0N 1N])))