精华内容
下载资源
问答
  • java管理windows系统内存_java释放内存缓存_java获得CPU使用率_系统内存_硬盘_进程源代码
  • 监控Java应用程序Windows内存使用情况

    千次阅读 2019-01-20 17:08:42
    监控Java应用程序Windows内存使用情况
                   
    尽管 Java™运行时能够解决大量的内存管理问题,但对程序的内存占用情况保持警惕仍然是优化机器性能、测定内存泄露的关键。Windows上有很多工具可以监控内存的使用。但每种工具各有长短,都有特定的倾向性,常常没有明确地定义自己测量的是什么。作者将澄清关于内存使用的一些常见误解,介绍很多有用的工具,同时还将提供何时以及如何使用它们的指南。
         

    Java技术最知名的一个优点是:与其他语言如 C 程序员不同,Java 程序员不需要对令人畏惧的内存分配和释放负责。Java运行库可以为您管理这些任务。每个实例化的对象都自动在堆中分配内存,垃圾收集程序定期收回不再使用的对象所占据的内存。但是您还不能完全撒手不管。您仍然需要监控程序的内存使用情况,因为 Java 进程的内存不仅仅包括堆中分配的对象。它还包括程序的字节码(JVM在运行时解释执行的指令)、JIT 代码(已经为目标处理器编译过的代码)、任何本机代码和 JVM使用的一些元数据(异常表、行号表等等)。情况更为复杂的是,某些类型的内存(如本机库)可以在进程间共享,因此确定 Java应用程序的内存占用可能是一项非常艰巨的任务。

         

    有大量在 Windows 监控内存使用的工具,但不幸的是没有一种能够提供您需要的所有信息。更糟的是,这些形形色色的工具甚至没有一个公共的词汇表。但本文会助您一臂之力,文中将介绍一些最有用的、可免费获得的工具,并提供了如何使用它们的技巧。

         

    Windows 内存:一次旋风般的旅行

         

    了解本文要讨论的工具之前,需要对 Windows 如何管理内存有基本的理解。Windows 使用一种        分页请求虚拟内存系统,现在我们就来分析一下这种系统。     

         

    虚拟地址空间

         

    虚拟内存的概念在上个世纪五十年代就提出了,当时是作为解决不能一次装入实际内存的程序这一复杂问题的方案提出的。在虚拟内存系统中,程序可以访问超出可用物理内存的更大的地址集合,专用内存管理程序将这些逻辑地址映射到实际地址,使用磁盘上的临时存储保存超出的部分。

         

    Windows 所使用的现代虚拟内存实现中,虚拟存储被组织成大小相同的单位,称为        。每个操作系统进程占用自己的        虚拟地址空间,即一组可以读写的虚拟内存页。每个页可以有三种状态:     

         
    •           自由:还没有进程使用这部分地址空间。如果企图访问这部分空间,无论读写都会造成某种运行时失效。该操作将导致弹出一个 Windows 对话框,提示出现了访问冲突。(Java 程序不会造成这种错误,只有用支持指针的语言编写的程序才可能造成这种问题。)         

    •           保留:这部分地址空间保留给进程,以供将来使用,但是在交付之前,不能访问该地址空间。很多 Java 堆在一开始处于保留状态。         

    •           提交:程序可以访问的内存,得到了完全          支持,就是说已经在分页文件中分配了页帧。提交的页只有在第一次被引用时才装入主存,因此成为          请求式分页。       

    图 1 说明了进程地址空间中的虚拟页如何映射到内存中的物理页帧。

                 
    图 1. 进程地址空间中的虚拟页到物理页帧的映射
    内存组织

    如果运行的是 32 位机器(如一般的 Intel 处理器),那么进程的整个虚拟地址空间就是 4GB,因为这是用 32位所能寻址的最大地址空间。Windows 通常不会允许您访问地址空间中的所有这些内存,进程自己使用的只有不到一半,其他供 Windows使用。这 2 GB 的私有空间部分包含了 JVM 执行程序所需要的多数内存:Java 堆、JVM 本身的 C堆、用于程序线程的栈、保存字节码和即时编译方法的内存、本机方法所分配的内存等等。后面介绍地址空间映射时,我们将描述这些不同的部分。

         

    希望分配了大量连续内存区域但这些内存不马上同时使用的程序常常结合使用保留内存和提交内存。JVM 以这种方式分配 Java 堆。参数         -mx 告诉 JVM 堆有多大,但 JVM 通常不在一开始就分配所有这些内存。它        保留        -mx所规定的大小,标记能够提交的整个地址范围。然后它仅仅提交一部分内存,这也是内存管理程序需要在实际内存和分页文件中分配页来支持它们的那一部分。以后活动数据数量增加,堆需要扩展,JVM 可以再提交多一点内存,这些内存与当前提交的部分相邻。通过这种方式,JVM可以维护单一的、连续的堆空间,并根据需要增长(关于如何使用 JVM 堆参数请参阅 参考资料)。     

         

    实际内存

         

    物理存储页组织成大小相同的单位,通常称为        页帧。操作系统有一种数据结构称为        页表,将应用程序访问的虚拟页映射到主存中的实际页帧。没有装入的页保存在磁盘上的临时分页文件中。当应用程序要访问当前不在内存中的页时,就会出现        页面错误,导致内存管理程序从分页文件中检索该页并放到主存中,这个任务称为        分页。决定将哪些页交换出去的具体算法取决于所用的 Windows 版本,可能是最近最少访问算法的一种变体。同样要注意,Windows允许进程间共享页帧,比如 DLL 分配的页帧,常常被多个应用程序同时使用。Windows通过将来自不同地址空间的多个虚拟页映射到同一个物理地址来实现这种机制。

         

    应用程序很高兴对所有这些活动一无所知。它只知道自己的虚拟地址空间。但是,如果当前在主存中的页面集(称为        驻留集)少于实际要使用的页面集(称为        工作集),应用程序的性能很快就会显著降低。(不幸的是,本文中您将看到,我们要讨论的工具常常交换使用这两个术语,尽管它们指的是完全不同的事物。)     

         





    Task Manager 和 PerfMon

         

    我们首先考察两种最常见的工具:Task Manager 和 PerfMon。这两个工具都随 Windows 一起提供,因此由此起步比较容易。

         

    Task Manager

         

    Task Manager 是一种非常见的 Windows 进程监控程序。您可以通过熟悉的 Ctrl-Alt-Delete 组合键来启动它,或者右击任务栏。Processes 选项卡显示了最详细的信息,如图 2 所示。

                 
    图 2. Task Manager 进程选项卡
    TaskManager

    图 2 中显示的列已经通过选择         View --> Select Columns 作了调整。有些列标题非常含糊,但可以在 Task Manager 帮助中找到各列的定义。和进程内存使用情况关系最密切的计数器包括:     

         
    •           Mem Usage(内存使用):在线帮助将其称为进程的工作集(尽管很多人称之为驻留集)——当前在主存中的页面集。但是这个数值包含能够和其他进程共享的页面,因此要注意避免重复计算。比方说,如果要计算共享同一个 DLL 的两个进程的总内存占用情况,不能简单地把“内存使用”值相加。         

    •           Peak Mem Usage(内存使用高峰值):进程启动以来 Mem Usage(内存使用)字段的最大值。         

    •           Page Faults(页面错误):进程启动以来要访问的页面不在主存中的总次数。         

    •           VM Size(虚拟内存大小):联机帮助将其称为“分配给进程私有虚拟内存总数。”更确切地说,这是进程所          提交的内存。如果进程保留内存而没有提交,那么该值就与总地址空间的大小有很大的差别。       

    虽然 Windows 文档将 Mem Usage(内存使用)称为工作集,但在该上下文中,它实际上指的是很多人所说的驻留集(resident set),明白这一点很重要。您可以在 Memory Management Reference 术语表(请参阅        参考资料)中找到这些术语的定义。        工作集 更通常的含义指的是一个逻辑概念,即在某一点上为了避免分页操作,进程需要驻留在内存中的那些页面。     

         

    PerfMon

         

    随 Windows 一起提供的另一种 Microsoft 工具是 PerfMon,它监控各种各样的计数器,从打印队列到电话。PerfMon 通常在系统路径中,因此可以在命令行中输入         perfmon 来启动它。这个工具的优点是以图形化的方式显示计数器,很容易看到计数器随时间的变化情况。     

         

    请在 PerfMon 窗口上方的工具栏中单击         + 按钮,这样会打开一个对话框让您选择要监控的计数器,如图 3a 所示。计数器按照        性能对象分成不同的类别。与内存使用关系最密切的两个类是         Memory 和         Process。选中计数器然后单击         Explain 按钮,就可以看到计数器的定义。说明出现在主对话框下方弹出的单独的窗口中,如图 3b 所示。     

                 
    图 3a. PerfMon 计数器窗口
    PerfMon 计数器窗口

    图 3b. 说明
    PerfMon 说明窗口

    选择感兴趣的计数器(使用 Ctrl 可以选中多行)和要监控的实例(所分析的应用程序的 Java 进程),然后单击         Add 按钮。工具立刻开始显示选择的所有计数器的值。您可以选择用报告、图表或者直方图来显示这些值。图 4 显示的是一个直方图。     

                 
    图 4. PerfMon 直方图
    PerfMon 直方图

    如果图中什么也看不到,表明您可能需要改变比例,右击图形区域,选择         Properties 然后切换到 Graph 选项卡。也可以到计数器的 Data 选项卡改变某个计数器的比例。     

         

            要观察的计数器       
    不幸的是,PerfMon 使用了与 Task Manager 不同的术语。表 1 列出了最常用的计数器,如果有的话,还给出了相应的 Task Manager 功能:     

         
    表 1. 常用的 PerfMon 内存计数器
                                         
                计数器名                      类别                      说明                      等价的 Task Manager 功能         
    Working Set Process 驻留集,当前在实际内存中有多少页面Mem Usage
    Private Bytes Process 分配的私有虚拟内存总数,即提交的内存VM Size
    Virtual Bytes Process 虚拟地址空间的总体大小,包括共享页面。因为包含保留的内存,可能比前两个值大很多--
    Page Faults / sec(每秒钟内的页面错误数)Process(进程)每秒中出现的平均页面错误数链接到 Page Faults(页面错误),显示页面错误总数
    Committed Bytes(提交的字节数)Memory(内存)“提交”状态的虚拟内存总字节数--

    尝试一个例子

         

    您可以下载并运行我们用 C 编写的一个小程序(请参阅        下载部分),来观察 Task Manager 和 PerfMon 中显示的这些数量。该程序首先调用 Windows         VirtualAlloc 保留内存,然后再提交这些内存,最后使用其中一些内存,每 4,096 个字节写入一个值,从而将页面代入工作集。如果运行该例子,并使用 Task Manager 或 PerfMon 观察,就会发现这些值的变化情况。     

         





    网络上的有用工具

         

    现在已经看到了应用程序使用多少内存,还需要深入分析内存的实际内容。这一节介绍一些更加复杂的工具,讨论什么时候适用输出结果,以及如何解释这些结果。

         

    PrcView

         

    PrcView 是我们要介绍的第一个可以观察进程地址空间内容的工具(请参阅        参考资料)。该工具不仅能用于观察内存占用,还可以设置优先级和杀死进程,还有一个很有用的命令行版本,用来列出机器上所有进程的属性。但我们要介绍的如何使用它观察内存占用情况。     

         

    启动 PrcView 会看到一个类 Task Manager 的视图,它显示了系统中的进程。如果滚动窗口并选中一个 Java 进程,屏幕就会如图 5 所示。

                 
    图 5. 启动后的 PrcView 窗口
    PrcView 进程查看器

    右击该 Java 进程打开弹出菜单,或者从上方的菜单条中选择         Process,就可以看到该进程的一些情况,比如它拥有的线程、加载的 DLL,也可以杀死该进程或者设置其优先级。我们所关心的是考察其内存占用,打开如图 6 所示的窗口。     

                 
    图 6. 观察进程的内存
    PrcView 内存 1

    现在我们分析一下 PrcView 显示的地址空间映射的前几行。第一行表明从地址 0 开始,有一块长度为 65,536 (64K)的内存是自由空间。这些地址什么也没有分配,也不能用于寻址。第二行说明紧跟在后面,从地址 0x00010000 起,有一个长为 8,192字节(两个 4K 页面)的提交内存,即可以寻址并且得到分页文件中的页帧支持的内存。然后是一段自由空间,另一段提交空间,如此等等。

         

    碰巧的是,这些地址空间区域对您来说没有什么意义,因为它是供 Windows 使用的。描述 Windows 地址空间的 Microsoft 文档指出,这些不同的区域是为兼容 MS-DOS 保留的用户数据和代码所用的区域从 4MB 开始(请参阅        参考资料)。     

         

    向下滚动窗口,最终会看到某些您能够清楚识别的地址空间,如图 7 所示。

                 
    图 7. Java 堆
    PrcView 内存 2

    图 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 计算得到的。三个总数为:     

         
    •           Private:分页文件支持的提交内存。       
    •           Mapped:直接映射到文件系统的提交内存。       
    •           Image:属于可执行代码的提交内存,包括启动的执行文件和 DLL。       

    到目前为止,我们只是在根据大小来了解堆在地址空间中的分配情况。为了更好的理解其他一些内存区域,如果能够观察内存的内部情形,会对您的了解很有帮助。这就要用到下面将讨论的工具 TopToBottom。

         

    TopToBottom

         

    TopToBottom 可以从 smidgeonsoft.com 免费获得(请参阅        参考资料)。该工具没有任何文档,但是为当前执行进程提供了一组完备的视图。您不仅能够按名称进程 ID 排序,还能够按起动时间排序,如果需要了解计算机上程序的启动顺序这一点可能很有用。     

         

    图 8 所示的 TopToBottom 窗口中,进程是按创建时间排序的(        View --> Sort --> Creation Time)。     

                 
    图 8. TopToBottom,进程按创建时间排序
    TopToBottom 启动窗口

    StartUp选项卡显示了创建 Java 进程的进程、开始的时间和日期、所用的命令行以及可执行文件和当前目录的完整路径。也可以单击 Environment选项卡显示启动时传递给该进程的所有环境变量的值。Modules 选项卡显示了 Java 进程所用的 DLL,如图 9 所示。

                 
    图 9. TopToBottom Modules 选项卡
    TopToBottom 模块

    同样可以按照不同的方式对列表进行排序。在图 9 中,它们是按照初始化顺序排列的。如果双击其中的一行,可以看到 DLL的详细信息:其地址和大小、编写的日期和时间、所依赖的其他 DLL 列表以及加载该 DLL的所有运行中的进程列表。如果研究这个列表,就会发现有的 DLL 是每个运行的进程都要用到的,比如 NTDLL.DLL;有的在所有 Java进程间共享,比如 JVM.DLL;而另有一些可能只有一个进程使用。

         

    通过累加各个 DLL的大小就可以计算出进程所用 DLL的总大小。但是得到的结果可能会造成误解,因为它并不意味着进程要消费所有这些内存占用。真正的大小取决于进程实际使用了 DLL的哪些部分。这些部分将进入进程的工作集。虽然很明显,但还是要注意 DLL 是只读的和共享的。如果大量进程都使用一个给定的DLL,同一时刻只有一组实际内存页保存 DLL 数据。这些实际的页面可以映射到不同的地址,进入使用它们的那些进程。Task Manager之类的工具将工作集看作是共享和非共享页面的总和,因此很难确定使用 DLL对内存占用的影响。模块信息是一种很有用的方式,提供了“最差情况下”由于 DLL 造成的内存占用,需要的话可以使用其他工具作更详尽地分析。

         

    我们关心的是内存占用情况,请单击 Memory 选项卡,图 10 显示了 Java 程序所用内存的一小部分。

                 
    图 10. TopToBottom Memory 选项卡
    TopToBottom 内存

    显示的内容和 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
    ................................

    (为便于阅读,省略了部分行。)

         

    对于每个块,都可以看到下列信息:

         
    •           Address:十六进制格式,相对于进程虚拟地址空间起始位置的偏移量。       
    •           Size:字节数,用十六进制表示。       
    •           State:自由、保留或提交。       
    •           Protection status:只读或/读写。       
    •           Type:私有(不能被其他进程访问)、映射(直接来自文件系统)或镜像(可执行代码)。       

    然后列出进程使用的所有 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 分配的内存组成了进程堆部分,Other Data中包括 Java 堆。Grand Total Working Set 对应 Task Manager 的 Mem Usage 和 TEB字段(进程的线程环境块所需要的内存,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 Process Explorer

         

    更有用的内存分析工具来自 Sysinternals 公司(请参阅        参考资料)。其中一个工具是图形化的进程管理器,如图 11 所示,它可以作为 Task Manager 的高级代替品。     

                 
    图 11. Process Explorer 进程树
    Process Explorer

    Process Explorer 具有和 Task Manager 相同的功能。比方说,您可以得到整个系统性能的动态图形(通过         View --> System Information...),也可用类似的方式配置主进程视图中的列。在         Process --> Properties... 中,Process Explorer 提供了进程的更多信息,比如完整路径和命令行、线程、CPU 实用的动态图表和私有内存。它的用户界面非常好,如图 11 所示。它还可以观察 DLL 的信息和进程的句柄。您可以使用         Options --> Replace Task Manager 用 Process Explorer 代替默认的 Task Manager。     

         

    Sysinternals ListDLLs

         

    还可以从 Sysinternals 下载两个命令行工具:ListDLLs 和 Handle。如果希望在脚本或者程序中集成某种形式的内存监控,这两个工具非常有用。

         

    ListDLLs 用于观察 DLL,DLL 可能造成很多内存占用。使用之前请将其添加到路径中,并使用帮助选项获得用法说明。您可以用进程 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

         

    Handle 给出进程所用句柄(文件、套接字等)的列表。解压 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     /KnownDll
      7fc: KeyedEvent    /KernelObjects/CritSecOutOfMemoryEvent

    如果关心内存的使用,句柄是一个重要因素,因为每个句柄都要消耗一些空间。具体的数量取决于操作系统版本和句柄的类型。一般而言,句柄不应该对内存占用产生很大影响。只要数一数该工具输出的行数,就可以判定句柄是不是太多,或者是否还在增长。无论出现哪种情况,都值得注意,建议进行更细致的分析。

         





    技巧和窍门

         

    现在您已经操作(不是双关语,handle 还有一个含义是句柄)了我们要介绍的所有工具,下面是您单独或一起使用这些工具,改进内存监控的一些方法。

         

    寻找进程 ID

         

    为了找到应用程序的进程 ID,以便在 VADump 这样的命令行工具中使用,请在 Task Manager 中打开 Applications 选项卡右击所关心的进程。选择         Go To Process,这样就会在 Processes 选项卡中看到对应的 ID。     

         

    确定一个 Java 进程

         

    是否对那些都命名为 Java 或 javaw 的进程感到困惑,希望找出您要分析的那个进程?如果从 IDE 或脚本中启动 Java进程,要确定使用了哪一个 JVM 和发送给 Java 进程的命令行参数可能很困难。这些信息可以在 TopToBottom Startup选项卡中找到。您可以看到调用 JVM 使用的完整命令行和进程启动的时间。

         

    确定大量占用句柄的进程

         

    是否遇到过保存文件却得到提示说文件正被另一个进程使用的情况?或者尝试关闭您认为可靠的程序而得到错误消息的情况?您可以使用SysInternals Process Explorer 工具的 Handle Search 功能发现谁在捣乱。只要打开 Search对话框并输入文件名即可。ProcExp 将遍历所有打开的句柄,并确定相应的进程。最终常常会发现,关闭用户界面后,编辑器或者 Web浏览器还留下一个小的存根进程在运行。

         

    调查有多少内存被共享

         

    您可以使用 VADump 的         -o选项获得进程当前工作集的详细视图,以及有多少是共享的。获得一个 Java程序在系统上运行的内存转储,然后再启动另一个并转储。只要比较每个结果的 Code/StaticData部分,就会发现“Shareable”字节变成了“Shared”,从而稍微降低了内存占用的增加。

         

    清理驻留集

         

    Windows实现了一种“清除”进程驻留集的策略,在其看起来不再有用的时候予以清除。为了说明这一点,打开 Task Manager 的 Processes选项框,便可以看到要监控的应用程序进程,然后最小化应用程序窗口,看看 Mem Usage 字段发生了什么变化!

         

    确定应用程序需要的最少内存

         

    对于 Windows Server 2003 和 Windows NT,Microsoft 提供了一个有趣的称为 ClearMem 的工具,如果希望进一步研究 Windows 下应用程序使用内存的情况,它可能非常有用(请参阅        参考资料)。该工具确定了实际内存的大小,分配足够的内存,很快地占用分配的内存然后将其释放。这样就增加了其他应用程序的内存占用压力,反复运行 ClearMem 的结果是迫使应用程序占用的内存数量减少到最小。     

         





    结束语

         

    本文简要介绍了 Windows 如何管理内存,考察了一些最有用的免费工具,您可以用这些工具监控 Java应用程序的内存使用。无疑您还会发现和使用其他的工具,无论从 Web上免费下载产品还是购买商业产品,我们都希望澄清相互矛盾的术语会对您有所帮助。通常要确定您测量的目标的惟一方法就是做试验,比如我们用于示范Task Manager 的 VM Size(虚拟内存大小)和 Mem Usage(内存使用)含义的 C 程序。

         

    当然这些工具只能帮助确定问题的所在,如何解决还要靠您自己。多数时候您会发现 Java 堆获取了内存的一大部分,您需要深入分析代码,确定对象引用是否超出了必要的时间。这方面有更多的工具和文章可以提供帮助,        参考资料部分给出了一些有用的链接,可以为您指出正确的方向。     

       






    下载

    描述名字大小下载方法
    A C program to demonstrate how Windows uses memoryexperiment.c3KBHTTP
    关于下载方法的信息


    参考资料

         
    • 单击本文顶端或底端的代码按钮(或者参阅          下载部分),下载本文使用的测试程序。       

    • 请访问           Memory Management Reference,它提供了关于内存管理的大量术语解释、FAQ 和文章等。       

    • Microsoft 开发人员网站提供了关于 Windows 内存管理的说明,请参阅           About Memory Management。       

    • 下载           PrcView 的最新版本,进一步了解其功能。       

    • 可以从           www.smidgeonsoft.com 下载 TopToBottom 或其他 Windows 工具。       

    • 从 Microsoft 下载           VADump,看一看           VADump 用户参考。       

    • Process Explore、ListDLLs 和 Handle 可以从           SysInternals 网站免费获得。       

    • 从 Microsoft Windows Server 2003 文档站点了解           ClearMem。       

    • “          处理 Java 程序中的内存漏洞”(developerWorks,2001 年 2 月)提供了与堆有关的内存问题的详细介绍。       

    • “          Java理论与实践: 它是谁的对象?”(developerWorks,2003 年 6 月)一文讨论了跟踪对象所有者避免内存泄露的需要。       

    • “          Sensible sanitation -- Understanding the IBM Java Garbage Collector, Part 3”(developerWorks,2002 年 9 月)描述了 JVM 堆参数和           verbosegc 选项。       

    •           JInsight 是来自 IBM alphaWorks 的一种 Java 性能工具。       

    • 从           developerWorks Java 技术专区中,可以找到数百篇 Java 技术资源。


    作者简介

    Emma Shepherd 的照片

    Emma Shepherd 于 2002 年 Warwick 大学获得计算机科学学士学位,毕业后就加入了 IBM。她的上一个项目是关于 Java Web 服务的,她还参与了         IBM WebSphere SDK for Web Services 和 Eclipse 插件的开发。业余时间她喜欢弹钢琴,学说塞尔维亚语。     


    Martin Trotter 的照片

    MartinTrotter 毕业于 Southampton 大学,获得了电气方面的学位,几年前刚加入 IBM。从一开始他就参与了 Java 的研究,在JVM 和 JIT 方面拥有广泛的经验,并曾领导过垃圾收集团队。其业余爱好包括制作陶器、散步、骑自行车兜风和做木工等。


    Caroline Maynard 的照片

    CarolineMaynard 从 Sussex 大学获得了数学学位,毕业后一直在 IBM 工作。她最近领导了 IBM Java ORB 的开发,对 Java程序的内存占用很感兴趣。工作之余,她喜欢参加温彻斯特交响乐队的演出,从她的小猫爪子底下挽救小生物,陪着孩子到处旅游,有时间的时候,会列一列要做的所有工作。


    Matthew Peters 的照片

    Matthew Peters 多年前从剑桥的女王学院获得了数学学位,此后一直在 IBM 工作。最近几年从事 IBM JVM 垃圾收集程序和 JVM 性能提升的研究。业余时间喜欢下围棋,弹奏古典吉他,或者和家人骑车旅行

               

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • Java线程怎样映射到操作系统线程

    千次阅读 2019-04-06 22:07:31
    先说多线程模型,参考...中文版是《操作系统概念,第9版》 https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/4_Threads.html 一个线程是CPU利用率的基本单元,包括一个程序计数器,堆栈,一组寄存...

    先说多线程模型,参考经典教材《Operating System Concepts , Silberschatz ,9th edition》

    中文版是《操作系统概念,第9版》

    https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/4_Threads.html

    一个线程是CPU利用率的基本单元,包括一个程序计数器,堆栈,一组寄存器和线程ID。

    传统(重量级)进程具有单个控制线程 - 有一个程序计数器,以及可在任何给定时间执行的一系列指令。

    如图4.1所示,多线程应用程序在单个进程中具有多个线程,每个线程都有自己的程序计数器,堆栈和寄存器集,但共享公共代码,数据和某些结构(如打开文件)。

     图4.1 - 单线程和多线程进程

    多线程有四大类优点:

    响应性 - 一个线程可以提供快速响应,而其他线程被阻塞或减慢进行密集计算。

    资源共享 - 默认情况下,线程共享公共代码,数据和其他资源,这允许在单个地址空间中同时执行多个任务。

    经济 - 创建和管理线程(以及它们之间的上下文切换)比为进程执行相同的任务要快得多。

    可伸缩性,即多处理器体系结构的利用 - 单线程进程只能在一个CPU上运行,无论有多少可用,而多线程应用程序的执行可能在可用处理器之间分配。(请注意,当有多个进程争用CPU时,即当负载平均值高于某个特定阈值时,单线程进程仍然可以从多处理器体系结构中受益。)

     

    多核编程

    计算机体系结构的最新趋势是在单个芯片上生产具有多个核心或CPU的芯片。

    在传统的单核芯片上运行的多线程应用程序必须交错线程,如图4.3所示。

    但是,在多核芯片上,线程可以分布在可用内核上,从而实现真正的并行处理,如图4.4所示。

    对于操作系统,多核芯片需要新的调度算法以更好地利用可用的多个核。
    随着多线程变得越来越普遍和越来越重要(数千而不是数十个线程),CPU已被开发用于支持硬件中每个核心更多的同步线程。 

     

    多核芯片的挑战


    识别任务 - 检查应用程序以查找可以同时执行的活动。
    平衡 - 查找同时运行的任务,提供相同的价值。即不要浪费一些线程来完成琐碎的任务。
    数据拆分 - 防止线程相互干扰。
    数据依赖性 - 如果一个任务依赖于另一个任务的结果,则需要同步任务以确保以正确的顺序进行访问。
    测试和调试 - 在并行处理情况下本身就更加困难,因为竞争条件变得更加复杂和难以识别。

     

    并行类型

    从理论上讲,有两种不同的工作负载并行化方法:

    数据并行性

    在多个核(线程)之间划分数据,并在数据的每个子集上执行相同的任务。例如,将大图像分成多个片段并对不同核心上的每个片段执行相同的数字图像处理。


    任务并行性

    划分要在不同核心之间执行的不同任务并同时执行它们。

    在实践中,任何程序都不会仅仅由这些中的一个或另一个划分,而是通过某种混合组合

     

    多线程模型

    在现代系统中有两种类型的线程需要管理:用户线程和内核线程

    用户线程由内核支持,而不需要内核管理。这些是应用程序员将在其程序中添加的线程。

    内核线程由操作系统本身支持和管理。所有现代操作系统都支持内核级线程,允许内核同时执行多个同时任务或服务多个内核系统调用。

    在特定实现中,必须使用以下策略之一将用户线程映射到内核线程。

     

    多对一模型

    在多对一模型中,许多用户级线程都映射到单个内核线程。

    线程管理由用户空间中的线程库处理,这非常有效。

    但是,如果进行了阻塞系统调用,那么即使其他用户线程能够继续,整个进程也会阻塞。

    由于单个内核线程只能在单个CPU上运行,因此多对一模型不允许在多个CPU之间拆分单个进程。

    Solaris和GNU可移植线程的绿色线程在过去实现了多对一模型,但现在很少有系统继续这样做

    一对一模型

    一对一模型创建一个单独的内核线程来处理每个用户线程。

    一对一模型克服了上面列出的问题,涉及阻止系统调用和跨多个CPU分离进程。

    但是,管理一对一模型的开销更大,涉及更多开销和减慢系统速度。

    此模型的大多数实现都限制了可以创建的线程数。

    Windows(从Win95开始)和Linux都实现了线程的一对一模型。

     

    多对多模型

    多对多模型将任意数量的用户线程复用到相同或更少数量的内核线程上,结合了一对一和多对一模型的最佳特性。

    用户对创建的线程数没有限制。

    阻止内核系统调用不会阻止整个进程。

    进程可以分布在多个处理器上。

    可以为各个进程分配可变数量的内核线程,具体取决于存在的CPU数量和其他因素。

    多对多模型的一个流行变体是双层模型,它允许多对多或一对一操作。

    IRIX,HP-UX和Tru64 UNIX使用双层模型,Solaris 9之前的Solaris也是如此。

    线程库

    线程库为程序员提供了用于创建和管理线程的API。

    线程库可以在用户空间或内核空间中实现。前者涉及仅在用户空间内实现的API函数,没有内核支持。后者涉及系统调用,并且需要具有线程库支持的内核。

    三个主要的线程库:
    POSIX Pthreads - 可以作为用户或内核库提供,作为POSIX标准的扩展。
    Win32线程 - 在Windows系统上作为内核级库提供。
    Java线程 - 由于Java通常在Java虚拟机上运行,​​因此线程的实现基于JVM运行的任何操作系统和硬件,即Pthreads或Win32线程,具体取决于系统。

     

    Java线程

    所有Java程序都使用Threads - 甚至是“常见的”单线程程序。

    新线程的创建需要实现Runnable接口的对象,这意味着它们包含一个方法“public void run()”。Thread类的任何后代自然都会包含这样的方法。(实际上,必须重写/提供run()方法,以使线程具有任何实际功能。)

    创建线程对象不会启动线程运行 - 为此,程序必须调用Thread的“start()”方法。Start()为Thread分配并初始化内存,然后调用run()方法。(程序员不直接调用run()。)

    因为Java不支持全局变量,所以必须将Threads传递给共享Object的引用才能共享数据。

    请注意,JVM在本机操作系统之上运行,并且JVM规范未指定用于将Java线程映射到内核线程的模型。此决定取决于JVM实现,可能是一对一,多对多或多对一..(在UNIX系统上,JVM通常使用PThreads,而在Windows系统上,它通常使用Windows线程。)

    Java中的全局变量Java中没有全局变量的概念,关键字static定义的全局类公共字段。

     

    线程池

    每次需要创建新线程然后在完成时删除它可能效率低下,并且还可能导致创建非常大(无限)的线程数

    另一种解决方案是在进程首次启动时创建多个线程,并将这些线程放入线程池中。根据需要从池中分配线程,并在不再需要时返回池。如果池中没有可用的线程,则该进程可能必须等到一个可用。

    线程池中可用的(最大)线程数可以由可调参数确定,可能动态地响应于改变的系统负载。

    Win32通过“PoolFunction”函数提供线程池。Java还通过java.util.concurrent包为线程池提供支持,Apple支持Grand Central Dispatch架构下的线程池。

    信号处理
    问:当多线程进程收到信号时,该信号应传递到哪个线程?
    答:有四个主要选择:
    将信号传递给信号所适用的线程。
    将信号传递给过程中的每个线程。
    将信号传递给过程中的某些线程。
    分配特定线程以接收进程中的所有信号。
    最佳选择可能取决于涉及哪个特定信号。


    UNIX允许各个线程指示它们接受哪些信号以及它们忽略哪些信号。但是,信号只能传递给一个线程,这通常是接受该特定信号的第一个线程。
    UNIX提供了两个独立的系统调用:kill(pid,signal)和pthread_kill(tid,signal),分别用于向进程或特定线程传递信号
    Windows不支持信号,但可以使用异步过程调用(APC)模拟它们。APC被传递到特定线程,而不是进程。

    线程取消
    不再需要的线程可能会被另一个线程以两种方式之一取消:
    异步取消立即取消线程。
    延迟取消设置一个标志,指示线程在方便时应自行取消。然后由取消的线程定期检查此标志,并在看到标志设置时很好地退出。
    异步取消(共享)资源分配和线程间数据传输可能会有问题。

    线程局部存储
    大多数数据在线程之间共享,这是首先使用线程的主要好处之一。
    但是,有时线程也需要特定于线程的数据。
    大多数主要线程库(pThreads,Win32,Java)都支持特定于线程的数据,称为线程本地存储或TLS。请注意,这更像是静态数据而不是局部变量,因为它在函数结束时不会停止存在。

     

    Linux线程
    Linux不区分进程和线程 - 它使用更通用的术语“Task”。
    传统的fork()系统调用完全复制了一个进程(Task),如前所述。
    另一个系统调用clone()允许父和子任务之间的不同程度的共享,由下表中显示的Flag控制:
     

    调用没有设置Flag的clone()等同于fork()。使用CLONE_FS,CLONE_VM,CLONE_SIGHAND和CLONE_FILES调用clone()等同于创建线程,因为所有这些数据结构都将被共享。
    Linux使用结构task_struct实现这一点,该结构实质上为任务资源提供了间接级别。如果未设置标志,则复制结构指向的资源,但如果设置了标志,则仅复制指向资源的指针,因此共享资源。(想想深层复制与OO编程中的浅层复制。)

    Linux的几个发行版现在支持NPTL(Native POXIS Thread Library)
    符合POSIX标准。
    支持SMP(对称多处理),NUMA(非统一内存访问)和多核处理器。
    支持数百到数千个线程。

     

    Linux 线程模型的比较:LinuxThreads 和 NPTL

    当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone() 系统调用将进程作为可调度的实体。这个调用创建了调用进程(calling process)的一个拷贝,这个拷贝与调用进程共享相同的地址空间。LinuxThreads 项目使用这个调用来完全在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步原语方面都存在问题。另外,这个线程模型也不符合 POSIX 的要求。

    要改进 LinuxThreads,非常明显我们需要内核的支持,并且需要重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT(Next-Generation POSIX Threads)项目。同时,Red Hat 的一些开发人员开展了 NPTL 项目。NGPT 在 2003 年中期被放弃了,把这个领域完全留给了 NPTL。

    NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。与 LinuxThreads 一样,NPTL 也实现了一对一的模型。

     

    著名的c10k论文

    虽然有点老,但是还是值得一读。

    注意:1:1线程与M:N线程

    在实现线程库时有一个选择:您可以将所有线程支持放在内核中(这称为1:1线程模型),或者您可以将其中的相当一部分移动到用户空间(这称为M:N线程模型)。有一点,M:N被认为是更高的性能,但它太复杂了,很难做到正确,大多数人都在远离它。

     

    Java线程如何映射到OS线程?

    JVM线程映射到OS线程是一种常见的读取语句。但这究竟意味着什么呢?我们在java中创建Thread对象并调用其start方法来启动新线程。它是如何启动OS线程的?以及如何将Thread对象的run方法附加到执行的OS线程?

    调用start0方法,该方法被声明为本机方法。“native”标记告诉JVM这是一个特定于平台的本机方法(用C / C ++编写),需要通过java本机接口调用。JNI是Java的本机方法接口规范,它详细说明了本机代码如何与JVM集成,反之亦然。(https://docs.oracle.com/javase/9​​/docs/specs/jni/design.html#jni-interface-functions-and-pointers

    从Java到C++,以JVM的角度看Java线程的创建与运行

    参考:【JVM源码探秘】深入理解Thread.run()底层实现

    以jdk8为例:

    通过new java.lang.Thread.start()来启动一个线程,只需要将业务逻辑放在run()方法里即可,启动一个Java线程,调用start()方法:

    在\openjdk-8u40-src-b25-10_feb_2015\openjdk\jdk\src\share\classes\java\lang\Thread.java

    在启动一个线程时会调用start0()这个native方法,关于本地方法的注册请参照【JVM源码探秘】深入registerNatives()底层实现

    在Java的系统包下如:

    java.lang.System

    java.lang.Object

    java.lang.Class

    都有一个静态块用来执行一个叫做registerNatives()的native方法:

    \openjdk-8u40-src-b25-10_feb_2015\openjdk\jdk\src\share\native\java\lang\Thread.c 

    start0对应JVM_StartThread

    VM_StartThread方法位于\openjdk-8u40-src-b25-10_feb_2015\openjdk\hotspot\src\share\vm\prims\jvm.cpp 


    //分配C ++ Thread结构并创建本机线程。该
    //从java检索的堆栈大小已签名,但构造函数需要
    // size_t(无符号类型),因此请避免传递负值
    //导致非常大的堆栈。

    代码native_thread = new JavaThread(&thread_entry, sz);用于创建JavaThread实例,位于

    \openjdk-8u40-src-b25-10_feb_2015\openjdk\hotspot\src\share\vm\runtime\thread.cpp

    //这里的_osthread可能为NULL,因为我们的内存不足(活动的线程太多)。
       //我们需要抛出OutOfMemoryError  - 但是我们不能这样做,因为调用者
       //可能会持有一个锁,并且在抛出异常之前必须解锁所有锁(抛出
       //异常包括创建异常对象并初始化它,初始化
       //将通过JavaCall离开VM,然后必须解锁所有锁。
       //当我们到达这里时,线程仍然被暂停 线程必须显式启动
       //由创作者! 此外,线程还必须显式添加到“线程”列表中
       //通过调用Threads:add。 之所以没有这样做,是因为线程
       //对象必须完全初始化(看看JVM_Start) 

    通过OS创建线程,位于\openjdk-8u40-src-b25-10_feb_2015\openjdk\hotspot\src\os\linux\vm\os_linux.cpp

    
    bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
      assert(thread->osthread() == NULL, "caller responsible");
    
      // Allocate the OSThread object
      OSThread* osthread = new OSThread(NULL, NULL);
      if (osthread == NULL) {
        return false;
      }
    
      // set the correct thread state
      osthread->set_thread_type(thr_type);
    
      // Initial state is ALLOCATED but not INITIALIZED
      osthread->set_state(ALLOCATED);
    
      thread->set_osthread(osthread);
    
      // init thread attributes
      pthread_attr_t attr;
      pthread_attr_init(&attr);
      pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    
      // stack size
      if (os::Linux::supports_variable_stack_size()) {
        // calculate stack size if it's not specified by caller
        if (stack_size == 0) {
          stack_size = os::Linux::default_stack_size(thr_type);
    
          switch (thr_type) {
          case os::java_thread:
            // Java threads use ThreadStackSize which default value can be
            // changed with the flag -Xss
            assert (JavaThread::stack_size_at_create() > 0, "this should be set");
            stack_size = JavaThread::stack_size_at_create();
            break;
          case os::compiler_thread:
            if (CompilerThreadStackSize > 0) {
              stack_size = (size_t)(CompilerThreadStackSize * K);
              break;
            } // else fall through:
              // use VMThreadStackSize if CompilerThreadStackSize is not defined
          case os::vm_thread:
          case os::pgc_thread:
          case os::cgc_thread:
          case os::watcher_thread:
            if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
            break;
          }
        }
    
        stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
        pthread_attr_setstacksize(&attr, stack_size);
      } else {
        // let pthread_create() pick the default value.
      }
    
      // glibc guard page
      pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
    
      ThreadState state;
    
      {
        // Serialize thread creation if we are running with fixed stack LinuxThreads
        bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
        if (lock) {
          os::Linux::createThread_lock()->lock_without_safepoint_check();
        }
    
        pthread_t tid;
        int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
    
        pthread_attr_destroy(&attr);
    
        if (ret != 0) {
          if (PrintMiscellaneous && (Verbose || WizardMode)) {
            perror("pthread_create()");
          }
          // Need to clean up stuff we've allocated so far
          thread->set_osthread(NULL);
          delete osthread;
          if (lock) os::Linux::createThread_lock()->unlock();
          return false;
        }
    
        // Store pthread info into the OSThread
        osthread->set_pthread_id(tid);
    
        // Wait until child thread is either initialized or aborted
        {
          Monitor* sync_with_child = osthread->startThread_lock();
          MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
          while ((state = osthread->get_state()) == ALLOCATED) {
            sync_with_child->wait(Mutex::_no_safepoint_check_flag);
          }
        }
    
        if (lock) {
          os::Linux::createThread_lock()->unlock();
        }
      }
    
      // Aborted due to thread limit being reached
      if (state == ZOMBIE) {
          thread->set_osthread(NULL);
          delete osthread;
          return false;
      }
    
      // The thread is returned suspended (in state INITIALIZED),
      // and is started higher up in the call chain
      assert(state == INITIALIZED, "race condition");
      return true;
    }
    

    主要是

    // 调用系统库创建线程,thread_native_entry为本地Java线程执行入口 // 

    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

    这个方法是C++创建线程的库方法,通过调用这个方法,会创建一个C++ 线程并使线程进入就绪状态,即可以开始运行

    展开全文
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    文章目录概述运行时数据区域程序计数器(线程私有)Java虚拟机栈(线程私有)局部变量表操作数栈动态链接方法返回地址小结本地方法栈(线程私有)Java堆(全局共享)方法区(全局共享)运行时常量池直接内存HotSpot...

    本博客主要参考周志明老师的《深入理解Java虚拟机》第二版

    读书是一种跟大神的交流。阅读《深入理解Java虚拟机》受益匪浅,对Java虚拟机有初步的认识。这里写博客主要出于以下三个目的:一方面是记录,方便日后阅读;一方面是加深对内容的理解;一方面是分享给大家,希望对大家有帮助。

    《深入理解Java虚拟机》全书总结如下:

    序号内容链接地址
    1深入理解Java虚拟机-走近Javahttps://blog.csdn.net/ThinkWon/article/details/103804387
    2深入理解Java虚拟机-Java内存区域与内存溢出异常https://blog.csdn.net/ThinkWon/article/details/103827387
    3深入理解Java虚拟机-垃圾回收器与内存分配策略https://blog.csdn.net/ThinkWon/article/details/103831676
    4深入理解Java虚拟机-虚拟机执行子系统https://blog.csdn.net/ThinkWon/article/details/103835168
    5深入理解Java虚拟机-程序编译与代码优化https://blog.csdn.net/ThinkWon/article/details/103835883
    6深入理解Java虚拟机-高效并发https://blog.csdn.net/ThinkWon/article/details/103836167

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来。

    概述

    对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”又是从事最基础工作的“劳动人民”——既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任。
    对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,由虚拟机管理内存这一切看起来都很美好。不过,也正是因为Java程序员把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的工作。

    运行时数据区域

    在这里插入图片描述

    JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

    Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。

    Execution engine(执行引擎):执行classes中的指令。

    Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。

    Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

    Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:

    在这里插入图片描述

    程序计数器(线程私有)

    程序计数器是一块较小的内存区域,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。「属于线程私有的内存区域」

    附加:

    1. 当前线程所执行的字节码行号指示器
    2. 每个线程都有一个自己的PC计数器。
    3. 线程私有的,生命周期与线程相同,随JVM启动而生,JVM关闭而死。
    4. 线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址
    5. 线程执行Native方法时,计数器记录为(Undefined)。
    6. 唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域。

    Java虚拟机栈(线程私有)

    线程私有内存空间,它的生命周期和线程相同。线程执行期间,每个方法被执行时,都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法从被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。「属于线程私有的内存区域」

    注意:下面的内容为附加内容,对Java虚拟机栈进行详细说明,感兴趣的小伙伴可以有针对性的阅读

    下面依次解释栈帧里的四种组成元素的具体结构和功能:

    局部变量表

    局部变量表局部变量表是 Java 虚拟机栈的一部分,是一组变量值的存储空间,用于存储方法参数局部变量。 在 Class 文件的方法表的 Code 属性的 max_locals 指定了该方法所需局部变量表的最大容量

    局部变量表在编译期间分配内存空间,可以存放编译期的各种变量类型:

    1. 基本数据类型boolean, byte, char, short, int, float, long, double8种;
    2. 对象引用类型reference,指向对象起始地址引用指针;不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置
    3. 返回地址类型returnAddress,返回地址的类型。指向了一条字节码指令的地址

    变量槽(Variable Slot):

    变量槽局部变量表最小单位,规定大小为32位。对于64位的longdouble变量而言,虚拟机会为其分配两个连续Slot空间。

    操作数栈

    操作数栈Operand Stack)也常称为操作栈,是一个后入先出栈。在 Class 文件的 Code 属性的 max_stacks 指定了执行过程中最大的栈深度。Java虚拟机的解释执行引擎被称为基于栈的执行引擎 ,其中所指的就是指-操作数栈

    1. 局部变量表一样,操作数栈也是一个以32字长为单位的数组。
    2. 虚拟机在操作数栈中可存储的数据类型intlongfloatdoublereferencereturnType等类型 (对于byteshort以及char类型的值在压入到操作数栈之前,也会被转换为int)。
    3. 局部变量表不同的是,它不是通过索引来访问,而是通过标准的栈操作压栈出栈来访问。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。

    虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

    begin
    iload_0    // push the int in local variable 0 onto the stack
    iload_1    // push the int in local variable 1 onto the stack
    iadd       // pop two ints, add them, push result
    istore_2   // pop int, store into local variable 2
    end
    

    在这个字节码序列里,前两个指令 iload_0iload_1 将存储在局部变量表中索引为01的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量表索引为2的位置。

    下图详细表述了这个过程中局部变量表操作数栈的状态变化(图中没有使用的局部变量表操作数栈区域以空白表示)。

    在这里插入图片描述

    动态链接

    每个栈帧都包含一个指向运行时常量池中所属的方法引用,持有这个引用是为了支持方法调用过程中的动态链接

    Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用:

    1. 静态解析:一部分会在类加载阶段或第一次使用的时候转化为直接引用(如finalstatic域等),称为静态解析
    2. 动态解析:另一部分将在每一次的运行期间转化为直接引用,称为动态链接
    方法返回地址

    当一个方法开始执行以后,只有两种方法可以退出当前方法:

    1. 正常返回:当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址。
    2. 异常返回:当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定。

    当一个方法返回时,可能依次进行以下3个操作:

    1. 恢复上层方法局部变量表操作数栈
    2. 返回值压入调用者栈帧操作数栈
    3. PC计数器的值指向下一条方法指令位置。
    小结

    注意:在Java虚拟机规范中,对这个区域规定了两种异常。

    其一:如果当前线程请求的栈深度大于虚拟机栈所允许的深度,将会抛出 StackOverflowError 异常(在虚拟机栈不允许动态扩展的情况下);

    其二:如果扩展时无法申请到足够的内存空间,就会抛出 OutOfMemoryError 异常。

    本地方法栈(线程私有)

    本地方法栈Java虚拟机栈发挥的作用非常相似,主要区别是Java虚拟机栈执行的是Java方法服务,而本地方法栈执行Native方法服务(通常用C编写)。

    有些虚拟机发行版本(譬如Sun HotSpot虚拟机)直接将本地方法栈Java虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常。

    Java堆(全局共享)

    对大多数应用而言,Java 堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一作用就是存放对象实例,几乎所有的对象实例都是在这里分配的(不绝对,在虚拟机的优化策略下,也会存在栈上分配、标量替换的情况,后面的章节会详细介绍)。

    Java 堆是 GC 回收的主要区域,因此很多时候也被称为 GC 堆。

    从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以在Java堆被划分成两个不同的区域:新生代 (Young Generation) 、老年代 (Old Generation) 。新生代 (Young) 又被划分为三个区域:一个Eden区和两个Survivor区 - From Survivor区和To Survivor区。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然时对象实例,记你一步划分的目的是为了使JVM能够更好的管理堆内存中的对象,包括内存的分配以及回收。

    简要归纳:新的对象分配是首先放在年轻代 (Young Generation) 的Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到老年代Old中。

    从内存回收的角度看,线程共享的 Java 堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。「属于线程共享的内存区域」

    方法区(全局共享)

    方法区和Java堆一样,为多个线程共享,它用于存储类信息常量静态常量即时编译后的代码等数据。Non-Heap(非堆)「属于线程共享的内存区域」

    运行时常量池

    运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生成的各种字面常量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池。

    下面信息为附加信息

    • HotSpot虚拟机中,将方法区称为“永久代”,本质上两者并不等价,仅仅是因为HotSpot虚拟机把GC分代收集扩展至方法区。
    • JDK 7的HotSpot中,已经将原本存放于永久代中的字符串常量池移出。
    • 根据虚拟机规范的规定,当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。当常量池无法再申请到内存时也会抛出OutOfMemoryError异常。
    • JDK 8的HotSpot中,已经将永久代废除,用元数据实现了方法区。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。

    在这里插入图片描述

    直接内存

    直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。Java 中的 NIO 可以使用 Native 函数直接分配堆外内存,通常直接内存的速度会优于Java堆内存,然后通过一个存储在 Java 堆中的 DiectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景显著提高性能,对于读写频繁、性能要求高的场景,可以考虑使用直接内存,因为避免了在 Java 堆和 Native 堆中来回复制数据。直接内存不受 Java 堆大小的限制。

    HotSpot虚拟机对象探秘

    对象的创建

    说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

    Header解释
    使用new关键字调用了构造函数
    使用Class的newInstance方法调用了构造函数
    使用Constructor类的newInstance方法调用了构造函数
    使用clone方法没有调用构造函数
    使用反序列化没有调用构造函数

    下面是对象创建的主要流程:

    在这里插入图片描述

    虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行<init>方法。

    下面内容是对象创建的详细过程

    对象的创建通常是通过new关键字创建一个对象的,当虚拟机接收到一个new指令时,它会做如下的操作。

    1.判断对象对应的类是否加载、链接、初始化

    虚拟机接收到一条new指令时,首先会去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被类加载器加载、链接和初始化过。如果没有则先执行相应的类加载过程。

    在这里插入图片描述

    2.为对象分配内存

    类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:

    • 指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
    • 空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。

    选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

    在这里插入图片描述

    3.处理并发安全问题

    对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

    • 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
    • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。

    在这里插入图片描述

    4.初始化分配到的内存空间

    内存分配完后,虚拟机要将分配到的内存空间初始化为零值(不包括对象头)。如果使用了 TLAB,这一步会提前到 TLAB 分配时进行。这一步保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用。

    5.设置对象的对象头

    接下来设置对象头(Object Header)信息,包括对象的所属类、对象的HashCode和对象的GC分代年龄等数据存储在对象的对象头中。

    6.执行init方法进行初始化

    执行init方法,初始化对象的成员变量、调用类的构造方法,这样一个对象就被创建了出来。

    对象的内存布局

    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头Header)、实例数据Instance Data)和对齐填充Padding)。

    在这里插入图片描述

    对象头

    HotSpot虚拟机中,对象头有两部分信息组成:运行时数据类型指针,如果是数组对象,还有一个保存数组长度的空间。

    • Mark Word(运行时数据):用于存储对象自身运行时的数据,如哈希码(hashCode)、GC分带年龄线程持有的锁偏向线程ID 等信息。在32位系统占4字节,在64位系统中占8字节;

      HotSpot虚拟机对象头Mark Word在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示:

    存储内容标志位状态
    对象哈希码、对象分代年龄01未锁定
    指向锁记录的指针00轻量级锁定
    指向重量级锁的指针10膨胀(重量级锁定)
    空,不需要记录信息11GC标记
    偏向线程ID、偏向时间戳、对象分代年龄01可偏向
    • Class Pointer(类型指针):用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节;
    • Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节;
    实例数据

    实例数据 是对象真正存储的有效信息,无论是从父类继承下来的还是该类自身的,都需要记录下来,而这部分的存储顺序受虚拟机的分配策略定义的顺序的影响。

    默认分配策略:

    long/double -> int/float -> short/char -> byte/boolean -> reference

    如果设置了-XX:FieldsAllocationStyle=0(默认是1),那么引用类型数据就会优先分配存储空间:

    reference -> long/double -> int/float -> short/char -> byte/boolean

    结论:

    分配策略总是按照字节大小由大到小的顺序排列,相同字节大小的放在一起。

    对齐填充

    无特殊含义,不是必须存在的,仅作为占位符。

    HotSpot虚拟机要求每个对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(32位为1倍,64位为2倍),因此,当对象实例数据部分没有对齐的时候,就需要通过对齐填充来补全。

    对象的访问定位

    Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄直接指针 两种方式。

    指针: 指向对象,代表一个对象在内存中的起始地址。

    句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。

    句柄访问

    Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据对象类型数据各自的具体地址信息,具体构造如下图所示:
    在这里插入图片描述
    优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而引用本身不需要修改。

    直接指针

    如果使用直接指针访问,引用 中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
    在这里插入图片描述
    优势:速度更,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

    实战:OutOfMemoryError异常

    内存异常是我们工作当中经常会遇到问题,但如果仅仅会通过加大内存参数来解决问题显然是不够的,应该通过一定的手段定位问题,到底是因为参数问题,还是程序问题(无限创建,内存泄露)。定位问题后才能采取合适的解决方案,而不是一内存溢出就查找相关参数加大。

    概念

    内存泄露:代码中的某个对象本应该被虚拟机回收,但因为拥有GCRoot引用而没有被回收。

    内存溢出:虚拟机由于堆中拥有太多不可回收对象没有回收,导致无法继续创建新对象。

    在分析问题之前先给大家讲一讲排查内存溢出问题的方法,内存溢出时JVM虚拟机会退出,那么我们怎么知道JVM运行时的各种信息呢,Dump机制会帮助我们,可以通过加上VM参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现内存溢出异常时生成dump文件,然后通过外部工具(VisualVM)来具体分析异常的原因。

    除了程序计数器外,Java虚拟机的其他运行时区域都有可能发生OutOfMemoryError的异常,下面分别给出验证:

    Java堆溢出

    Java堆用来存储对象,因此只要不断创建对象,并保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清楚这些对象,那么当对象数量达到最大堆容量时就会产生 OOM。

    /**
     * java堆内存溢出测试
     * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
     */
    public class HeapOOM {
    
        static class OOMObject{}
    
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<OOMObject>();
            while (true) {
                list.add(new OOMObject());
            }
        }
    }
    

    运行结果:

    java.lang.OutOfMemoryError: Java heap space 
    Dumping heap to java_pid7164.hprof … 
    Heap dump file created [27880921 bytes in 0.193 secs] 
    Exception in thread “main” java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2245) 
    at java.util.Arrays.copyOf(Arrays.java:2219) 
    at java.util.ArrayList.grow(ArrayList.java:242) 
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216) 
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208) 
    at java.util.ArrayList.add(ArrayList.java:440) 
    at com.jvm.oom.HeapOOM.main(HeapOOM.java:17)
    

    堆内存 OOM 是经常会出现的问题,异常信息会进一步提示 Java heap space

    虚拟机栈和本地方法栈溢出

    在 HotSpot 虚拟机中不区分虚拟机栈和本地方法栈,栈容量只由 -Xss 参数设定。关于虚拟机栈和本地方法栈,在 Java 虚拟机规范中描述了两种异常:

    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。
    /**
     * 虚拟机栈和本地方法栈内存溢出测试,抛出stackoverflow exception
     * VM ARGS: -Xss128k 减少栈内存容量
     */
    public class JavaVMStackSOF {
    
        private int stackLength = 1;
    
        public void stackLeak () {
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) throws Throwable {
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println("stack length = " + oom.stackLength);
                throw e;
            }
    
        }
    
    }
    

    运行结果:

    stack length = 11420 
    Exception in thread “main” java.lang.StackOverflowError 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12) 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) 
    

    以上代码在单线程环境下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,抛出的都是 StackOverflowError 异常。

    如果测试环境是多线程环境,通过不断建立线程的方式可以产生内存溢出异常,代码如下所示。但是这样产生的 OOM 与栈空间是否足够大不存在任何联系,在这种情况下,为每个线程的栈分配的内存足够大,反而越容易产生OOM 异常。这点不难理解,每个线程分配到的栈容量越大,可以建立的线程数就变少,建立多线程时就越容易把剩下的内存耗尽。这点在开发多线程的应用时要特别注意。如果建立过多线程导致内存溢出,在不能减少线程数或更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。

    /**
     * JVM 虚拟机栈内存溢出测试, 注意在windows平台运行时可能会导致操作系统假死
     * VM Args: -Xss2M -XX:+HeapDumpOnOutOfMemoryError
     */
    
    public class JVMStackOOM {
    
        private void dontStop() {
            while (true) {}
        }
    
        public void stackLeakByThread() {
            while (true) {
                Thread thread = new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
    
        public static void main(String[] args) {
            JVMStackOOM oom = new JVMStackOOM();
            oom.stackLeakByThread();
        }
    }
    

    方法区和运行时常量池溢出

    方法区用于存放Class的相关信息,对这个区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。使用CGLib实现。

    方法区溢出也是一种常见的内存溢出异常,在经常生成大量Class的应用中,需要特别注意类的回收情况,这类场景除了使用了CGLib字节码增强和动态语言外,常见的还有JSP文件的应用(JSP第一次运行时要编译为Java类)、基于OSGI的应用等。

    /**
     * 测试JVM方法区内存溢出
     * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
     */
    public class MethodAreaOOM {
    
        public static void main(String[] args) {
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                enhancer.create();
            }
        }
    
        static class OOMObject{}
    }
    

    本机直接内存溢出

    DirectMemory 容量可通过 -XX:MaxDirectMemorySize 指定,如不指定,则默认与Java堆最大值一样。测试代码使用了 Unsafe 实例进行内存分配。

    由 DirectMemory 导致的内存溢出,一个明显的特征是在Heap Dump 文件中不会看见明显的异常,如果发现 OOM 之后 Dump 文件很小,而程序直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。

    /**
     * 测试本地直接内存溢出
     * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
     */
    public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
            while (true) {
                unsafe.allocateMemory(_1MB);
            }
        }
    }
    

    本章小结

    通过本章的学习,我们明白了虚拟机中的内存是如何划分的,哪部分区域、什么样的代码和操作可能导致内存溢出异常。虽然Java有垃圾收集机制,但内存溢出异常离我们仍然并不遥远,本章只是讲解了各个区域出现内存溢出异常的原因。

    展开全文
  • Windows操作系统下Sigar.jar 依赖sigar-amd64-winnt.dll或sigar-x86-winnt.dll linux 操作系统则依赖libsigar-amd64-linux.so或libsigar-x86-linux.so。【好好阅读这句话,不然很容易出错】 具体的对应关系...

    资源下载,有需要的可以下载
    https://download.csdn.net/download/xiaozhegaa/10345098
    一、Sigar【了解一下,主要是how】【what +why + how】
         【what】Sigar全名是System Information Gatherer And Reporter,中文名是系统信息收集和报表工具。
           我是一个开源的工具,提供了跨平台的系统信息收集的API ,是Hyperic-hq产品的基础包,是Hyperic HQ主要的数据收集组件。它用来从许多平台收集系统和处理信息。
           这些平台包括:Linux,Windows,Solaris,AIX,HP-UX,FreeBSD和Mac OSX。
          【why】使用Sigar可以方便收集以下的系统信息。【how】放在后面讲

    二、使用Sigar能够收集的信息
    1. CPU信息:包括基本信息(vendor、model、mhz、cacheSize)和统计信息(user、sys、idle、nice、wait)
    2. 文件系统信息:包括Filesystem、Size、Used、Avail、Use%、Type
    3. 事件信息:类似Service Control Manager
    4. 内存信息:物理内存和交换内存的总数、使用数、剩余数;RAM的大小
    5. 网络信息:包括网络接口信息和网络路由信息
    6. 进程信息:包括每个进程的内存、CPU占用数、状态、参数、句柄
    7. IO信息:包括IO的状态,读写大小等
    8. 服务状态信息
    9. 系统信息:包括操作系统版本,系统资源限制情况,系统运行时间以及负载,JAVA的版本信息等

    三、简单看一下文档说明
    Sigar有C,C#,Java和Perl API,java版的API为sigar.jar。sigar.jar的底层是用C语言编写的,它通过本地方法来调用操作系统API来获取系统相关数据。
    Windows操作系统下Sigar.jar 依赖sigar-amd64-winnt.dll或sigar-x86-winnt.dll
    linux 操作系统下则依赖libsigar-amd64-linux.so或libsigar-x86-linux.so。【好好阅读这句话,不然很容易出错】
    具体的对应关系如下:

    这里写图片描述
    这里写图片描述
    【注意】上述可知,window开发,需要导入sigar-amd64-winnt.dll,如果没有导入的话,会报“no sigar-amd64-winnt.dll in java.library.path”错误,具体错误解决方法,下一篇分享给大家
    https://blog.csdn.net/xiaozhegaa/article/details/79920792

    四、开发步骤
    4.0 导入 Sigar的jar包

       <!-- https://mvnrepository.com/artifact/org.fusesource/sigar 系统信息-->
        <dependency>
            <groupId>org.fusesource</groupId>
            <artifactId>sigar</artifactId>
            <version>1.6.4</version>
        </dependency>
    

    4.1 事先导入好 sigar-amd64-winnt.dll到 “C:\Windows\System32”。
    【值得提醒的是】很多人缺少了这一步,然后报以下错误。

    这里写图片描述
    解决方法:更详细的操作可以看这篇文章
    https://blog.csdn.net/xiaozhegaa/article/details/79920792

    4.2 写测试代码如下:

       package recruitmentWebsite.util.test.jiankong;
    
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    import java.util.Map;
    import java.util.Properties;
    import org.hyperic.sigar.CpuInfo;
    import org.hyperic.sigar.CpuPerc;
    import org.hyperic.sigar.FileSystem;
    import org.hyperic.sigar.FileSystemUsage;
    import org.hyperic.sigar.Mem;
    import org.hyperic.sigar.NetFlags;
    import org.hyperic.sigar.NetInterfaceConfig;
    import org.hyperic.sigar.NetInterfaceStat;
    import org.hyperic.sigar.OperatingSystem;
    import org.hyperic.sigar.Sigar;
    import org.hyperic.sigar.SigarException;
    import org.hyperic.sigar.Swap;
    import org.hyperic.sigar.Who;
    
    public class AA {
         public static void main(String[] args) {
    
            // System信息,从jvm获取
            try {
                 // System信息,从jvm获取
                property();
                System.out.println("----------------------------------");
                // cpu信息
                cpu();
                System.out.println("----------------------------------");
                // 内存信息
                memory();
                System.out.println("----------------------------------");
                // 操作系统信息
                os();
                System.out.println("----------------------------------");
                // 用户信息
                who();
                System.out.println("----------------------------------");
                // 文件系统信息
                file();
                System.out.println("----------------------------------");
                // 网络信息
                net();
                System.out.println("----------------------------------");
                // 文件系统信息
                file();
                System.out.println("----------------------------------");
                // 网络信息
                net();
                System.out.println("----------------------------------");
                // 以太网信息
                ethernet();
                System.out.println("----------------------------------");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
    
    
    
    
         }
         private static void property() throws UnknownHostException {
            Runtime r = Runtime.getRuntime();
            Properties props = System.getProperties();
            InetAddress addr;
            addr = InetAddress.getLocalHost();
            String ip = addr.getHostAddress();
            Map<String, String> map = System.getenv();
            String userName = map.get("USERNAME");// 获取用户名
            String computerName = map.get("COMPUTERNAME");// 获取计算机名
            String userDomain = map.get("USERDOMAIN");// 获取计算机域名
            System.out.println("用户名:    " + userName);
            System.out.println("计算机名:    " + computerName);
            System.out.println("计算机域名:    " + userDomain);
            System.out.println("本地ip地址:    " + ip);
            System.out.println("本地主机名:    " + addr.getHostName());
            System.out.println("JVM可以使用的总内存:    " + r.totalMemory());
            System.out.println("JVM可以使用的剩余内存:    " + r.freeMemory());
            System.out.println("JVM可以使用的处理器个数:    " + r.availableProcessors());
            System.out.println("Java的运行环境版本:    " + props.getProperty("java.version"));
            System.out.println("Java的运行环境供应商:    " + props.getProperty("java.vendor"));
            System.out.println("Java供应商的URL:    " + props.getProperty("java.vendor.url"));
            System.out.println("Java的安装路径:    " + props.getProperty("java.home"));
            System.out.println("Java的虚拟机规范版本:    " + props.getProperty("java.vm.specification.version"));
            System.out.println("Java的虚拟机规范供应商:    " + props.getProperty("java.vm.specification.vendor"));
            System.out.println("Java的虚拟机规范名称:    " + props.getProperty("java.vm.specification.name"));
            System.out.println("Java的虚拟机实现版本:    " + props.getProperty("java.vm.version"));
            System.out.println("Java的虚拟机实现供应商:    " + props.getProperty("java.vm.vendor"));
            System.out.println("Java的虚拟机实现名称:    " + props.getProperty("java.vm.name"));
            System.out.println("Java运行时环境规范版本:    " + props.getProperty("java.specification.version"));
            System.out.println("Java运行时环境规范供应商:    " + props.getProperty("java.specification.vender"));
            System.out.println("Java运行时环境规范名称:    " + props.getProperty("java.specification.name"));
            System.out.println("Java的类格式版本号:    " + props.getProperty("java.class.version"));
            System.out.println("Java的类路径:    " + props.getProperty("java.class.path"));
            System.out.println("加载库时搜索的路径列表:    " + props.getProperty("java.library.path"));
            System.out.println("默认的临时文件路径:    " + props.getProperty("java.io.tmpdir"));
            System.out.println("一个或多个扩展目录的路径:    " + props.getProperty("java.ext.dirs"));
            System.out.println("操作系统的名称:    " + props.getProperty("os.name"));
            System.out.println("操作系统的构架:    " + props.getProperty("os.arch"));
            System.out.println("操作系统的版本:    " + props.getProperty("os.version"));
            System.out.println("文件分隔符:    " + props.getProperty("file.separator"));
            System.out.println("路径分隔符:    " + props.getProperty("path.separator"));
            System.out.println("行分隔符:    " + props.getProperty("line.separator"));
            System.out.println("用户的账户名称:    " + props.getProperty("user.name"));
            System.out.println("用户的主目录:    " + props.getProperty("user.home"));
            System.out.println("用户的当前工作目录:    " + props.getProperty("user.dir"));
          }
    
    
         private static void memory() throws SigarException {
    
                Sigar sigar = new Sigar();
                Mem mem = sigar.getMem();
                // 内存总量
                System.out.println("内存总量:    " + mem.getTotal() / 1024L + "K av");
                // 当前内存使用量
                System.out.println("当前内存使用量:    " + mem.getUsed() / 1024L + "K used");
                // 当前内存剩余量
                System.out.println("当前内存剩余量:    " + mem.getFree() / 1024L + "K free");
                Swap swap = sigar.getSwap();
                // 交换区总量
                System.out.println("交换区总量:    " + swap.getTotal() / 1024L + "K av");
                // 当前交换区使用量
                System.out.println("当前交换区使用量:    " + swap.getUsed() / 1024L + "K used");
                // 当前交换区剩余量
                System.out.println("当前交换区剩余量:    " + swap.getFree() / 1024L + "K free");
            }
    
         private static void cpu() throws SigarException {
                Sigar sigar = new Sigar();
                CpuInfo infos[] = sigar.getCpuInfoList();
                CpuPerc cpuList[] = null;
                cpuList = sigar.getCpuPercList();
                for (int i = 0; i < infos.length; i++) {// 不管是单块CPU还是多CPU都适用
                    CpuInfo info = infos[i];
                    System.out.println("第" + (i + 1) + "块CPU信息");
                    System.out.println("CPU的总量MHz:    " + info.getMhz());// CPU的总量MHz
                    System.out.println("CPU生产商:    " + info.getVendor());// 获得CPU的卖主,如:Intel
                    System.out.println("CPU类别:    " + info.getModel());// 获得CPU的类别,如:Celeron
                    System.out.println("CPU缓存数量:    " + info.getCacheSize());// 缓冲存储器数量
                    printCpuPerc(cpuList[i]);
                }
            }
    
         private static void printCpuPerc(CpuPerc cpu) {
                System.out.println("CPU用户使用率:    " + CpuPerc.format(cpu.getUser()));// 用户使用率
                System.out.println("CPU系统使用率:    " + CpuPerc.format(cpu.getSys()));// 系统使用率
                System.out.println("CPU当前等待率:    " + CpuPerc.format(cpu.getWait()));// 当前等待率
                System.out.println("CPU当前错误率:    " + CpuPerc.format(cpu.getNice()));//
                System.out.println("CPU当前空闲率:    " + CpuPerc.format(cpu.getIdle()));// 当前空闲率
                System.out.println("CPU总的使用率:    " + CpuPerc.format(cpu.getCombined()));// 总的使用率
            }
    
         private static void os() {
                OperatingSystem OS = OperatingSystem.getInstance();
                // 操作系统内核类型如: 386486586等x86
                System.out.println("操作系统:    " + OS.getArch());
                System.out.println("操作系统CpuEndian():    " + OS.getCpuEndian());//
                System.out.println("操作系统DataModel():    " + OS.getDataModel());//
                // 系统描述
                System.out.println("操作系统的描述:    " + OS.getDescription());
                // 操作系统类型
                // System.out.println("OS.getName():    " + OS.getName());
                // System.out.println("OS.getPatchLevel():    " + OS.getPatchLevel());//
                // 操作系统的卖主
                System.out.println("操作系统的卖主:    " + OS.getVendor());
                // 卖主名称
                System.out.println("操作系统的卖主名:    " + OS.getVendorCodeName());
                // 操作系统名称
                System.out.println("操作系统名称:    " + OS.getVendorName());
                // 操作系统卖主类型
                System.out.println("操作系统卖主类型:    " + OS.getVendorVersion());
                // 操作系统的版本号
                System.out.println("操作系统的版本号:    " + OS.getVersion());
            }
    
            private static void who() throws SigarException {
                Sigar sigar = new Sigar();
                Who who[] = sigar.getWhoList();
                if (who != null && who.length > 0) {
                    for (int i = 0; i < who.length; i++) {
                        // System.out.println("当前系统进程表中的用户名" + String.valueOf(i));
                        Who _who = who[i];
                        System.out.println("用户控制台:    " + _who.getDevice());
                        System.out.println("用户host:    " + _who.getHost());
                        // System.out.println("getTime():    " + _who.getTime());
                        // 当前系统进程表中的用户名
                        System.out.println("当前系统进程表中的用户名:    " + _who.getUser());
                    }
                }
            }
    
            private static void file() throws Exception {
                Sigar sigar = new Sigar();
                FileSystem fslist[] = sigar.getFileSystemList();
                for (int i = 0; i < fslist.length; i++) {
                    System.out.println("分区的盘符名称" + i);
                    FileSystem fs = fslist[i];
                    // 分区的盘符名称
                    System.out.println("盘符名称:    " + fs.getDevName());
                    // 分区的盘符名称
                    System.out.println("盘符路径:    " + fs.getDirName());
                    System.out.println("盘符标志:    " + fs.getFlags());//
                    // 文件系统类型,比如 FAT32、NTFS
                    System.out.println("盘符类型:    " + fs.getSysTypeName());
                    // 文件系统类型名,比如本地硬盘、光驱、网络文件系统等
                    System.out.println("盘符类型名:    " + fs.getTypeName());
                    // 文件系统类型
                    System.out.println("盘符文件系统类型:    " + fs.getType());
                    FileSystemUsage usage = null;
                    usage = sigar.getFileSystemUsage(fs.getDirName());
                    switch (fs.getType()) {
                    case 0: // TYPE_UNKNOWN :未知
                        break;
                    case 1: // TYPE_NONE
                        break;
                    case 2: // TYPE_LOCAL_DISK : 本地硬盘
                        // 文件系统总大小
                        System.out.println(fs.getDevName() + "总大小:    " + usage.getTotal() + "KB");
                        // 文件系统剩余大小
                        System.out.println(fs.getDevName() + "剩余大小:    " + usage.getFree() + "KB");
                        // 文件系统可用大小
                        System.out.println(fs.getDevName() + "可用大小:    " + usage.getAvail() + "KB");
                        // 文件系统已经使用量
                        System.out.println(fs.getDevName() + "已经使用量:    " + usage.getUsed() + "KB");
                        double usePercent = usage.getUsePercent() * 100D;
                        // 文件系统资源的利用率
                        System.out.println(fs.getDevName() + "资源的利用率:    " + usePercent + "%");
                        break;
                    case 3:// TYPE_NETWORK :网络
                        break;
                    case 4:// TYPE_RAM_DISK :闪存
                        break;
                    case 5:// TYPE_CDROM :光驱
                        break;
                    case 6:// TYPE_SWAP :页面交换
                        break;
                    }
                    System.out.println(fs.getDevName() + "读出:    " + usage.getDiskReads());
                    System.out.println(fs.getDevName() + "写入:    " + usage.getDiskWrites());
                }
                return;
            }
    
            private static void net() throws Exception {
                Sigar sigar = new Sigar();
                String ifNames[] = sigar.getNetInterfaceList();
                for (int i = 0; i < ifNames.length; i++) {
                    String name = ifNames[i];
                    NetInterfaceConfig ifconfig = sigar.getNetInterfaceConfig(name);
                    System.out.println("网络设备名:    " + name);// 网络设备名
                    System.out.println("IP地址:    " + ifconfig.getAddress());// IP地址
                    System.out.println("子网掩码:    " + ifconfig.getNetmask());// 子网掩码
                    if ((ifconfig.getFlags() & 1L) <= 0L) {
                        System.out.println("!IFF_UP...skipping getNetInterfaceStat");
                        continue;
                    }
                    NetInterfaceStat ifstat = sigar.getNetInterfaceStat(name);
                    System.out.println(name + "接收的总包裹数:" + ifstat.getRxPackets());// 接收的总包裹数
                    System.out.println(name + "发送的总包裹数:" + ifstat.getTxPackets());// 发送的总包裹数
                    System.out.println(name + "接收到的总字节数:" + ifstat.getRxBytes());// 接收到的总字节数
                    System.out.println(name + "发送的总字节数:" + ifstat.getTxBytes());// 发送的总字节数
                    System.out.println(name + "接收到的错误包数:" + ifstat.getRxErrors());// 接收到的错误包数
                    System.out.println(name + "发送数据包时的错误数:" + ifstat.getTxErrors());// 发送数据包时的错误数
                    System.out.println(name + "接收时丢弃的包数:" + ifstat.getRxDropped());// 接收时丢弃的包数
                    System.out.println(name + "发送时丢弃的包数:" + ifstat.getTxDropped());// 发送时丢弃的包数
                }
            }
            private static void ethernet() throws SigarException {
                Sigar sigar = null;
                sigar = new Sigar();
                String[] ifaces = sigar.getNetInterfaceList();
                for (int i = 0; i < ifaces.length; i++) {
                    NetInterfaceConfig cfg = sigar.getNetInterfaceConfig(ifaces[i]);
                    if (NetFlags.LOOPBACK_ADDRESS.equals(cfg.getAddress()) || (cfg.getFlags() & NetFlags.IFF_LOOPBACK) != 0
                            || NetFlags.NULL_HWADDR.equals(cfg.getHwaddr())) {
                        continue;
                    }
                    System.out.println(cfg.getName() + "IP地址:" + cfg.getAddress());// IP地址
                    System.out.println(cfg.getName() + "网关广播地址:" + cfg.getBroadcast());// 网关广播地址
                    System.out.println(cfg.getName() + "网卡MAC地址:" + cfg.getHwaddr());// 网卡MAC地址
                    System.out.println(cfg.getName() + "子网掩码:" + cfg.getNetmask());// 子网掩码
                    System.out.println(cfg.getName() + "网卡描述信息:" + cfg.getDescription());// 网卡描述信息
                    System.out.println(cfg.getName() + "网卡类型" + cfg.getType());//
                }
            }
    }
    

    这里写图片描述

    至此,就学会了Sigar简单的使用了。

    展开全文
  • 操作系统】堆与内存管理概述

    千次阅读 2017-04-13 21:00:14
    —–要说到操作系统的堆与内存的管理的话,那内容真的是海了去了,从开始的地方就能不停的扩展,但内容的重要性也是不可言喻的,本片博客着重于总结以下三点: Linux的虚拟地址空间布局 堆和栈的管理,堆和栈的区别 ...
  • Java内存溢出异常及其处理

    千次阅读 2019-02-04 12:19:30
    熟悉Java内存划分及运行的首要目的就是预防JVM抛出内存溢出相关的异常,或者说当发生这样异常是该如何排查问题,定位问题并且给出合理的解决方案,这对于开发工作以及后期维护工作的顺利进行尤为重要。 一、在Java...
  • java 运行时指定内存大小

    千次阅读 2021-03-06 17:46:23
    java-jar-Xms1024m-Xmx1536m-XX:PermSize=128M-XX:MaxPermSize=256MXXX.jarjava-Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M MyClass说明:后面是JVM的参数-Xms128m JVM初始分配的堆内存-Xmx512m JVM...
  • 操作系统系统笔记整理

    千次阅读 多人点赞 2020-09-26 13:37:27
    总之,对于开发人员来说,操作系统需要四个方面进行学习:进程/线程;并发/锁;内存管理与调度;I/O原理,本文也将围绕这几点逐渐深入。 一、常用术语总结 名词 概念 PCB 进程控制块(PCB Process ...
  • Java堆外内存的使用

    千次阅读 2014-12-31 15:20:35
    最近经常有人问我在Java中使用堆外(off heap)内存的好处与用途何在。我想其他面临几样选择的人应该也会这个答案感兴趣吧。 堆外内存其实并无特别之处。线程栈,应用程序代码,NIO缓存用的都是堆外内存。事实上...
  • java内存配置详解

    千次阅读 2017-03-19 23:11:31
    前段时间在一个项目的性能测试中又发生了一次OOM(Out of swap sapce),情形和以前网店版的那次差不多,比上次更奇怪的是,此次搞了几天之后啥都没调整系统就自动好了,...在32Bit操作系统上有4G的限制,一般来说Wi
  • Java内存模型 详解

    千次阅读 2016-03-20 10:02:39
    Java内存模型概念 Java平台自动集成线程以及多处理技术。内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象...
  • Java内存溢出详解及解决方案

    万次阅读 2018-03-12 10:27:18
    内存溢出与锁表则不然,一般现象是操作一般时间后系统越来越慢,直到死机,但并不能明确是在什么操作上出现的,发生的时间点也没有规律,查看日志或查看数据库也不能定位出问题的代码。更严重的是内存溢出与数据库锁...
  • JVM是怎么和操作系统交互的?

    千次阅读 多人点赞 2019-11-29 08:51:00
    点击上方“朱小厮的博客”,选择“设为星标”后台回复”加群“加入公众号专属技术群来源:阿里巴巴中间件肉眼看计算机是由 CPU 、内存、显示器这些硬件设备组成,但大部分人从事的是软件开发工作...
  • 获取系统的信息使用情况
  • 手把手教你用Java设计并实现一个城市公交查询系统

    千次阅读 多人点赞 2020-12-19 10:11:33
    为了使得我国公交乘客出行及查询有关信息更方便,本文运用JAVA语言技术,Jsp技术,Mysql数据库开发了B/S结构的城市公交查询系统。 该系统顺应了时代发展且具有以下优点:首先,方便乘客的出行,乘客不用询问站牌工作...
  • JVM默认内存大小

    千次阅读 2021-02-26 18:05:19
    堆(Heap)和非堆(Non-heap)内存按照官方的说法:“Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap...
  • 新产品发布,拿来试用。由于本机是win7_x64,但是为方便工作,机器安装了从32位的JDK1.5一直到64位的JDK1.6的4个JDK。为保证运行时能与大多数人的运行状况相同,...# There is insufficient memory for the Java Run
  • java堆外内存泄漏

    万次阅读 2016-07-14 13:53:01
    java堆外内存泄漏 最近有个系统在做压力测试, 环境配置: 4核CPU 8g内存 jdk1.6.0_25,jvm配置-server -Xms2048m -Xmx2048m 出现问题如下 执行并发300人,压测持续1个小时内存使用率从20%上升到100%,tps从1100多...
  • 基于Java内存溢出的解决方法详解

    千次阅读 2016-04-22 14:12:50
    1、java.lang.OutOfMemoryError: PermGen space JVM管理两种类型的内存,堆和非堆。堆是给开发人员用的上面说的就是,是在JVM启动时创建;非堆是留给JVM自己用的,用来存放类的信息的。它和堆不同,运行期内GC不会...
  • 本机内存简介:操作系统,硬件限制及虚拟内存 本文介绍操作系统和底层硬件给本机内存带来的限制。硬件限制本机进程遇到的许多限制都是由硬件造成的,而与操作系统没有关系。每台计算机都有一个处理器和一些随机存取...
  • JAVA程序启动时JVM都会分配一个初始内存和最大内存给这个应用程序。这个初始内存和最大内存在一定程度都会影响程序的性能;Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,需要调大。有...
  • java nio及操作系统底层原理

    万次阅读 多人点赞 2017-06-27 12:53:20
    版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] ...相关资料IO基本概念 ...阻塞IO模型非阻塞IO模型IO复用模型信号驱动异步IO模型异步IO模型总结 ...Java对BIONIOAIO的支持AIORefer
  • 其实博主最近想更的博文不是这个,原是想分享一个自己写的油猴...此前想要通过虚拟机玩游戏的想法始终难以得偿,毕竟VMWare Workstation对Windows 98的支持简直差到令人发指,NVIDIA GRID又不是个人能玩得起的东西...
  • Java内存模型与Java线程的实现原理

    万次阅读 2016-07-15 23:52:18
    Java内存模型与Java线程的实现原理
  • 尽管 Java™ 运行时能够解决大量的内存管理问题,但程序的内存占用情况保持警惕仍然是优化机器性能、测定内存泄露的关键。Windows 上有很多工具可以监控内存的使用。但每种工具各有长短,都有特定的倾向性,常常...
  •  ◆JVM最大内存: 首先JVM内存限制于实际的最大物理内存,假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给...
  • 操作系统(一)—— 操作系统概论

    千次阅读 2019-02-23 19:34:31
    一、操作系统的概念 1、计算机系统  计算机系统是一种可以按照用户的要求接收和存储信息、自动进行数据处理并输出结果信息的系统。广义的计算机系统包含机械式系统和电子式系统两类。  计算机系统包括硬件系统...
  • Java内存大小限制

    千次阅读 2010-07-03 21:33:00
    但是从操作系统的角度去考虑,一个运行的进程,其可以申请的进程内存空间大小是受限。而不是可以随意的申请。 在Linux系统中可以去手动设置进程可以申请的进程内存空间大小,但是在windows系统上,我目前还不知道...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 73,382
精华内容 29,352
关键字:

windows操作系统下对java内存的限制

java 订阅