Java技术的一项众所周知的优点是,Java程序员(与C程序员不同)不承担分配和释放所需内存的艰巨任务。 Java运行时只为您管理这些任务。 在堆区域中为每个实例化的对象自动分配内存,垃圾回收器会定期回收不再需要的对象占用的内存。 但是您并没有完全摆脱困境。 您仍然需要监视程序的内存使用情况,因为Java进程的内存由堆中漂浮的多个对象组成。 它还包括程序的字节码(JVM在运行时解释的指令),JIT代码(已为目标处理器编译的代码),任何本机代码以及JVM使用的一些元数据(异常表,行号表)等等)。 使事情变得复杂的是,某些类型的内存(例如本机库)可以在进程之间共享,因此确定Java应用程序的实际占用空间可能是一项艰巨的任务。
Windows下有许多用于监视内存使用情况的工具,但不幸的是,没有哪个工具可以为您提供所需的所有信息。 更糟糕的是,那里的各种工具甚至都没有共同的词汇。 但是帮助已经到来。 本文介绍了一些最有用的免费工具,并提供了有关如何使用它们的提示。
Windows内存:旋风之旅
在理解本文中讨论的工具之前,您需要对Windows如何管理内存有基本的了解。 Windows使用按需分页的虚拟内存系统。 继续阅读速成课程。
虚拟地址空间
虚拟内存的概念诞生于1950年代,它是解决复杂问题的一种解决方案,该问题涉及如何处理无法一次全部放入真实内存的程序。 在虚拟内存系统中,程序可以访问比物理上可用的地址更大的地址集,并且专用的内存管理器使用磁盘上的临时存储来防止溢出,将这些逻辑地址映射到实际位置。
在Windows使用的现代虚拟内存实现中,虚拟存储被组织为大小相等的单元,称为页面 。 每个操作系统进程都分配有自己的虚拟地址空间 -一组虚拟内存页,可以从中读取和写入这些虚拟内存页。 每个页面可以处于以下三种状态之一:
- 空闲 :该进程尚未使用该地址空间区域。 任何尝试访问该区域以进行读取或写入的尝试都会导致某种类型的运行时故障。 Windows对话框可能会弹出,说明发生了访问冲突。 (您的Java程序不能犯这种错误;只有用支持指针的语言编写的程序可以。)
- Reserved :地址空间的此区域已被保留,供进程将来使用,但是只有在提交后才能访问。 许多Java堆都是从保留开始的。
- Committed :程序可以访问并完全支持的内存,这意味着已在页面文件中为其分配了页面框架。 提交的页面仅在进程首先引用它们时才加载到主存储器中。 因此,名称为按需分页 。
图1说明了如何将进程地址空间中的虚拟页面映射到内存中的物理页面框架。
图1.流程地址空间中的虚拟页面到物理页面框架的映射
如果您在32位计算机(例如,普通的Intel处理器)上运行,则该进程的总虚拟地址空间为4GB,因为4GB是您最多可以用32位寻址的数字。 Windows通常不允许您访问此地址空间中的所有内存。 您的进程仅占一半以供私人使用,其余部分由Windows使用。 2GB的专用区域包含JVM执行程序所需的大部分内存:Java堆,JVM本身的C堆,程序线程的堆栈,用于保存字节码和JITted方法的内存,本机方法分配的内存,和更多。 我们将在本文后面的地址空间映射中识别其中一些。
想要分配较大且连续的内存区域但又不需要立即全部使用的程序通常会使用保留内存和提交内存的组合。 JVM通过这种方式分配Java堆。 JVM的-mx
参数告诉它堆的最大大小应该是多少,但是JVM在开始时通常不会分配所有的内存。 它保留由-mx
指定的-mx
,将整个地址范围标记为可提交。 然后,它仅提交一部分内存,并且仅对于这一部分,内存管理器才需要在实际内存和分页文件中分配页面以备份它们。 稍后,当活动数据量增加并且需要扩展堆时,JVM可以在当前提交区域附近提交更多内容。 这样,JVM可以在按需增长的同时维护单个连续堆(请参阅参考资料,获取有关如何使用JVM堆参数的文章)。
真实记忆
物理存储也被组织成相等大小的单位,通常称为页面框架 。 称为页表的操作系统数据结构将应用程序访问的虚拟页映射到主内存中的实际页框架。 无法容纳的页面将保留在光盘上的临时页面文件中。 当某个进程尝试访问当前不在内存中的页面时,就会发生页面错误 ,该错误会导致内存管理器从页面文件中检索页面并将其放回主内存中-这是一个称为pageing的任务。 用于决定换出哪一页的精确算法取决于您所使用的Windows版本。 这可能是最近访问的方法的一种变体。 同样重要的是要注意Windows允许在进程之间共享页面框架,例如DLL,这些DLL通常经常被多个应用程序一次使用。 Windows通过将多个虚拟页面从不同的地址空间映射到同一物理位置来实现此目的。
应用程序很高兴没有意识到所有这些活动。 它所知道的只是它自己的虚拟地址空间。 但是,如果当前位于主内存中的应用程序页面集(称为驻留集 )小于其实际需要使用的页面集(称为工作集) ,则应用程序很快就会开始性能下降。 (不幸的是,正如您将在本文中看到的那样,我们将讨论的工具经常互换使用这两个术语,即使它们所指的是完全不同的事物。)
任务管理器和PerfMon
我们首先来看两个最常用的工具,任务管理器和PerfMon。 它们都与Windows捆绑在一起,因此您可以轻松开始使用它们。
任务管理器
任务管理器是一个相当简单的Windows进程监视器。 您可以使用熟悉的Ctrl-Alt-Delete组合键或右键单击任务栏来访问它。 Processes选项卡显示了最详细的信息,如图2所示。
图2. Task Manager的Processs选项卡
通过选择“ 视图”>“选择列”,可以自定义图2显示的列 。 一些列标题具有相当隐秘的名称,但是您可以在任务管理器帮助中找到每个定义的名称。 与进程的内存使用量最相关的计数器是:
- 内存用法 :在线帮助将其称为流程的工作集(尽管许多人将其称为常驻集)-当前位于主内存中的页面集。 但是,要注意的是它包含可由其他进程共享的页面,因此您必须注意不要重复计算。 例如,如果您试图找出使用公用DLL的两个进程的合并内存使用情况,则不能简单地添加它们的“内存使用情况”值。
- 峰值内存使用量 :自进程启动以来,“内存使用量”字段的最大值。
- 页面错误 :自进程开始以来,访问页面不在主内存中的总次数。
- VM大小 :联机帮助将其描述为“进程分配的总私有虚拟内存”。 需要明确的是,这是进程已提交的专用内存。 如果进程保留内存但不提交,则这可能与总地址空间的大小完全不同。
尽管Windows文档将“内存使用情况”称为工作集,但了解这种情况确实意味着许多人称为常驻集,这可能会有所帮助。 你会发现在内存管理参考的术语,这些术语(见定义相关主题 )。 工作集更通常是指在这一点上该过程需要在内存中具有哪些页面以避免任何页面调度的逻辑概念。
性能监视器
Microsoft随Windows一起提供的另一个工具是PerfMon,它可以监视从打印队列到电话的各种计数器。 PerfMon通常在系统路径上,因此您可以通过从命令行输入perfmon
来启动它。 该工具的优势在于它以图形方式显示计数器,因此您可以轻松查看它们随着时间的变化。
要开始操作,请单击PerfMon屏幕顶部工具栏上的+按钮。 这将弹出一个对话框,让您选择要监视的计数器,如图3a所示。 这些计数器分为几类,称为性能对象 。 与内存使用相关的两个是Memory和Process 。 您可以通过突出显示计数器并单击“ 解释”按钮来获得其定义。 然后,说明将出现在一个单独的窗口中,该窗口在主对话框下方弹出,如图3b所示。
图3a。 PerfMon计数器窗口
图3b。 说明
选择您感兴趣的计数器(使用Ctrl突出显示多行)以及要监视的实例(您正在检查的应用程序的Java进程),然后单击Add 。 该工具将立即开始显示您选择的所有计数器的值。 您可以将它们显示为报告,一段时间内的图表或直方图。 图4显示了直方图显示。
图4. PerfMon直方图
如果看不到任何图形,则可能需要通过右键单击图形区域,选择“ 属性” ,然后导航至“图形”选项卡来更改比例。 或者,要更改特定计数器的小数位数,请转到其“数据”标签。
柜台提防
不幸的是,PerfMon使用了与任务管理器不同的术语。 表1为您简要概述了最有用的计数器,以及每个计数器的等效任务管理器(如果适用):
表1.有用的PerfMon内存计数器
柜台名称 | 类别 | 描述 | 任务管理器等效 |
---|---|---|---|
工作集 | 处理 | 居民集-实际内存中当前有多少页 | 内存使用 |
专用字节 | 处理 | 已分配的专用虚拟内存总数,即已提交的内存 | 虚拟机大小 |
虚拟字节 | 处理 | 虚拟地址空间的总大小,包括共享页面。 可以大于前两个值中的任何一个,因为它包含保留的内存。 | - |
页面错误/秒 | 处理 | 每秒平均发生的页面错误数 | 链接到“页面错误”,其中显示了总数 |
提交的字节数 | 记忆 | 处于“已提交”状态的虚拟字节总数 | - |
尝试一个例子
通过下载并运行我们用C语言编写的小程序,您可以探索这些数量在任务管理器和PerfMon中的显示方式(请参见下载部分)。 该程序首先使用Windows VirtualAlloc
调用来保留,然后提交内存。 最后,它开始接触一些内存,每4,096个字节向其中写入一个值,以将页面放入工作集中。 如果您运行该程序并使用“任务管理器”或PerfMon进行观看,则会看到值发生了变化。
网络上的有用工具
现在您已经知道应用程序正在使用多少内存,现在该深入了解实际内存内容的详细信息了。 本节介绍一些稍微复杂的工具,讨论何时使用它们,并解释如何解释其输出。
查看
PrcView是我们会告诉你,让你检查进程的地址空间中的内容(见第一工具相关主题 )。 您可以使用它做更多的事情,而不仅仅是查看足迹。 它可以设置优先级并终止进程,它还存在于有用的命令行版本中,该版本列出了计算机上进程的属性。 但是,我们将向您展示如何使用它查看足迹。
当您启动PrcView时,它将显示系统中进程的类似于任务管理器的视图。 如果滚动到并突出显示Java进程,则该屏幕类似于图5中的示例。
图5.初始PrcView屏幕
右键单击Java进程以弹出一个菜单,或从顶部菜单栏中选择进程 ,可以检查有关该进程的一些信息-它拥有哪些线程以及它已经加载了哪些DLL-并且您可以杀死它或设置其优先级。 我们感兴趣的选项是检查其内存,这将弹出如图6所示的屏幕。
图6.检查进程的内存
现在,您可以检查PrcView显示的地址空间映射的前几行。 第一行告诉您,从地址0开始,长度为65,536(64K)的内存可用。 没有分配任何内容,也无法使用地址。 第二行告诉您,紧接在地址0x00010000之后的是已提交内存的8,192字节扩展区(两个4K页),这些内存可以寻址,并由页面文件中的页面帧支持。 然后是另一个自由拉伸,然后是另一个承诺拉伸,依此类推。
由于Windows使用了地址空间,因此该地址空间的任何区域对您都没有任何意义。 Microsoft的描述Windows地址空间的文档说,这些是保留用于MS-DOS兼容性的各个区域,用于用户数据和代码的区域起始于4MB(请参阅参考资料 )。
如果向下滚动,最终会到达地址空间中您可以清楚识别的位置,如图7所示。
图7. Java堆值
图7中突出显示的行及其紧下方的一行对应于Java堆。 我们在这里开始的Java进程有1000MB的堆空间(使用-mx1000m
),对于有问题的程序来说,这是非常大的,但由于达到了这个大小,因此可以清楚地显示在PrcView映射中。 高亮显示的行显示堆的已提交部分只有4MB,从地址0x10180000开始。 突出显示的行之后紧接着是一个较大的保留段,这是堆中尚未提交的其余部分。 在启动期间,JVM最初保留了完整的1000MB(使地址范围0x10180000到0x4e980000不可用),然后仅提交了启动所需的空间,在本例中为4MB。 要验证此值确实确实与当前堆大小相对应,可以使用-verbosegc
JVM选项调用Java程序,该选项会打印出垃圾回收器的详细信息。 从以下-verbosegc
输出的第二个GC的第二行中,您可以看到当前堆大小约为4MB:
>java -mx1000m -verbosegc Hello
[ JVMST080: verbosegc is enabled ]
[ JVMST082: -verbose:gc output will be written to stderr ]
<GC[0]: Expanded System Heap by 65536 bytes
<GC(1): GC cycle started Wed Sep 29 16:55:44 2004
<GC(1): freed 417928 bytes, 72% free (3057160/4192768), in 104 ms>
<GC(1): mark: 2 ms, sweep: 0 ms, compact: 102 ms>
<GC(1): refs: soft 0 (age >= 32), weak 0, final 2, phantom 0>
<GC(1): moved 8205 objects, 642080 bytes, reason=4>
-verbosegc
输出的格式取决于您使用的JVM实现。 看到相关主题有关IBM JVM上的一篇文章或者咨询自己的提供商的文档。
如果实时数据量增加,并且JVM需要将堆扩展到4MB以上,它将占用更多的保留区域。 这意味着新区域可以从0x10580000开始,与已经提交的堆内存相邻。
图7中 PrcView屏幕底部一行的三个总计为您提供了您的进程的总提交内存,该内存基于第七列Type总计。 总计为:
- 专用 :页面文件支持的已承诺内存
- 映射 :已提交的内存直接映射到文件系统
- 图片 :属于可执行代码(起始可执行文件和DLL)的承诺内存
到目前为止,仅根据堆的大小将堆放置在地址空间内。 为了更好地了解其他一些内存区域,能够对内存内部进行观察将很有帮助。 这就是我们将要讨论的下一个工具TopToBottom。
从上到下
TopToBottom可免费从smidgeonsoft.com(参见相关主题 )。 它没有任何文档,但是提供了对当前正在执行的过程的全面视图。 您不仅可以按名称和进程ID对进程进行排序,还可以按启动时间进行排序,如果您需要了解计算机上程序启动的顺序,这将非常有用。
图8显示了TopToBottom以及按创建时间排序的进程列表(“ 视图”>“排序”>“创建时间” )。
图8. TopToBottom流程按创建时间排序
“启动”选项卡显示了创建Java进程的进程,启动该进程的时间和日期,用于调用该进程的实际命令行以及可执行文件和当前目录的完整路径。 您还可以单击“环境”选项卡,以显示启动时传递到进程中的所有环境变量的值。 Modules选项卡显示了我们的Java进程正在使用的DLL,如图9所示。
图9. TopToBottom模块选项卡
同样,您可以通过多种方式对列表进行排序。 在图9中,它们按初始化顺序排序。 如果双击某行,将看到有关DLL的详细信息:其地址和大小,写入的日期和时间,其依赖的其他DLL列表以及所有正在运行的进程的列表。已经加载了DLL。 如果您浏览该列表,将会发现每个运行的进程都需要一些DLL,例如NTDLL.DLL。 有些,例如JVM.DLL,在所有Java进程之间共享; 其他可能仅由单个进程使用。
您可以通过累加各个DLL的大小来尝试计算出该进程使用的DLL的总大小。 但是,得出的数字可能会引起误解,因为这并不意味着该过程正在消耗所有的内存。 实际大小取决于进程实际使用的DLL的哪些部分。 这些部分有助于流程的工作。 看起来似乎很明显,但是也值得注意的是DLL是只读的和共享的。 如果许多进程都使用给定的DLL,则任何时候只有一组实内存页可保存DLL数据。 然后,可以将这些真实页面在许多不同的地址映射到正在使用它们的进程中。 诸如任务管理器之类的工具将工作集报告为共享和非共享页面的总数,因此,很难确定DLL使用对占用空间的真正影响。 模块信息是获取DLL占用空间的“最坏情况”视图的有用方法,如有必要,您可以通过使用其他工具进行更详细的分析来进一步优化。
您对内存占用量感兴趣,因此请单击“内存”选项卡。 图10显示了Java程序使用的所有内存的一小部分。
图10. TopToBottom Memory选项卡
此显示与PrcView相似,但仅显示虚拟地址空间中的已提交内存,而不显示保留内存。 但是,它有两个优点。 首先,它可以更详细地表征页面。 例如,在图10中,它专门标识了Thread 3760堆栈区域,而不仅仅是一些读/写数据。 它识别的其他数据区域包括环境,进程参数,进程堆,线程堆栈和线程环境块(TEB)。 其次,您可以直接在TopToBottom本身内部浏览甚至搜索内存。 您可以搜索文本字符串,也可以十六进制搜索最多16个字节的序列。 您可以将十六进制搜索限制为指定的对齐方式,这在搜索对地址的引用时很有用。
TopToBottom还具有快照功能,可将其具有的有关该过程的所有信息转储到剪贴板。
VADump
VADump是Microsoft®Platform SDK软件包的一部分,是一个方便的命令行工具(请参阅参考资料 )。 其目的是转储特定进程的虚拟地址空间和驻留集的概述。 使用VADump的最简单方法是在命令行中输入以下命令:
vadump process_id
process_id是您感兴趣的进程号。您可以不带任何参数调用VADump以显示完整的用法信息。 我们还建议您将输出通过管道传输到文件(例如, vadump 1234 > output.txt
),因为VADump会生成太多信息以致无法显示在屏幕上。
输出首先显示进程的虚拟地址空间的索引:
>vadump -p 3904
Address: 00000000 Size: 00010000
State Free
Address: 00010000 Size: 00002000
State Committed
Protect Read/Write
Type Private
Address: 00012000 Size: 0000E000
State Free
Address: 00020000 Size: 00001000
State Committed
Protect Read/Write
Type Private
Address: 00021000 Size: 0000F000
State Free
Address: 00030000 Size: 00010000
State Committed
Protect Read/Write
Type Private
Address: 00040000 Size: 0003B000 RegionSize: 40000
State Reserved
Type Private
................................
(为了便于阅读,我们在虚线处截断了输出。)
对于每个块,您可以看到以下信息:
- 地址 :相对于进程的虚拟地址空间开始的十六进制格式
- 大小 :以字节为单位,也以十六进制表示
- 状态 :免费,保留或承诺
- 保护状态 :只读或读/写
- 类型 :私有(其他进程无法访问),映射(直接从文件系统访问)或映像(可执行代码)
然后,它列出了进程使用的所有DLL及其大小,然后是有关工作集和页面文件使用情况的统计信息摘要。
到目前为止,还可以从其他工具获得此信息。 但是,通过使用VADump的-o
选项,您可以生成更多具有启发性的输出。 它将生成当前工作集的快照(在给定的时间点实际上位于主内存中的页面)。 该选项的文献资料很少,但是对于确定常驻集的最重要组成部分是什么,以及对于内存优化最有希望的候选对象,它可能非常有用。 您还可以使用它来确定您是否有内存泄漏,方法是在一段时间内定期拍摄快照。 在这种模式下,输出从虚拟地址空间中已提交的页面的更详细的转储开始,而不管它们当前是否在主内存中:
>vadump -o -p 3904
0x00010000 (0) PRIVATE Base 0x00010000
0x00011000 (0) PRIVATE Base 0x00010000
0x00020000 (0) PRIVATE Base 0x00020000
0x00030000 (0) PRIVATE Base 0x00030000
0x00031000 (0) Private Heap 2
0x00032000 (0) Private Heap 2
0x00033000 (0) Private Heap 2
0x00034000 (0) Private Heap 2
0x00035000 (0) Private Heap 2
0x00036000 (0) Private Heap 2
0x00037000 (0) Private Heap 2
0x00038000 (0) Private Heap 2
0x00039000 (0) Private Heap 2
0x0003A000 (0) Private Heap 2
0x0003B000 (0) Private Heap 2
0x0003C000 (0) Private Heap 2
0x0003D000 (0) Private Heap 2
0x0003E000 (0) Private Heap 2
0x0003F000 (0) Private Heap 2
0x0007C000 (0) Stack for ThreadID 00000F64
0x0007D000 (0) Stack for ThreadID 00000F64
0x0007E000 (0) Stack for ThreadID 00000F64
0x0007F000 (0) Stack for ThreadID 00000F64
0x00080000 (7) UNKNOWN_MAPPED Base 0x00080000
0x00090000 (0) PRIVATE Base 0x00090000
0x00091000 (0) Process Heap
0x00092000 (0) Process Heap
0x00093000 (0) Process Heap
...........................
如果向下滚动到该长列表的末尾,您将获得更有趣的信息:当前驻留在主内存中的进程页面的页面表映射列表:
0xC0000000 > (0x00000000 : 0x003FFFFF) 132 Resident Pages
(0x00280000 : 0x00286000) > jsig.dll
(0x00290000 : 0x00297000) > xhpi.dll
(0x002A0000 : 0x002AF000) > hpi.dll
(0x003C0000 : 0x003D8000) > java.dll
(0x003E0000 : 0x003F7000) > core.dll
(0x00090000 : 0x00190000) > Process Heap segment 0
(0x00190000 : 0x001A0000) > Private Heap 0 segment 0
(0x001A0000 : 0x001B0000) > UNKNOWN Heap 1 segment 0
(0x00380000 : 0x00390000) > Process Heap segment 0
(0x00030000 : 0x00040000) > Private Heap 2 segment 0
(0x00390000 : 0x003A0000) > Private Heap 3 segment 0
(0x00040000 : 0x00080000) > Stack for thread 0
0xC0001000 > (0x00400000 : 0x007FFFFF) 13 Resident Pages
(0x00400000 : 0x00409000) > java.exe
.................................................................
这些映射中的每一个对应于页表中的单个条目,因此为该流程的工作集贡献了额外的4KB。 从这些映射中找出应用程序的哪些部分正在使用最多的内存仍然是非常困难的,但是幸运的是,输出的下一部分是一个有用的摘要:
Category Total Private Shareable Shared
Pages KBytes KBytes KBytes KBytes
Page Table Pages 20 80 80 0 0
Other System 10 40 40 0 0
Code/StaticData 1539 6156 3988 1200 968
Heap 732 2928 2928 0 0
Stack 9 36 36 0 0
Teb 5 20 20 0 0
Mapped Data 30 120 0 0 120
Other Data 1314 5256 5252 4 0
Total Modules 1539 6156 3988 1200 968
Total Dynamic Data 2090 8360 8236 4 120
Total System 30 120 120 0 0
Grand Total Working Set 3659 14636 12344 1204 1088
最有趣的两个值通常是Heap(即Windows进程堆)和Other Data。 通过Windows API调用直接分配的内存构成了进程堆的一部分,而其他数据包括Java堆。 总体工作集与任务管理器的“内存使用情况”以及“ Teb”字段相关,该字段是进程的“线程环境块”(一种内部Windows结构)所需的内存。
最后, VADump -o
输出的底部总结了DLL,堆和线程堆栈对工作集的相对贡献:
Module Working Set Contributions in pages
Total Private Shareable Shared Module
9 2 7 0 java.exe
85 5 0 80 ntdll.dll
43 2 0 41 kernel32.dll
15 2 0 13 ADVAPI32.dll
11 2 0 9 RPCRT4.dll
53 6 0 47 MSVCRT.dll
253 31 222 0 jvm.dll
6 3 3 0 jsig.dll
7 4 3 0 xhpi.dll
15 12 3 0 hpi.dll
12 2 0 10 WINMM.dll
21 2 0 19 USER32.dll
14 2 0 12 GDI32.dll
6 2 0 4 LPK.DLL
10 3 0 7 USP10.dll
24 18 6 0 java.dll
22 16 6 0 core.dll
18 14 4 0 zip.dll
915 869 46 0 jitc.dll
Heap Working Set Contributions
6 pages from Process Heap (class 0x00000000)
0x00090000 - 0x00190000 6 pages
2 pages from Private Heap 0 (class 0x00001000)
0x00190000 - 0x001A0000 2 pages
0 pages from UNKNOWN Heap 1 (class 0x00008000)
0x001A0000 - 0x001B0000 0 pages
1 pages from Process Heap (class 0x00000000)
0x00380000 - 0x00390000 1 pages
715 pages from Private Heap 2 (class 0x00001000)
0x00030000 - 0x00040000 15 pages
0x008A0000 - 0x009A0000 241 pages
0x04A60000 - 0x04C60000 450 pages
0x054E0000 - 0x058E0000 9 pages
1 pages from Private Heap 3 (class 0x00001000)
0x00390000 - 0x003A0000 1 pages
7 pages from Private Heap 4 (class 0x00001000)
0x051A0000 - 0x051B0000 7 pages
Stack Working Set Contributions
4 pages from stack for thread 00000F64
1 pages from stack for thread 00000F68
1 pages from stack for thread 00000F78
1 pages from stack for thread 00000F7C
2 pages from stack for thread 00000EB0
您还可以在这种模式下使用VADump来准确查看两个或多个Java进程的合并占用空间(请参阅本文后面的技巧和窍门 )。
Sysinternals流程浏览器
然而,对于分析存储更多的有用工具都来自一个叫Sysinternals的公司(参见相关主题 )。 一个是图形化的流程浏览器,如图11所示,可以用作Task Manager的高级替代品。
图11. Process Explorer进程树
Process Explorer具有与任务管理器相同的所有功能。 例如,您可以获取总体系统性能的动态图(使用“ 视图”>“系统信息...” ),并且可以以类似方式在主流程视图中配置列。 在“ 进程”>“属性...”下 ,“进程资源管理器”提供了有关进程的更多信息,例如完整路径和命令行,线程以及CPU使用率和专用字节的动态图。 如图11所示,它的用户界面非常出色。它还可以检查有关DLL的信息和进程的句柄。 默认情况下,您可以使用选项>替换任务管理器来执行Process Explorer,而不是任务管理器。
Sysinternals ListDLLs
您还可以下载一些Sysinternals命令行实用程序-ListDLLs和Handle。 如果要将某种形式的内存监视合并到脚本或程序中,它们特别有用。
ListDLL使您可以查看DLL,这些DLL可以显着增加内存占用。 要开始使用它,请将其添加到您的路径中,并使用help选项将其调用以获取使用情况信息。 您可以使用进程ID或名称来调用它。 这是我们的Java程序使用的DLL列表:
>listdlls -r 3904
ListDLLs V2.23 - DLL lister for Win9x/NT
Copyright (C) 1997-2000 Mark Russinovich
http://www.sysinternals.com
---------------------------------------------------------------------
java.exe pid: 3904
Command line: java -mx1000m -verbosegc Hello
Base Size Version Path
0x00400000 0x9000 141.2003.0005.0022 C:\WINDOWS\system32\java.exe
0x77f50000 0xa7000 5.01.2600.1217 C:\WINDOWS\System32\ntdll.dll
0x77e60000 0xe6000 5.01.2600.1106 C:\WINDOWS\system32\kernel32.dll
0x77dd0000 0x8d000 5.01.2600.1106 C:\WINDOWS\system32\ADVAPI32.dll
0x78000000 0x87000 5.01.2600.1361 C:\WINDOWS\system32\RPCRT4.dll
0x77c10000 0x53000 7.00.2600.1106 C:\WINDOWS\system32\MSVCRT.dll
0x10000000 0x178000 141.2004.0003.0001 C:\Java141\jre\bin\jvm.dll
### Relocated from base of 0x10000000:
0x00280000 0x6000 141.2004.0003.0001 C:\Java141\jre\bin\jsig.dll
### Relocated from base of 0x10000000:
0x00290000 0x7000 141.2004.0003.0001 C:\Java141\jre\bin\xhpi.dll
### Relocated from base of 0x10000000:
0x002a0000 0xf000 141.2004.0003.0001 C:\Java141\jre\bin\hpi.dll
0x76b40000 0x2c000 5.01.2600.1106 C:\WINDOWS\system32\WINMM.dll
0x77d40000 0x8c000 5.01.2600.1255 C:\WINDOWS\system32\USER32.dll
0x7e090000 0x41000 5.01.2600.1346 C:\WINDOWS\system32\GDI32.dll
0x629c0000 0x8000 5.01.2600.1126 C:\WINDOWS\system32\LPK.DLL
0x72fa0000 0x5a000 1.409.2600.1106 C:\WINDOWS\system32\USP10.dll
### Relocated from base of 0x10000000:
0x003c0000 0x18000 141.2004.0003.0001 C:\Java141\jre\bin\java.dll
### Relocated from base of 0x10000000:
0x003e0000 0x17000 141.2004.0003.0001 C:\Java141\jre\bin\core.dll
### Relocated from base of 0x10000000:
0x04a40000 0x12000 141.2004.0003.0001 C:\Java141\jre\bin\zip.dll
### Relocated from base of 0x10000000:
0x04df0000 0x3a1000 141.2004.0003.0001 C:\Java141\jre\bin\jitc.dll
另外, listdlls -r java
命令显示所有正在运行的Java进程及其正在使用的DLL。
Sysinternals手柄
句柄显示进程正在使用的句柄列表(到文件,套接字等)。 解压缩Handle下载文件并将其添加到您的路径中以进行尝试。 如果您在我们的Java程序上尝试它,它将产生以下输出:
>handle -p 3904
Handle v2.2
Copyright (C) 1997-2004 Mark Russinovich
Sysinternals - www.sysinternals.com
------------------------------------------------------------------
java.exe pid: 3904 99VXW67\cem
c: File C:\wsappdev51\workspace\Scratch
4c: File C:\wsappdev51\workspace\Scratch\verbosegc.out
50: File C:\wsappdev51\workspace\Scratch\verbosegc.out
728: File C:\WebSphere MQ\Java\lib\com.ibm.mq.jar
72c: File C:\WebSphere MQ\Java\lib\fscontext.jar
730: File C:\WebSphere MQ\Java\lib\connector.jar
734: File C:\WebSphere MQ\Java\lib\jms.jar
738: File C:\WebSphere MQ\Java\lib\jndi.jar
73c: File C:\WebSphere MQ\Java\lib\jta.jar
740: File C:\WebSphere MQ\Java\lib\ldap.jar
744: File C:\WebSphere MQ\Java\lib\com.ibm.mqjms.jar
748: File C:\WebSphere MQ\Java\lib\providerutil.jar
74c: File C:\Java141\jre\lib\ext\oldcertpath.jar
750: File C:\Java141\jre\lib\ext\ldapsec.jar
754: File C:\Java141\jre\lib\ext\JawBridge.jar
758: File C:\Java141\jre\lib\ext\jaccess.jar
75c: File C:\Java141\jre\lib\ext\indicim.jar
760: File C:\Java141\jre\lib\ext\ibmjceprovider.jar
764: File C:\Java141\jre\lib\ext\ibmjcefips.jar
768: File C:\Java141\jre\lib\ext\gskikm.jar
794: File C:\Java141\jre\lib\charsets.jar
798: File C:\Java141\jre\lib\xml.jar
79c: File C:\Java141\jre\lib\server.jar
7a0: File C:\Java141\jre\lib\ibmjssefips.jar
7a4: File C:\Java141\jre\lib\security.jar
7a8: File C:\Java141\jre\lib\graphics.jar
7ac: File C:\Java141\jre\lib\core.jar
您可以看到我们的进程对我们的类路径上的目录和几个JAR文件都有一个句柄。 实际上,该进程具有更多的句柄,但默认情况下,该实用程序仅显示引用文件的句柄。 您可以使用-a
参数显示其他内容:
>handle -a -p 3904
Handle v2.2
Copyright (C) 1997-2004 Mark Russinovich
Sysinternals - www.sysinternals.com
------------------------------------------------------------------
java.exe pid: 3904 99VXW67\cem
c: File C:\wsappdev51\workspace\Scratch
4c: File C:\wsappdev51\workspace\Scratch\verbosegc.out
50: File C:\wsappdev51\workspace\Scratch\verbosegc.out
71c: Semaphore
720: Thread java.exe(3904): 3760
724: Event
728: File C:\WebSphere MQ\Java\lib\com.ibm.mq.jar
72c: File C:\WebSphere MQ\Java\lib\fscontext.jar
730: File C:\WebSphere MQ\Java\lib\connector.jar
734: File C:\WebSphere MQ\Java\lib\jms.jar
738: File C:\WebSphere MQ\Java\lib\jndi.jar
73c: File C:\WebSphere MQ\Java\lib\jta.jar
740: File C:\WebSphere MQ\Java\lib\ldap.jar
744: File C:\WebSphere MQ\Java\lib\com.ibm.mqjms.jar
748: File C:\WebSphere MQ\Java\lib\providerutil.jar
74c: File C:\Java141\jre\lib\ext\oldcertpath.jar
750: File C:\Java141\jre\lib\ext\ldapsec.jar
754: File C:\Java141\jre\lib\ext\JawBridge.jar
758: File C:\Java141\jre\lib\ext\jaccess.jar
75c: File C:\Java141\jre\lib\ext\indicim.jar
760: File C:\Java141\jre\lib\ext\ibmjceprovider.jar
764: File C:\Java141\jre\lib\ext\ibmjcefips.jar
768: File C:\Java141\jre\lib\ext\gskikm.jar
76c: Key HKCU
770: Semaphore
774: Thread java.exe(3904): 3964
778: Event
77c: Semaphore
780: Semaphore
784: Thread java.exe(3904): 3960
788: Event
78c: Thread java.exe(3904): 3944
790: Event
794: File C:\Java141\jre\lib\charsets.jar
798: File C:\Java141\jre\lib\xml.jar
79c: File C:\Java141\jre\lib\server.jar
7a0: File C:\Java141\jre\lib\ibmjssefips.jar
7a4: File C:\Java141\jre\lib\security.jar
7a8: File C:\Java141\jre\lib\graphics.jar
7ac: File C:\Java141\jre\lib\core.jar
7b0: Event
7b4: Thread java.exe(3904): 3940
7b8: Event
7bc: Semaphore
7c0: Directory \BaseNamedObjects
7c4: Key HKLM\SOFTWARE\Windows NT\Drivers32
7c8: Semaphore
7cc: Semaphore
7d0: Event
7d4: Desktop \Default
7d8: WindowStation \Windows\WindowStations\WinSta0
7dc: Event
7e0: WindowStation \Windows\WindowStations\WinSta0
7e4: Event
7e8: Section
7ec: Port
7f0: Directory \Windows
7f4: Key HKLM
7f8: Directory \KnownDlls
7fc: KeyedEvent \KernelObjects\CritSecOutOfMemoryEvent
如果您对内存感兴趣,则句柄很重要,因为每个句柄都会占用一些空间。 确切的数量取决于操作系统版本和句柄类型。 通常,手柄不应对占用空间做出重大贡献。 通过简单地计算该实用程序输出的行数,您可以快速查看句柄的数量是否显着或者(甚至更糟)在增长。 每种情况都可能引起关注,并建议进行一些更详细的调查。
技巧和窍门
既然您已经掌握了我们展示给您的所有工具的功能(没有双关语),下面是一些可以单独使用或一起使用它们以改善内存监视的方法。
查找进程ID
要查找应用程序的进程ID,以便可以在诸如VADump之类的命令行工具中使用它,请在“任务管理器”中打开“应用程序”标签,然后右键单击所需的进程。选择“ 转到进程” 。 这会将您带到“进程”选项卡中的相应ID。
识别Java进程
您是否曾经为所有名为java或javaw的进程列表感到困惑,试图找出您要调查的进程? 如果从IDE或脚本中启动了Java进程,则可能很难确定正在使用哪个JVM,以及哪些命令行参数已发送到Java进程。 在TopToBottom启动选项卡上可以轻松获得此信息。 您将看到用于调用JVM的完整命令行以及进程启动的时间。
识别手柄猪
是否曾经试图保存文件只是被告知另一进程正在使用它? 即使关闭您认为负责的程序,仍然会收到错误消息? 您可以使用SysInternals Process Explorer工具的“句柄搜索”工具来查找罪魁祸首。 只需打开“搜索”对话框并输入文件名即可。 ProcExp浏览所有打开的句柄并识别过程。 通常,这是关闭用户界面后由编辑器或Web浏览器运行的一个很小的存根进程。
调查正在共享多少内存
您可以将VADump与-o
选项一起使用,以详细了解进程当前工作集中的内容以及共享的数量。 对系统上运行的单个Java程序进行转储,然后启动另一个程序并再次进行转储。 如果比较每个摘要中的Code / StaticData值,您将看到“ Shareable”字节已变为“ Shared”,从而少量减少了增量占用空间。
修剪居民集
Windows实施了一项策略,即在似乎不使用该进程的驻留集时对其进行“修剪”。 为了证明这一点,请在任务管理器中打开“进程”选项卡,以便可以查看正在监视的应用程序的过程,然后最小化应用程序窗口。 观察“内存使用情况”字段会发生什么!
确定您的应用程序所需的最小内存量
对于Windows Server 2003和Windows NT,微软提供了一个叫做ClearMem有趣的工具,如果你想进一步探讨应用程序如何使用Windows下的内存(请参阅可能有用的相关信息 )。 该工具确定实际内存的大小,分配足够的内存以消耗它,尽快触摸分配的内存,然后释放它。 这严重影响了其他应用程序的内存消耗,并且多次运行ClearMem的最终结果是将应用程序正在使用的内存量降低到最小值。
结论
在本文中,我们概述了Windows如何管理内存,并调查了一些可用于监视Java程序的内存使用情况的最有用的免费工具。 毫无疑问,您会找到并使用其他工具,无论它们是从Web上还是从商业产品中免费下载,但我们希望我们为您提供了足够的知识,可以对付术语上的矛盾。 通常,唯一确定要确切知道要测量的内容的唯一方法是执行实验,例如我们用来演示任务管理器的VM大小和内存使用情况的含义的小C程序。
当然,这些工具只能帮助您确定问题。 然后由您来解决。 大多数时候,您会发现Java堆吞噬了最大的内存,并且您需要深入研究代码的详细信息,以确保对象引用的保留时间不会超过必需的时间。 更多工具和文章可以帮助您实现这一目标,“ 参考资料”部分中的一些有用链接应为您指明正确的方向。
翻译自: https://www.ibm.com/developerworks/java/library/j-memusage/index.html