블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

 node.js에서 Heapdump를 이용한 메모리 누수 추적하기


조대협 (http://bcho.tistory.com)
 

대부분의 애플리케이션 서버들에서 고질적인 문제점중의 하나가 메모리 누수 현상이다. 비단 애플리케이션 서버에만 해당하는 문제는 아니지만 특히나 동시에 여러개의 요청을 반복적으로 받는 애플리케이션 서버의 경우에는 이 메모리 누수 현상이 훨씬 더 많이 발생한다.

 

node.js 의 경우, 엔진의 근간이 되는 구글 크롬 V8 자바스크립트 엔진 자체가 많은 메모리 누수 버그를 가지고 있지만, 애플리케이션에서 발생하는 메모리 누수는 발생하는 양도 크거니와 더 큰 문제를 야기할 수 있다. 이러한 애플리케이션에서의 메모리 누수를 추적하기 위한 방법을 알아보자

 

node.js의 확장 모듈인 heapdump는 기동중인 node.js 서버의 메모리 스냅샷을 추출할 수 있는 기능을 제공한다. 이 메모리 스냅샷을 힙덤프라고 하는데, 힙덤프에서 어떤 객체들이 메모리를 반복적으로 많이 점유하는 지를 찾아내면 메모리가 누수 되는 지점을 파악할 수 있다.

 

예제를 통해서 살펴보자

먼저 heapdump 모듈을 설치해야 한다. https://www.npmjs.com/package/heapdump

%npm install heapdump

명령어를 이용하여 heapdump 모듈을 설치한다.

 

Express로 간단한 웹 애플리케이션을 생성하자

package.jsonheapdump 모듈에 대한 의존성을 다음과 같이 추가한다.

 

{

  "name": "MemoryLeak",

  "version": "0.0.0",

  "private": true,

  "scripts": {

    "start": "node ./bin/www"

  },

  "dependencies": {

    "body-parser": "~1.13.2",

    "cookie-parser": "~1.3.5",

    "debug": "~2.2.0",

    "express": "~4.13.1",

    "jade": "~1.11.0",

    "morgan": "~1.6.1",

    "serve-favicon": "~2.3.0",

    "heapdump":"~0.3.7"

  }

}

 

Figure 1 package.jsonheapdump 모듈 의존성을 추가

 

다음 express 프로젝트의 app.js에 아래와 같은 코드를 추가한다.

app.use('/', routes); // 기존에 자동으로 생성된 코드

app.use('/users', users); // 기존에 자동으로 생성된 코드

 

var heapdump = require('heapdump');

var memoryLeak = [];

function LeakedObject(){ };

 

app.use('/leak',function(req,res,next){

     

      for(var i=0;i<1000;i++){

           memoryLeak.push(new LeakedObject());

      }

      res.send('making memory leak. Current memory usage :'

                 +(process.memoryUsage().rss / 1024 / 1024) + 'MB');

});

 

app.use('/heapdump',function(req,res,next){

      var filename = '/Users/terry/heapdump' + Date.now() + '.heapsnapshot';

      heapdump.writeSnapshot(filename);

      res.send('Heapdump has been generated in '+filename);

});

 

 

Figure 2 메모리 누수를 유발하는 코드 및 힙덤프를 추출하는 코드 추가

 

/leak URL을 처리하는 부분은 인위적으로 메모리 누수를 만들어낸다. for 루프를 이용하여 LeakedObject 1000개씩 배열에 추가하여 메모리 누수를 유발하고, 화면에 현재 메모리 사용량을 리턴하도록 하였다.

 

다음 /heapdump라는 URL에서는 실제로 heapdump를 생성한다. heapdump.writeSnapshot(파일명) 이라는 메서드를 사용하는데, 이 파일명 위치에 힙덤프를 저장한다.

 

또는 명령어를 사용하지 않더라도 프롬프트 상에서 힘덤프를 생성할 수 있다.

% kill –USR2 {node.js 프로세스 ID}

명령을 이용하면, node.js를 실행한 위치에 heapdumpxxx.heapsnapshot 이라는 이름으로 힙덤프를 생성해준다.

 

코드 작성이 끝났으면 애플리케이션을 실행해보자. http://localhost:3000/leak 로 들어가면 메모리 누수를 유발하고, 현재 사용중인 메모리 양을 볼 수 있다.

 



Figure 3 메모리 누수를 유발하는 URL에 접속

 

많은 메모리 누수를 유발해보기 위해서 ab (Apache benchmark : 아파치 웹서버를 설치하면 같이 설치되는 간단한 부하 테스트 툴이다. http://httpd.apache.org/) 를 이용해서 반복적으로 http://localhost:3000/leak 를 호출해보자

%ab -n 5000 http://localhost:3000/leak

명령어를 이용하면 http://localhost:3000/leak 5000번 호출한다.



Figure 4 아파치 ab툴을 이용하여 메모리 누수를 유도하기 위해서 5000번 부하를 줌

 

다시 http://localhost:3000/leak 를 접속해보면 사용중인 메모리 양이 늘어난것을 볼 수 있다.

 



Figure 5 node.js 의 메모리 사용량이 늘어난것을 확인

 

이제 힙덤프를 추출해보자. http://localhost:3000/heapdump 에 접속하면 자동으로 힙 덤프가 생성된다.

이 힙덤프는 현재 node.js가 사용중인 메모리 양이 클수록 추출하는 속도가 느려진다.

 



Figure 6 힙덤프 추출

 

힙덤프 파일이 추출되었으면, 이 힙덤프를 분석하기 위해서 구글의 크롬 브라우져에서 개발자 도구를 실행해보자

개발자 도구에서 “Profiles” 탭에서 Load 버튼을 눌러서 앞에서 추출한 힙덤프 파일을 로드한다.




Figure 7 크롬 브라우져 개발자 모드에서 힙덤프 파일을 로드

 

힙덤프를 보면, LeakedObject라는 객체가 전체 메모리의 66%, 120MB를 점유함을 확인할 수 있다.

이 객체를 열어보면, 같은 객체가 수도 없이 반복됨을 확인할 수 있다.

 



Figure 8 메모리 누수를 유발한 LeakedObject를 발견

 

이러한 방식으로 어떤 객체들이 메모리를 많이 점유해서 메모리 누수를 유발하는지 찾아낼 수 있다.

예제 소스 코드는 https://github.com/bwcho75/nodejs_tutorial/tree/master/MemoryLeak 를 참고하기 바란다.

VisualVM을 이용한 JVM 모니터링

성능과 튜닝/JVM | 2013.09.05 23:30 | Posted by 조대협

JVM Monitoring

조대협

 

JVM 1.4 버전대에만 해도 GUI 기반으로 JVM을 모니터링 하는 도구는 거의 없었다. 콘솔로 접속해서 Thread dump GC 로그등을 분석하거나 필요할 경우 JMX 기반의 모니터링 명령어등을 만들어야 했는데, 근래에는 좋은 모니터링 도구들이 많이 나와서, 쉽게 JVM의 상태를 모니터링 할 수 있고, 메모리,CPU 사용률, Thread 현황들을 매우 쉽게 분석할 수 있다.

여기에, 여러개의 VM을 동시에 중앙에서 매우 세세하게 모니터링이 가능하게 되었다.

여기서는 몇가지 자주 사용되는 JVM 모니터링 툴을 소개하고자 한다. 

VisualVM

대표적인 도구로는 오픈소스 기반의 Visual VM이라는 도구가 있다. http://visualvm.java.net/

에서 다운로드 받을 수 있다. 여러개의 VM을 동시 모니터링이 가능할 뿐만 아니라, profiling도 가능하고, eclipse 안에 embed 해서 사용할 수 도 있다.

먼저 사용법을 알아보자. VisualVM을 다운받은 후에, 압축을 풀어놓는다.다음은 원격지에 있는 VM을 모니터링 하는 설정이다. 모니터링할 VM 서버에서 먼저 rmiregistry 서버를 기동한다.

C:\dev\lang\jdk1.7\bin>rmiregistry 1099

 

다음으로는 VM에 대한 정보를 접근할 수 있도록 permission을 허용해야 하는데, ${java.home}/bin 디렉토리에 tools.policy라는 파일을 다음과 같이 만든 후에,

grant codebase "file:${java.home}/../lib/tools.jar" {

   permission java.security.AllPermission;

};

jstatd 라는 데몬을 기동 시킨다.

C:\dev\lang\jdk1.7\bin\> jstatd -p 1099
   -J-Djava.security.policy=tools.policy

위의 설정까지 하면 기본적인 VM에 대한 상태 정보 (메모리)를 모니터링할 수 있는데, 조금 더 상세한 모니터링을 위해서, JVM JMX 설정을 on 한다.

다음은 Tomcat JMX 설정을 하기 위해서 ${tomcathome}/bin/catalina.bat를 수정한 내용이다.

JVM옵션에 다음과 같은 내용을 추가 하면 된다.



set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG% -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9983


여기 까지 했으면 모든 준비가 끝났다.

주의할점은, 실제 운영이나 개발 환경들에 이러한 설정을 적용할때, 포트가 외부에 오픈 되기 때문에, 보안상 문제가 될 수 있다. 그래서 반드시 OS (Linux) iptable 설정등에서, VisualConsole이 인스톨 되는 서버로 부터의 inbound connection 만 오픈하도록 하는 것이 좋다.

Visual VM을 실행하고, Remote Host Add하고, ip 주소와 위에서 지정한 포트를 지정하면 아래와 같이 VM을 모니터링 할 수 있다.

아래 그림은 편의상 local에 있는 JVM을 모니터링 하도록 설정하였다.



여기서는 전체 VM에 대한 Thread 개수, Class개수와 기본적인 CPU JVM 메모리 상황을 모니터링 할 수 있다.

 

다음은 Thread를 모니터링 하는 화면이다. time frame에 따라서 어떤 thread가 있고,어떤 상태인지를 모니터링이 가능하다.



 

아래는 Thread dump 추출 화면이다. 좌측 메뉴에서 JVM을 선택하고 오른쪽 마우스 버튼을 이용하면 Thread dump 추출 메뉴가 나오는데, 아래 그림과 같이 추출한 Thread dump를 저장해서 보여준다. Bottleneck 분석등에 매우 유용하게 사용할 수 있다. 



다음은 성능 튜닝 부분에서 유용하게 사용할 수 있는 기능인데, profiling 기능을 제공한다.

우측 상자에 profiling하고 싶은 class (패키지명)을 지정해놓고, start를 하게 되면, 각 패키지 또는 클래스에서 소요되는 CPU 사용량을 보여준다. 


이를 이용하면 손쉽게 CPU 소모가 많은 모듈을 찾아낼 수 있다.


 

JConsole

참고로, JVM을 인스톨 하면 default로 인스톨 되는 GUI 기반의 JVM 모니터링 툴이 있는데, ${java.home} jconsole이라는 유틸리티이다.

VMConsole 처럼 permission이나 rmi setting이 필요 없고, JMX를 이용해서 통신을 한다.

기능상으로나 UI 면에서는 개인적으로는 Visual VM이 더 났다고 판단하는데, JConsole의 경우에는 모니터링 대상 VM이 제공하는 MBean(JMX)를 모니터링 할 수 있다.

Tomcat이나 각종 자바 서버들은 MBean을 통해서 서버의 상태나 Configuration  정보를 저장하기 때문에 이 MBean 만 모니터링 하더라도 서버의 상태를 알아내는데 매우 유용하게 사용할 수 있다.

아래 그림은 JConsole을 이용하여, Tomcat MBean을 모니터링 하는 화면이다.

 


 


 2013.9.12 추가. Oracle JVM 7 최신 버전에 multiple JVM을 monitoring 할 수 있는 JMC 라는 것이 포함 되었음

http://www.oracle.com/technetwork/java/javase/2col/jmc-relnotes-2004763.html


 

'성능과 튜닝 > JVM' 카테고리의 다른 글

VisualVM을 이용한 JVM 모니터링  (0) 2013.09.05
G1GC Collector  (0) 2009.04.22
새로운 GC Collector G1.  (1) 2009.04.20
JVM 튜닝  (5) 2008.03.12
Sun JVM HeapDump 얻기  (1) 2007.11.28
-XX:PretenureSizeThreshold  (0) 2007.11.10
AOP를 이용하면 특정 클래스의 메서드 실행 전후에 Code를 삽입할 수 있다.
사용자 애플리케이션의 비지니스 메서드에 Code를 삽입하여
1. 시작 시간, 종료시간을 측정하면 비지니스 메서드의 응답 시간을 측정할 수 있고
2. JMX를 이용하여 CPU 사용시간을 측정하면 비지니스 메서드의 CPU 사용률을 측정할 수 있다.
3. Thread Local 변수를 이용하면, Transaction이 시작될때부터 끝날때 까지를 측정할 수 있다. (예를 들어 JSP는 JSPServlet : service 메서드에서부터 시작되기 때문에, AOP에서 before advice에 transaction id를 set하게 하고, after advice에서 Trace내용을 flush하게 하면 된다. 그 중간에는 Thread Local에 Trace 정보를 저장하게 한다.)

초간단하게 작성한 AspectJ를 이용한 메서드별 응답시간 측정 프로그램이다.
Aspect J 1.5이상 JVM 1.5이상에서만 동작한다.

AbstractTracerAspect.aj 소스
==
package bcho.aspectj.aspects;

import bcho.aspectj.logger.xml.*;

public abstract aspect AbstractTracerAspect {
abstract pointcut scope();
pointcut targetMethod(): scope() && execution(* *(..));
Object around(): targetMethod(){
long startTime = System.currentTimeMillis();
try{
return proceed();
}finally{
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
Logger.log(thisJoinPoint,startTime,elapsedTime);
}// try
}
}
===

Logger.java 소스

==
package bcho.aspectj.logger.xml;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
public class Logger {
static public void log(JoinPoint point,long startTime,long elapsedTime){
Signature signature;
Object args[];
signature = point.getSignature();
args = point.getArgs();
System.out.println("["+startTime+"|signature] "+signature);
System.out.println("\tElapsed time :"+elapsedTime);
for(int i=0;i<args.length;i++){
System.out.println("\targ["+i+"] :"+args[i]);
}
}
}
==

코드 작성후 ajc로 컴파일 한후에 jar로 묶고 측정할 Application이 있는 디렉토리에 배포

%TRACER_HOME%/aspectjTracer.jar
%TRACER_HOME%/META-INF/aop.xml 을 다음과 같이 작성

==
 <aspectj>
    <aspects>
       <concrete-aspect name="myTracerAspect" extends="bcho.aspectj.aspects.AbstractTracerAspect">
          <pointcut name="scope" expression="within(bcho..*) &amp;&amp; !within(bcho.aspectj..*) " />
       </concrete-aspect>
    </aspects>
</aspectj>
==

expression 부분이 Weaving을 할 class에 대한 정의 
bcho.* 시작되는 모든 클래스를 profiling하되 bcho.aspectj.* 클래스는 제거하도록 설정함. (XML이기 때문에, &를 &amp;로 정의해야 함)

다음 CLASSPATH에 다음 내용을 추가함
set CLASSPATH=%TRACER_HOME%;%CLASSPATH%
set CLASSPATH=%TRACER_HOME%/aspectjTracer.jar;%CLASSPATH%

java 실행시 다음과 같이 -Xagent 옵션 추가

java -javaagent:%ASPECTJ_HOME%/lib/aspectjweaver.jar {실행할 JAVACLASS}

를 해주면 Runtime에 Weaving이 되면서 메서드 수행 시간을 trace해준다.

자세한 내용은 다 만들면 다시 정리하겠다... ^^