>  기사  >  웹 프론트엔드  >  Node.js_node.js의 메모리 누수 문제에 대한 간략한 분석

Node.js_node.js의 메모리 누수 문제에 대한 간략한 분석

WBOY
WBOY원래의
2016-05-16 15:53:191089검색

이 기사는 지난달 페르소나의 첫 번째 베타 버전을 출시한 Mozilla의 Identity 팀이 제공한 A Node.JS Holiday Season 기사 시리즈 중 첫 번째입니다. 페르소나를 개발할 때 우리는 디버깅부터 현지화, 종속성 관리 등에 이르는 일련의 도구를 구축했습니다. 이 기사 시리즈에서는 우리의 경험과 이러한 도구를 커뮤니티와 공유할 것입니다. 이는 Node.js로 고가용성 서비스를 구축하려는 모든 사람에게 유용할 것입니다. 이 기사를 즐기시고 여러분의 생각과 기여를 기대하시기 바랍니다.

Node.js의 실질적인 문제인 메모리 누수에 대한 주제 기사부터 시작하겠습니다. Node.js에서 메모리 누수를 찾아 격리하는 데 도움이 되는 라이브러리인 node-memwatch를 소개하겠습니다.


왜 문제를 제기하시나요?

메모리 누수 추적에 관해 가장 자주 묻는 질문은 "왜 귀찮게 해야 합니까?"입니다. 먼저 해결해야 할 시급한 문제가 더 있지 않나요? 때때로 서비스를 다시 시작하거나 서비스에 더 많은 RAM을 할당하는 것을 선택해 보는 것은 어떨까요? 이러한 질문에 답하기 위해 다음 세 가지 제안을 제안합니다.

1. 어쩌면 당신은 증가하는 메모리 공간에 관심이 없을 수도 있지만 V8은 그렇습니다(V8은 노드 런타임의 엔진입니다). 메모리 누수가 증가함에 따라 V8은 가비지 수집기에 더욱 적극적이 되어 애플리케이션 실행 속도가 느려질 수 있습니다. 따라서 Node에서 메모리 누수는 프로그램 성능에 해를 끼칩니다.

2. 메모리 누수로 인해 다른 유형의 오류가 발생할 수 있습니다. 메모리 누수 코드는 제한된 리소스를 지속적으로 참조할 수 있습니다. 파일 설명자가 부족할 수도 있고 갑자기 새 데이터베이스 연결을 할 수 없게 될 수도 있습니다. 이러한 유형의 문제는 앱의 메모리가 부족해지기 훨씬 전에 표면화될 수 있지만 여전히 문제를 일으킬 수 있습니다.

3. 결국, 귀하의 앱은 조만간 충돌을 일으키게 되며, 앱이 인기를 얻으면 반드시 그런 일이 일어날 것입니다. Hacker News에서 모두가 당신을 비웃고 조롱할 것이고, 그것은 당신을 비극으로 만들 것입니다.

수천 마일의 둑을 뚫은 개미집은 어디에 있나요?

복잡한 애플리케이션을 구축할 때 여러 곳에서 메모리 누수가 발생할 수 있습니다. 클로저는 아마도 가장 잘 알려지고 악명 높은 것일 것입니다. 클로저는 해당 범위 내의 항목에 대한 참조를 유지하기 때문에 일반적으로 메모리 누수가 발생하는 곳입니다.

클로저 누출은 누군가가 찾아볼 때만 발견되는 경우가 많습니다. 하지만 비동기식 Node 세계에서는 언제 어디서나 콜백 함수를 통해 지속적으로 클로저를 생성합니다. 이러한 콜백 함수를 생성 후 즉시 사용하지 않으면 할당된 메모리가 계속 늘어나 메모리 누수가 발생하지 않는 것으로 보이는 코드가 누수됩니다. 그리고 이런 종류의 문제는 찾기가 더 어렵습니다.

업스트림 코드 문제로 인해 애플리케이션에서 메모리 누수가 발생할 수도 있습니다. 메모리가 누출된 코드를 찾을 수도 있지만, 완벽한 코드를 바라보고 어떻게 누출되었는지 궁금할 수도 있습니다.


우리가 node-memwatch와 같은 도구를 원하게 만드는 것은 찾기 어려운 메모리 누수입니다. 전설에 따르면 몇 달 전 Lloyd Hilaiel은 스트레스 테스트를 통해 명백해진 메모리 누수를 추적하기 위해 이틀 동안 작은 방에 갇혀 있었습니다. (그런데, 부하 테스트에 대한 Lloyd의 향후 기사를 계속 지켜봐 주시기 바랍니다)

이틀간의 노력 끝에 마침내 그는 노드 커널에서 http.ClientRequest의 이벤트 리스너가 해제되지 않은 문제를 발견했습니다. (결국 문제를 해결한 패치는 단 두 글자였지만 결정적인 글자였습니다). Lloyd가 메모리 누수를 찾는 데 도움이 되는 도구를 작성하게 된 것은 바로 이러한 고통스러운 경험이었습니다.

메모리 누수 찾기 도구

Node.js 애플리케이션에서 메모리 누수를 찾기 위한 유용하고 지속적으로 개선되는 도구가 많이 있습니다. 그 중 일부는 다음과 같습니다.

  • Jimb Esser의 node-mtrace는 GCC의 mtrace 도구를 사용하여 힙 사용량을 분석합니다.
  • Dave Pacheco의 node-heap-dump는 V8 힙의 스냅샷을 찍고 모든 것을 거대한 JSON 파일로 직렬화합니다. 또한 연구 스냅샷 결과를 분석하기 위한 JavaScript 도구도 포함되어 있습니다.
  • Danny Coates의 v8-profilernode-inspector는 Node에 번들로 제공되는 V8 프로파일러와 WebKit Web Inspector 기반의 디버그 인터페이스를 제공합니다.
  • Felix Gnass의 는 키퍼 차트 브랜치 를 비활성화하지 않습니다.
  • Felix Geisendorfer의 노드 메모리 누수 튜토리얼은 v8-profiler 및 node-debugger 사용 방법에 대한 짧고 멋진 튜토리얼입니다. 또한 가장 발전된 Node.js 메모리 누수 디버깅 기술 가이드이기도 합니다.
  • Node.js 메모리 누수 디버깅을 위한 다양한 도구를 제공하는 Joyent의 SmartOS 플랫폼입니다.

우리 모두는 위의 도구를 좋아하지만 그 중 어느 것도 우리 시나리오에 적용되지 않습니다. Web Inspector는 애플리케이션 개발에 적합하지만 특히 여러 서버와 하위 프로세스가 관련된 경우 핫 배포 시나리오에서는 사용하기 어렵습니다. 마찬가지로 장기간 고부하 작업 중에 발생하는 메모리 누수도 재현하기 어렵습니다. dtrace 및 libumem과 같은 도구는 인상적이지만 모든 운영 체제에서 사용할 수 있는 것은 아닙니다.

Enternode-memwatch

프로그램에 메모리 누수가 발생할 수 있는 시기를 장치에서 알려주지 않고 누수가 있는 위치를 찾는 데 도움이 되는 크로스 플랫폼 디버깅 라이브러리가 필요합니다. 그래서 우리는 node-memwatch를 구현했습니다.

다음 세 가지를 제공합니다.

'누수' 이벤트 이미터
 

   memwatch.on('leak', function(info) {
  // look at info to find out about what might be leaking
  });

'상태 이벤트 이미터

       

  var memwatch = require('memwatch');
  memwatch.on('stats', function(stats) {
  // do something with post-gc memory usage stats
  });

힙 메모리 영역 분류

  var hd = new memwatch.HeapDiff();
  // your code here ...
  var diff = hd.end();

그리고 테스트 중에 매우 유용한 가비지 수집기를 실행할 수 있는 기능도 있습니다. 알겠습니다. 총 4개입니다.
 

 var stats = memwatch.gc();

memwatch.on('stats', ...): GC 이후 힙 통계

node-memwatch는 JS 객체가 할당되기 전에 완전한 가비지 수집 및 메모리 압축 후에 메모리 사용량 샘플을 내보낼 수 있습니다. (V8의 post-gc 후크인 V8::AddGCEpilogueCallback을 사용하여 가비지 수집이 트리거될 때마다 힙 사용 정보를 수집합니다.)

통계에는 다음이 포함됩니다.

  • Usage_trend(사용 추세)
  • current_base(현재 베이스)
  • estimate_base(예상 베이스)
  • num_full_gc(완전한 가비지 수집 수)
  • num_inc_gc(가비지 수집 횟수 증가)
  • heap_compacions(메모리 압축 횟수)
  • 분(최소)
  • 최대(최대)

다음은 메모리 누수가 발생한 애플리케이션의 데이터가 어떤 모습인지 보여주는 예입니다. 아래 차트는 시간 경과에 따른 메모리 사용량을 추적합니다. 이상한 녹색 선은 process.memoryUsage()가 보고하는 내용을 보여줍니다. 빨간색 선은 node_memwatch에서 보고한 current_base를 보여줍니다. 왼쪽 하단의 상자에는 추가 정보가 표시됩니다.

2015623152204606.png (572×441)

Incr GC가 매우 높다는 점에 유의하세요. 이는 V8이 필사적으로 메모리를 지우려고 노력하고 있음을 의미합니다.

memwatch.on('leak', ...): 힙 할당 추세

애플리케이션에 메모리 누수가 있을 수 있음을 경고하는 간단한 감지 알고리즘을 정의했습니다. 즉, 5번의 연속 GC 후에도 메모리가 여전히 할당되었지만 해제되지 않은 경우 node-memwatch는 누수 이벤트를 발생시킵니다. 이벤트의 구체적인 정보 형식은 다음과 같이 명확하고 읽기 쉽습니다.

{ start: Fri, 29 Jun 2012 14:12:13 GMT,
 end: Fri, 29 Jun 2012 14:12:33 GMT,
 growth: 67984,
 reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr' }

memwatch.HeapDiff(): 查找泄漏元凶

最后,node-memwatch能比较堆上对象的名称和分配数量的快照,其对比前后的差异可以帮助找出导致内存泄漏的元凶。
 

var hd = new memwatch.HeapDiff();
 
// Your code here ...
 
var diff = hd.end();

对比产生的内容就像这样:
 

{
 "before": {
  "nodes": 11625,
  "size_bytes": 1869904,
  "size": "1.78 mb"
 },
 "after": {
  "nodes": 21435,
  "size_bytes": 2119136,
  "size": "2.02 mb"
 },
 "change": {
  "size_bytes": 249232,
  "size": "243.39 kb",
  "freed_nodes": 197,
  "allocated_nodes": 10007,
  "details": [
   {
    "what": "Array",
    "size_bytes": 66688,
    "size": "65.13 kb",
    "+": 4,
    "-": 78
   },
   {
    "what": "Code",
    "size_bytes": -55296,
    "size": "-54 kb",
    "+": 1,
    "-": 57
   },
   {
    "what": "LeakingClass",
    "size_bytes": 239952,
    "size": "234.33 kb",
    "+": 9998,
    "-": 0
   },
   {
    "what": "String",
    "size_bytes": -2120,
    "size": "-2.07 kb",
    "+": 3,
    "-": 62
   }
  ]
 }
}

HeapDiff方法在进行数据采样前会先进行一次完整的垃圾回收,以使得到的数据不会充满太多无用的信息。memwatch的事件处理会忽略掉由HeapDiff触发的垃圾回收事件,所以在stats事件的监听回调函数中你可以安全地调用HeapDiff方法。

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.