内容


企业中的 IBM 和 Node.js

针对 IBM SDK for Node.js 的核心转储调试

使用 IDDE Eclipse 插件诊断程序崩溃和内存泄漏

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 企业中的 IBM 和 Node.js

敬请期待该系列的后续内容。

此内容是该系列的一部分:企业中的 IBM 和 Node.js

敬请期待该系列的后续内容。

在 Node.js 的开发中,核心转储分析可以帮助调试程序崩溃和内存泄漏。IBM SDK for Node.js 通过 IBM Monitoring and Diagnostic Tools — Interactive Diagnostic Data Explorer (IDDE),为 Node.js 应用程序提供了一种新的核心转储分析和调试方法。IDDE 是一个 Eclipse 插件,可通过免费许可获得,它支持来自 IBM SDK for Node.js 发布到的所有平台的核心转储文件,但 Mac OS X 除外。而且这些转储是可移植的;您可以获取一个计算机的核心转储文件并在另一个计算机上的 IDDE 中打开它,甚至是在运行不同的(受支持)OS 的计算机上也可以打开它。

可以从 Eclipse 市场 或使用 更新站点 安装 IDDE。请继续阅读,了解在开发 Node.js 应用程序的过程中,如何使用 IDDE 调试程序崩溃和内存泄漏。

生成核心转储文件

生成核心转储文件的方法在不同系统中有所不同。Joyent(Node.js 的企业管理人)推荐使用 --abort-on-uncaught-exception 标记运行所有生产级 Node.js 系统。在 UNIX 系统上,还需要设置 ulimit -c unlimited 来支持生成没有大小限制的核心文件。

如果没有抛出异常,则需要使用系统工具(比如 LInux 上的 gcore 或 AIX 上的 gencore)来生成核心文件,或者使用 kill -11 结束进程。在 Windows 7 和更高版本上,可以使用任务管理器来生成核心转储文件:按下 Ctrl+Alt+Delete 并选择 Start Task Manager。从 Processes 选项卡中,右键单击 Node.js 进程并选择 Create Dump File。还可以使用用于 Windows 的免费的 ProcDump 实用程序。

调试崩溃

为了生成程序崩溃,我们将使用 Test.js 脚本。这个简单脚本循环 5 次,然后抛出一个错误。

清单 1. Test.js
function main() {
  var inputObject = {
    input: ["one", "two", "three", "fifteen", "one hundred"],
    counter:0,
  };

  for(; inputObject.counter< inputObject.input.length; inputObject.counter++) {
    if (inputObject.input[inputObject.counter].length > 8) {
      throw "Input String Too Big";
    }
  }
}

main();

可在 Linux 上像这样运行 test.js:

$ cd node-v0.12.4-linux-x64/bin/
$ ulimit -c unlimited
$ ./node --abort-on-uncaught-exception ../../test.js
Uncaught Input String Too Big

FROM
main (/home/sian/test.js:11:7)
Object.<anonymous> (/home/sian/test.js:16:1)
Module._compile (module.js:460:26)
Object.Module._extensions..js (module.js:478:10)
Module.load (module.js:355:32)
Function.Module._load (module.js:310:12)
Function.Module.runMain (module.js:501:10)
startup (node.js:129:16)
node.js:814:3
Illegal instruction (core dumped)

在 IDDE 中打开一个核心转储文件

如果 IDDE 已安装,并在核心转储文件所在的相同计算机上运行,您可以直接从磁盘位置打开核心转储文件。右键单击 PD Navigator View(PD 表示问题判定)并选择 New PD Artifact

图 1. 选择 New PD Artifact
该图显示了选择 New PD Artifact 的过程。
该图显示了选择 New PD Artifact 的过程。

浏览到您的核心转储文件的位置并单击 Finish

要在另一个计算机上打开该转储文件,可以使用常用工具复制该转储文件,然后采用我们刚才介绍的方法在第二个系统上的 IDDE 中打开它。要在将转储文件复制到不同的位置或不同的计算机时实现更好的原生堆栈轨迹,还可以将 Node 可执行程序复制到同一个目录来启用要解析的符号。

您现在需要使用 IDDE 编辑器。从您的新核心文件下选择 Start Investigation 来打开该编辑器。

图 2. 打开 IDDE 编辑器。
该图显示了打开 IDDE 编辑器的过程
该图显示了打开 IDDE 编辑器的过程

大型转储文件可能需要更多的时间来完成加载。

IDDE 命令

它有助于将 IDDE 编辑器视为编辑器与控制台的一种混合体。您可以像使用控制台一样在 IDDE 编辑器中输入并运行命令,但您的进度保存在编辑器中。

要运行命令,必须在命令的前面添加 ! 并按下 Ctrl+Enter。例如,输入 !help 并按下 Ctrl+Enter 会输出帮助消息,其中列出了处理转储的其他可用命令。Node 命令集与 Java 集不同,甚至对于不同的 Node 版本(或 Java)可能也会有所不同,因为新的 IDDE 版本中不断添加新的命令。

与其他 Eclipse 编辑器一样,Ctrl+Space 会显示完成建议,这在 IDDE 编辑器中就是命令列表。

图 3. 使用 Ctrl+Space 打开的命令列表
该图显示了使用 Ctrl+Space 打开的命令列表
该图显示了使用 Ctrl+Space 打开的命令列表

一个首先使用的不错命令是 nodeoverview,它提供了您运行的 Node 版本、依赖关系等的基本摘要。

!nodeoverview {

Node Property           Value                                                        
----------------------  -------------------------------------------------------------
Node version            0.12.4                                                       
Path to executable      /home/sian/node-v0.12.4-linux-x64/bin/node                   
Architecture            x64                                                          
Platform                linux                                                        
Command Line Arguments  /home/sian/node-v0.12.4-linux-x64/bin/node /home/sian/test.js
Execution Arguments     --abort-on-uncaught-exception                                
Process ID              5643                                                        

Dependency   Version   
-----------  ----------
http_parser  2.3       
node         0.12.4    
v8           3.28.71.19          2.2    

[...]

调查问题

从前面的控制台输出中可以看出,该程序将会崩溃并抛出了一个 uncaught 异常,所以在这里要做的第一件事就是查看堆栈轨迹。运行 threads 命令查找线程 ID。

!threads {

Thread ID: 0x74c9 (29897) IP: 0x0000000000eb112d
    !stack 29897

}

只有一个线程在运行,所以它一定是 JavaScript 线程。

尝试对该线程运行 stack 命令。threads 命令的输出中包含该命令的快捷键,所以您可以将光标放在包含 !stack 29897 的行的末尾处并按下 Ctrl+Space。这是输出(为了提高可读性,我们删除了参数列并截断了一些行):

!stack 29897 {

Instruction Pointer  Frame Address       Location / Frame Type                                                                                                                                     
-------------------  ------------------  ------------------------------------------------------------------------------------
 0x0000000000eb112d  0x00007FFF0A81F960  node::_ZN2v84base2OS11GetUserTimeEPjS2_+0x9d                                              
 0x0000000000a890c1  0x00007FFF0A81FAC0  node::_ZN2v88internal7Isolate29CaptureAndSetSimpleStackTraceENS0_6HandleINS0_8JSO...                                                 
 0x0000000000b63c9d  0x00007FFF0A81FAF0  node::_ZN2v88internal25Runtime_ThrowNotDateErrorEiPPNS0_6ObjectEPNS0_7IsolateE+0xdd
 0x00000E7CAA2A8E32  0x00007FFF0A81FB40  main [/home/sian/test.js]                                                                                                                                    
                                         !jsobject 0x00001519ED09B451                                                                                                                                       
 0x00000E7CAA2A8B34  0x00007FFF0A81FB78  <anonymous> [/home/sian/test.js]                                                                                    
                                         !jsobject 0x00001519ED09B341                                                                                                    
 0x00000E7CAA224AC6  0x00007FFF0A81FBE0  INTERNAL FRAME                                                                                                                                                     
 0x00000E7CAA2A7D26  0x00007FFF0A81FC58  Module._compile [module.js]                                                                                                                 
                                         !jsobject 0x00000DA39E6185F9                                                                                            
 0x00000E7CAA2A220C  0x00007FFF0A81FCA0  Module._extensions..js [module.js]                                                                                  
                                         !jsobject 0x00000DA39E618691                                                                                            
 0x00000E7CAA29E940  0x00007FFF0A81FCE8  Module.load [module.js]                                                                                                  
                                         !jsobject 0x00000DA39E618569                                                                                                                                      
 0x00000E7CAA295565  0x00007FFF0A81FD70  Module._load [module.js]           
                                         !jsobject 0x00000DA39E6184D9           
 0x00000E7CAA294F24  0x00007FFF0A81FDB8  Module.runMain [module.js]                                                                          
                                         !jsobject 0x00000DA39E618721                                                                                                       
 0x00000E7CAA26B31F  0x00007FFF0A81FE28  startup [node.js]                                                    
                                         !jsobject 0x0000079578AC8A61
 0x00000E7CAA269D10  0x00007FFF0A81FE58  <anonymous> [node.js]       
                                         !jsobject 0x0000079578A6EF49                                                                                                                                      
 0x00000E7CAA21EF40  0x00007FFF0A81FE98  INTERNAL FRAME                                                                                                                                                     
 0x00000E7CAA21DE90  0x00007FFF0A81FF20  ENTRY FRAME                                                                                                                                                        
 0x00000E7CAA21DE90  0x00007FFF0A81FF20  ENTRY FRAME                                                                                                                                                        
 0x0000000000914E28  0x00007FFF0A81FFF0  NONE FRAME                                                                                                                                                         
 0x0000000000813bda  0x00007FFF0A820060  node::_ZN2v88Function4CallENS_6HandleINS_5ValueEEEiPS3_+0xba                                                                                                      
 0x0000000000c9d40f  0x00007FFF0A820190  node::_ZN4node15LoadEnvironmentEPNS_11EnvironmentE+0x1df                                                                                                          
 0x0000000000c9d67f  0x00007FFF0A8202E0  node::_ZN4node15LoadEnvironmentEPNS_11EnvironmentE+0x44f                                                                                                          
 0x0000003d0fa1ed5d  0x0000000000000000  node::_fini+0x3d0eb6c8c5                                                                                                                                          

}

可以看到,崩溃发生在 main 方法中。要返回到代码来查看可能已发生的情况,不需要离开 IDDE;如果运行前面的输出中以粗体显示的命令,IDDE 会显示源代码。

!jsobject 0x00001519ED09B451 {

Object has fast properties
Number of descriptors : 5

Name       Value               More Information               
---------  ------------------  -------------------------------
length     0x0000079578A0FE19  <EXECUTABLE_ACCESSOR_INFO_TYPE>
name       0x0000079578A0FE51  <EXECUTABLE_ACCESSOR_INFO_TYPE>
arguments  0x0000079578A0FE89  <EXECUTABLE_ACCESSOR_INFO_TYPE>
caller     0x0000079578A0FEC1  <EXECUTABLE_ACCESSOR_INFO_TYPE>
prototype  0x0000079578A0FEF9  <EXECUTABLE_ACCESSOR_INFO_TYPE>

Object is a function

Name: main

Source:

() {
  var inputObject = {
    input: ["one", "two", "three", "fifteen", "one hundred"],
    counter:0,
  };

  for(; inputObject.counter< inputObject.input.length; inputObject.counter++) {
    if (inputObject.input[inputObject.counter].length > 8) {
      throw "Input String Too Big";
    }
  }
}
}

备注:此特性有助于确认您认为在运行的代码确实是实际运行的代码。

从代码中可以明显看到,mainObject.counter 肯定已经达到 4,它指向字符串 "one hundred",该字符串的长度大于 8。可以使用另外两个 IDDE 命令确认事实就是这样:jsfindbyproperty jsobjectjsfindbyproperty 搜索堆中所有具有所提供的名称的属性的对象 — 在本例中为 counterjsobject 显示了该对象的属性。

!jsfindbyproperty counter {

!jsobject 0x00001519ED09B539
!jsobject 0x00001519ED09B689

}


!jsobject 0x00001519ED09B689 {

Object has fast properties
Number of descriptors : 2

Name     Value               More Information                             
-------  ------------------  ---------------------------------------------
input    0x00001519ED09B6C1  <JS Array[5]> :- !jsobject 0x00001519ed09b6c1
counter  0x0000000400000000  SMI = 4                                      

}

jsfindbyproperty 生成的十六进制数(例如 0x00003CF51F09B441)显示了该命令找到的对象的内存地址。可以对其中一个十六进制地址运行 jsobject 命令,如果某个 JavaScript 对象位于该地址中,该命令会打印出该对象的属性信息。在本例中,我们可以看到 counter 在此对象上达到了 4。

现在使用来自前面的输出的快捷键命令,查看 input 数组。

!jsobject 0x00001519ed09b6c1 {

Array at !hexdump 0x00001519ED09B4E1
Array len = 5

0 : 0x00000CEAAC1A3679, one
1 : 0x00000CEAAC1A3699, two
2 : 0x00000CEAAC1A36B9, three
3 : 0x00000CEAAC1A36D9, fifteen
4 : 0x00000CEAAC1A36F9, one hundred

}

可以看到 test.js 代码中未通过测试并导致抛出了一个异常的数组元素。

查找内存泄漏

内存泄漏可能是所有程序中的常见问题。IDDE 拥有多个命令来帮助跟踪哪些对象正在占用内存。在本例中,我们首先从我们认为存在内存泄漏的 Node.js 应用程序中获取一个核心转储文件。

跟踪泄漏的一种方式是获取两个或更多具有较大时间差的转储文件,比较在两个转储文件之间执行一些命令的输出。

jsmeminfo 命令有助于直观显示是否有非常大的对象正在使用大量空间,像本例中一样。

!jsmeminfo {

Memory allocator, used: 1423 MB, available: 0 MB
Total Heap Objects: 29497

Largest 5 heap objects  Type               Size (bytes)  More information          
----------------------  -----------------  ------------  --------------------------
0x0000000088a6d319      JS_OBJECT_TYPE          1311125  !jsobject 0x0000000088a6d319
0x0000000088aac6d9      FIXED_ARRAY_TYPE          98360  !array 0x000003ff88aac6d9
0x000003ff8abe31b9      ASCII_STRING_TYPE         48176  !string 0x000003ff8abe31b9
0x000003ff8ab34f09      ASCII_STRING_TYPE         48104  !string 0x000003ff8ab34f09
0x000003ff8ab04101      ASCII_STRING_TYPE         40936  !string 0x000003ff8ab04101

调试崩溃 小节中一样,对此对象运行 jsobject 命令可以将它与您程序中的一个对象相关联并修复问题。

对于程序创建了许多对象但不处理它们的内存问题,jsgroupobjects 可将相同类型的对象分组到一起,并显示程序中有多少对象。jsgroupobjects 还有助于识别 Node.js 缓冲区在何处被用来显示对象的构造函数。(Node 中的缓冲区是分配超出堆范围的内存的一种方式。)在本例中,Buffer 是最常出现的对象:

!jsgroupobjects {

Representative Object Address  Object Type    Num Objects Constructor  Num Properties  Properties                                                                      
-----------------------------  -------------  ----------  -----------  --------------  ---------------
!jsobject 0x000003ffec004101   JS_OBJECT_TYPE       2572      Buffer         2        length, parent
...

您可以通过识别缓冲区所分配的外部数组的位置(在以下输出中用粗体显示),在 IDDE 中打印缓冲区的内容。

print 0x000003ffec004101 {

Object at 0x000003FFEC004101 is JSObject

Class hierarchy :-

|-JSObject
|  |- kElementsOffset 0x10 (EXTERNAL_UINT8_ARRAY_TYPE, !print 0x000003FFEC004159)
|  |- kPropertiesOffset 0x8 (FIXED_ARRAY_TYPE, !print 0x000003FF92A04111)
| |-JSReceiver
| | |-HeapObject
| | |  |- kMapOffset 0x0 (MAP_TYPE, !print 0x000003FF8BE1F6E9)
| | | |-Object
...

获取 kElementsOffset 的地址并提供给 array 数组。

!array 0x000003FFEC004159 {

Array type : EXTERNAL_UINT8_ARRAY_TYPE
Len : 10
0 0x48 H
1 0x65 e
2 0x6c l
3 0x6c l
4 0x6f o
5 0x20 
6 0x6e n
7 0x6f o
8 0x64 d
9 0x65 e
..

objtypes 命令对解决内存问题也很有用。它显示了 V8 堆对象类型的计数和内存大小。

完整的命令参考列表

以下是 IDDE 的完整的命令参考列表。这里用斜体显示的命令可用于任何核心转储文件;其他所有命令特定于 Node.js 转储文件:

array
显示指定地址上一个固定数组的元素
findfindallfindnext
在内存中查找一个字符串
frame
显示单个 JavaScript 堆栈帧的详细信息
help
显示命令列表
hexdump
以十六进制和 ASCII 格式输出一个内存区段
jsfindbyproperty
找到具有指定属性的 JavaScript 对象
jsgroupobjects
列出共享同一个 Map 的 JavaScript 对象组
jslistobjects
列出具有指定的 V8 对象类型的堆对象
jsmeminfo
显示 JavaScript 内存使用信息,包括 5 个最大的对象
jsobject
显示 JavaScript 对象细节
jsobjectsmatching
打印与所提供的对象共享同一个 Map 的 JavaScript 对象
jsstringsearch
在堆上搜索某个指定的字符串
locate
在内存中搜索某个指定的字符串
nodeoverview
显示 Node 概述信息,包括版本
objtype
列出所有 V8 对象类型
objtypes
显示 V8 实例类型的堆栈使用分类信息
print
显示指定的堆栈对象的 C++ 分层结构
ranges
打印可用的内存范围列表
stack
显示某个给定线程的 JavaScript 堆栈轨迹
string
显示指定地址上的字符串
threads
列出所有线程

结束语

在本教程中,您学习了如何生成 Node.js 核心转储文件,在 IDDE 中打开该转储文件,在 IDDE 编辑器中输入并运行命令,获取可用于您的转储文件的所有命令的列表。您还了解了哪些 IDDE 命令能够以最佳方式帮助您跟踪程序崩溃和内存泄漏的根源。

如果对使用 IDDE 有任何疑问,或者希望报告您使用该工具时遇到的错误或问题,请发送电子邮件至 javatool@uk.ibm.com


相关主题

  • IDDE 知识中心:访问 IDDE 知识中心,获取 IDDE 的在线帮助。
  • IBM 开发人员工具包:获取其他用于 Java 和 Node.js 的 IBM 诊断工具的信息,包括 Health Center 和 GCMV。
  • MDB 和 Linux:了解 Joyent 推荐如何从 Node.js 获取核心转储文件。
  • 缓冲区:在 Node.js 文档中了解缓冲区。
  • developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
  • developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
  • 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Linux
ArticleID=1021460
ArticleTitle=企业中的 IBM 和 Node.js: 针对 IBM SDK for Node.js 的核心转储调试
publish-date=11182015