jvm内存详解

所需积分/C币:1 2018-10-21 19:30:30 656KB PDF
收藏 收藏
举报

jvm内存详解,理解 JVM 如何使用 Windows 和 Linux 上的本机内存
OxFFFFFFFF Physical RAM 0x00000000 Page fil Process 1-running ileAexe OxFFFFFFFF File syst fileA.exe fileB. exe 0x00000000 Process 2-running fileBexe 程序的每个实例以逝程的形式运行。仁Linuⅹ和 Windows二,进程是一个由受操作系统控制的资潺(比如文件和套接字信息)、一个共型的虚拟地 址空问(在某些架构上不止一个)和至少一个执行线程构成的集合 虚拟川空间大小可能比处理器的物理地址大小更小。32位 Intel x86最初拥有的32伩物理地计仅介许处理器寻址4GB存储窄间。后来,添加了 一和称为物埋地址扩展( Physical Address Extension,PAE)的标性,将物埋地址大小扩大到了36位,允许安装或寻址形多64G8RAM。PAE允 许澡作系统将32位的4GB虛地址空旬映射到·个较大的物理地址范围,但是它不允许每个进程拥有64GB虚拟地址空闬。这意味着如果您将大于 4GB的內存放入32位Inte服务器屮,您离无法将所有内存直接映射到一个单一进程屮 地址窗口扩展( Address windowing Extension)特性允许 Windows进程将其32位地址穴间的一部分作为滑动窗口映射到较大的内存区域中Lnux 使用类似的枝六将内存区域映射到虑拟地址间中。这意味着尽管您丁法育接引用大于4GB的內存,但仍然可以使用较大的内存区域 内核空间和用户空间 尽管每个进程都有其自己的地址空旬,但程序通常无法使用所有这些空间。地址空叵被划分为用宀空同和内空。内核是主要的操作系统程序,包含 用于连接计算札硬件、调度程序以及提供联网和虚拟内存等服务逻辑。 作为计算机后动序列的一部分,作系统内核运行并初始化螋件。一旦内核配置硬什及其自己的内部状态,第一个川户空间进程就会启动。如果用户程 序需要来自操作系统篚服务,宁可以枫行一种称为系统源的作与闪核程序交可,内咳程序然后枫行该请求。系统调用通常是读取和写入文件、联网 和启动新进程等操所必需的 当执行系统调月时,内核需要访问其自己的内存和调用进程旳内存。为为正在执行当前线程的处理器型置为使用地址空问陝射来为当前过程欥射虚拟地 址,所以大部分操作系统将每个进程地址空间的一部分映射到一个通用的内核內存区域。被映射来供内核使月的地址空间部分称为内核空间,其余部分称 为用户空间,可供用户应用程序使用 内核空闰和川户空间之间的衡关糸因操作系统的不同而不同,甚全仨运行于人同使件架构之上的司一操作系统约各个实例间也有所个同。这种平衡常 是可配置的,可进行调整来为用户应用程序或内核提供更多空间。缩减内核区域可能导致一些问题,比如能够同时登录的压广数量限制或能够运行的进程 数量限制。更小的用户空间意味着应用程序编程人员只能使用更少的内存空间。 默认情况下,32位Wndw拥有2GB用户空间和2GB为核空间。在一些 Windows5版本上,逍过向后动配置添加/3GB开类并使川 / LARGEADDRESSAWARE开关重新链接应用程序,可以将这种平衡调为3GB用户中和16B内核间。在32份Lmux1,默 认设置为3GB片户空间和1GB内核空间。一些LinuⅨx分发版提供了一个 hageman内核:支持4GB川户空间。为∫实现这种配置,将进行系统 调用时用的地址空自分配给内核。道过这种方式培加压广空间会减慢系统调月,因为每次讲行系调用时,操作系纤必须在地加空目之间复制数据并重 置进程地址-空间映射。饜2展示了32位 Windows的地址-空间布局 图2.32位 Windaws的地址空间布局 OxFFFFFFFF OxFFFFFFFF 1GB Kemel space 2GB Kemel space OxBFFFFFFF Ox7FFFFFFF Libranes Libranes 2GB User space 3GB User space 0x000o000 Default settings With /3GB switch 图3显示了32位Lnux的地址-空间配置: 图3.32位Lnux的地址-空间布局 OxFFFFFFFF OxFFFFFFFF 1GB Kernel space OxBFFFFFFF 4GB User 4GB Kernel spa 3GB User space 000000000 Default settings With Huge Mem kernel 31位 Linux390上还便用了一个独W的内核地址空间,其中较小的2GB地址空句使对单个地址空进行划分不太合理,但是,390架构可以同时使 用多个地址空间,而且不会降低伫能 步和空闰必须包含程序需要的所有内容,包括程序本身和它使用的共享库(在 Windows|为DDL,在 Linux1为50文件)。共享库不仅会占据窄 使程序无法在其中存储数据,它们还会使地址空间碎片化,减少可作为连续内存块分配的内存。这对于在捱有3GB用户空间 Windows x86上 运行的程序尤为明昱。DLL在构建时没置了首选的加毂地址:当加线DLL时,它被映射到处于特定位置的地址空间,除非该位置已经被山用,在这种情 况下,它会加载到别处 Windows nt最初设计时设置了2GB可用用户空间,这对于要构建来加载接近2GB区域的系统库很有用一—使大部分压 户区域都冂供应用程序白由使用。当用户区域扩展到3GB时,系统共享库仍然加线接近2GB数据(约为用尸空间的一半)。尽管总体用户空间为3GB 但是不可能分配3GB大的内存块,因为共享库无法加载这么大的内存。 在 Windaws口使用/3GB开关,可以将内核空间减少一半,也就是最初设计的大小。车一些情形下,可能耗尽1GB内棱空曰,使TOo变得缓慢, 且无法止常创建新的用户会话。尽管/3GB开关可能对一些应用程序丰常有用,性位何使用它的坏境在部署之前都应该进行彻底的负线测试参见参 考资料,获取兴于/3GB关及其优缺点的更多信总的篷援 本机内仔泄漏或过度使用本利内仁将导致不同的向题,具体取决于您是耗尽了地址空间还是用完了物埋内仔。耗尽地社空通常只会发生在32位进程上, 因为最大4GB的内存很容易分配完。64位进程具有数百或数千GB的用户空间,即使您特意消耗空旬也很难耗尽这么大的空间。如果您确实耗尽了 Java进程的地址空旬,那么]ava运行时可能会出现一些陌生现象,本文稍后将详细讨论.当在进程地址空间比物理内存大的系统上运行时,内存泄漏 或过度伩用本札内存会迫使操作系统交换后各存储器来用怍本机进程的虛拟地址空闩。访问经过交換的内存地址比读取驻留(在物理内存中)的地址慢得 名,因为操作系统必须从硬盘驱动器拉取数据。可能会分配大量内存来月完所有物理内车和所有交换内存(页面窄间),在 Linux,这将轴发内核内 存不足(OOM)结東程序,强制结束最消耗内存的进程。在 Windows上,与地址空被占满时一样,内存分配将会失败 同时,如果尝试使用比物理内存大的虚拟内存,显然在进程由于消耗内存太大而被结束之前就会遇到问题。系统将变得异常缓慢,因为它会将大部分时间 用于在內存与交換空闫之间来回复制数据。当发生这种情况时,計算机和独立应月程序的性能将变得非常糟糕,从而使用户意识到出现了问题。当JM的 ava堆被交换出来时,垃圾旼集器性能会变淂非常差,冂梐序可能被挂起。如果一台机器上冋时使片∫多个Java运行时,那么物理冋存必足够 分配给所有Java堆 鲁回页首 Java运行时如何使用本机网存 ava运行时是一个操作系统进程,它会受到我在上一节屮列出的硬件和操作系统局限性的限制。运行环境提供的功能受一些未知的用户代码驱动,这 使得尢沄预测在毎种情形中运行时环境将耑爕何种资源εJaa应片桯序玍托管Jaνa汘璄中执行的毎^揀作都会潜在地髟响提倛该玕境的运行吋旳 末。本节描述Java应用科序为什么和如何使用本机内存 Java堆和垃圾收集 Java堆是分配了对象的内存区域。大多数 Java se实现都拥冇一个逻辑堆,但是一些专家级Java运行时拥有多个堆,比如实现Java实时范(Real Time specification for Java:RTS])运行时。一个物理堆可板划分为多个逻辑扇区,具体取决于用于管理堆内存的垃圾收集〔GC)算法。这些扇 区迪常实现为连续约本机内存决,这些内存块受Java行管理器(气合垃圾收集器)控制 堆的大小可以在JBva命令行使川Xmx和-Xms选攻来控制(mX表示维的最大大小,mS表示初始大小)。尽管逻轴堆(经宵被使川的内 存区域)可以根据堆上的太象数量和在GC上花费的时间而曾人和缩小,但使用约本机内存大小保持不变,而且由一Xmx值(最人堆人小)指定 大部分GC算法依賴于被分配为连续的内存块的堆,因此不能在堆要扩大时分配更多本内有。所有墐内存必须预先保留ε 保留本机内存与分配本机内存不司。当本机内存被保留时,无法使用物理内存或其他存储器作为备用内存。尽管保留地址空间块不会耗尽物理资源,但会 阻止内存被川、其他川途。由保留从未使川的内存导致的泄漏与泄漏分配的内存一样严重。 当使用钔堆区域缩小时,些垃圾收集器会回收堆的部分(释放堆的后备存储空闫),从而减少使用的物理内存。 对于维护Java堆的內存管理系统,需要更多本机勺存来维护它的状态。当进行垃圾收集时,必领分配数据结构来跟踪空闲存储空间和记录进度。这些数 据结构的确切大小和性质因实现的不同而不同,仨许多数据结构都与堆大小戌正比。 即时(JT)编译器 J编译器在行时编译Java字节怛来忱化本机可执行代码。这极大地媞扃了Java运时的逗度,并且支持」ava应用程序以与本机代码相当的速 度运行 字节冯编译使月本机内存(使用方式与gCC等静杰编泽器使用内存来运行一样),但JT编译器的输入(字节瑪)和输中(可执行代码)必须+存 诸在本机内存口。包含多个经过JI编译的万法的Java应川程序会使用比小型应川序史多的本机内存。 类和类加载器 aa应用程序出一些类组成,这些类定义对象细构和方法逻料。av3应用程序也使用1aa运行时类率 Itin Java.lang. String 中的类,也可以使用第二方库。这些类需要存储在内存中以备使用。 存储类的方式取决于具体实现。 Sun DK使用永久生成( permanent generation, PermGen)堆区域。Java5的IBM实现会为每^类加载器分配 本机内存埙,并将类数据存储在其中。现代Java运行时拥有类其享等技术,这些技术可能需樊将共享内冇Ⅸ域映射刭地址空间。婁理解这些分配机制如 何影响悠]ava运行时的本机内存占压,您需要查阅该实现的技术文档。然而,一些普遍的事实会影响所有实现。 从最基本的层面来看,使用更多的类将需要使用更多内存。(这可能意味着您的本机内使用量会增加,或者您必须明蜘池重新设置 PermGen或共皇 类缓存等区域的大小,以装入所有类)。记住,不仅您的应用程序需要加载到内存中,框樊、应用服务器、第二方库以及包含关的Java运行时也会按需 加载占用空间。 ava运行时可以卸毂类来回收空叵,但是灬有在非常严酷的条件下才会这样做。个能卸载单个类,而是卸毂类加毂器,随其加线的所有类都会被卸毂 只有在以下情况下才能卸载类加毂器: Jaa堆不包含对表示该类加载器的java.lang. Classloader对象的引用。 aa堆不包含对表示类加或器加较的类的任何java.1ang.C1asS对象的引用 ·在Java堆上,该类加戟器加载的仁何类的所有对象鄱不再存活被引用) 需要注意的是,Java运行时为所有Java应用程序创建的3个默认类加栽器( bootstrap, extension和 application冫都不可能满足这些条件 因此,仨何系统类(比如java.lang. String;通过应用程序类加器加载打任何业用程序类不能生送行时释放 即使类力载器适合进行收集,运行时也只矣将收集类加线器作为GC周期的一部分。一些实现只在某些GC盾期中卸载类加载器。 也可能在运行时生成类,而不用释放它。许多正E应用程序使用 Javaserver paces(SP)技术来生成Web页面。使用J5P会为执行的每 页面生成个关,并且这些类会在加载它们的类加载器的整个生存期中直存在一一这个生存期通常是Web应用程序的生存期。 另一种生成类的常见方法是使用Java反射。反射的工作方式因Java实现的不巨而不同,但Sun和IBM实现都使用了这和方,我马上就会讲到 当使用java.lang. reflect Apr时,lava运行时必须样一个反射对象(比如java.lang. reflect. Field 的方法连授到被反尉到的象或类。这可以迪过使川』ava本机接口( Java Native interface,JNI)访问器来完成,这种方法霈要的设置很少,但是 速庋缓慢。也可以在运行时为您想要反射到的每种对象类型动态构建一个关。后一种方法在设置上更慢,但运行速度更快,非常适合于经常反射到一个特 定类的应用程序 Java运行时在最初几次反射到个类吋使用JNI方法,但当使用了若T次NⅠ方法之后,访问器会膨肘为字节码访叵器,这涉及到构建关并通过新的 类加载器进行加载。执行多次反射可能导致创建了许多访问器类和类加裁器。保持对反射对象的引用会导致这些类一直存活,并续占用空间。因为创建 字节碼访问器非常缓慢,所以Java运行时可以缓有这些访问器以备以后使片。一应川程序和框架还会缓行反肘对,这进一步増加」它们的本机内有 占用 JⅠNI支持本机代码(使用匚和C+十等本机编译浯言編写的应用程序)调用Java方法,反之亦然。Java运行吋本身极大地依赖于JNI代码末实现 类库功能,比如文件和网终7O。]NI应用程字可能通过3种方式增加1ava运行时的本机内存占用: NI应用程宇的本机代码被编译到共享库中,或编译为加载到进程地址空间中的可执行文件。大型本机应用程序可能仅仅加载就会占用大量 土程地址空间。 太机代码必须与]ava运行时共享址空间。仟何本机码分酮或本机代码妆行的内存映射都会耗用java运行时的闪存。 其些]N【函数可能在它们的常规操作中使本机内存。 Get TypeArrayelements和 Get/ ypeArrayRegion函数可以将la堆数据复由到本机内存缓冲区中,以供本机代码使用。是否短制数据依赖于运 行时实现:( IBM Developer kit for Java5.0和更高版本会进行本机复制)。通过这种方式访大量Java准数据可能会使用大量本机 NIO Java1.4中添加的新I(NO)类引入了一种基于通道和级冲区来执行I/o的新方式。就像Java堆上的内存支持I/O缓冲区一样,NO添加了 对 ByteBuffer的支持(使用java.nio. ByteBuffer.a1 LocateDirect(方法进行分面) ByteBuffer受本机存而不是Ja堆支持.直接 ByteBuffer可以直接传递到本机换作系统库函数,以执行IO一这使这些 函数在些场景中要快得多,因为它们可以避免在Java埋与本机堆之间复制数据。 对于在何处存储直接 ByteBuffer数据,很容易产生混涌。应用程序仍然在1ava上使用一个对象来编排To强作,但持有该数据的缓冲 区将保存在本札内存中,aa堆对象仅包含对本机堆缓冲区的引用。非接 ByteBuffer将其数据保在Na准上的byte[]数 中。4展示了直接与丰直接 By deBurrer对象之间的区别: 图4.直按与丰直接 Java. nlo. ByteBuffer的内存拓扑结构 Native address space Native address space Native memory buffer va Heap Java Heap Array of Java byte Direct Direct ava.nio Byte Buffer java nio, Byte Buffer object object Memary topology of direct Memory topology of non-direct java nio, ByteBuffers java nio Byte Buffers 直接 ByteBuffer对象会自动清埋本机缓冲区,但这个过程只能作为Java堆GC的一部分来执行,因此它们不会自动响应沲加在本机堆上 的玉力。GC仅在lava堆被填满,以至于无法为堆分配请求捏供脹务职发生,或者在]ava应用程序中显式请求它发生(不议采月这种方式,因为 这刂能导致性能问题 友生垃圾收桌的恃形可能是,本机堆被填满,并且一个或多个直按 ByteBuffers這合于垃圾收集(并且可以驶轻放来出本机堆的空间) 但Java堆几乎总是空的,所以不会发生垃及收集 线程 应用程序中的每个线秆都需要内来存估器堆(用于在调用函数时持有局部变量并维护状态的内存区域)。每个]ava线程都需要堆栈间来运行。根 据实现的不同,Java线程η以分为本机线程和Java堆栈。除了堆梡空间,毎个线程还霍要为线程本地你储( thread- ocal storage)和冈部数结构 提共·些本机闪存。 堆栈大小因Java实现和构的不同而不同。一些实現文持为Java线程指定堆栈大小,其范道常在255KB到756KB之间。 尽管每个绞程使用约内存量非常小,但对于拥有数百个线程的应用程序来说,线程堆栈飞总内存使用量可能非常大。如果运行的应用程序的线程数量比可 用于如理它们的处理器效量多,敕率通常很低,并目可能导致糟糕的性能和更高的内存七用 鲁回页首 本机内存耗尽会发生什么? Java达行时盖于以不同的万式来处理Java堆的耗尽与本机堆的耗尽,但这两种情形具有类以的症状。当Java堆耗尽时,Java应用程序很难正常还 行,因为java应用程序必须通过分酊对象来完成工作。只要Java堆被填满,就会岀现糟橒的GC性能并出表示Java堆被填满的 OutofMemory error. 相反,一旦Java运行时开妇运行并且应片程序处于稳定状态,它可以在本机堆完全託尽之后继续上运行。个一定会发生奇怪釣行为,因为分配本 机内右的操作比需要分配1ava堆的操作少得多、尽管需要本机内存的操作因ⅣM实现不同而异,但也有一些操作很常见:启动线程、加载类以及执行 某和类型的网终和文件1/O 本机内存不足行为与Java堆内存不足行为也不太样,因为无法对本机堆分配进行单点控制。尽管所有Java堆分配都在Java内存管理系统控制之 下,但任何本机代码(无论其位于M、Java类库还是应用程序代码中)都可能执行本机为存分配,而且会失败。尝试进行分配的代码然后会处理这种 情况,无论设计人员的意图是什么,它可能通过]N1接L抛出一个 Outofmemoryerror,在屏幕上偷出一条咱息,发生无提示失败并在 后再试一次,或者执行其他噪作 缺乏可预测行为意味着无法定本机内存是否耗尽。相反,您需要使用日操作系統和Java运仃时的数据执行诊断。 鲁回页首 本机内存耗尽示例 为了帮助您了解本机内存耗尽如何影响您正使用的Java实现,本文的示例代码(参见下载)中包含了些Java程序,用于以不同方式触发本机堆耗 尽。这些示例使用通过¢言编写的本机库来消耗所有本机地址空间,然后尝执行一些使用本机内存的操作。提俱的示例已经过编译,编译它们的指 令包含在示例包的顶级目录下的 README htm文件中 com.ibm.jtc. demos. NativeMemory Glutton类提供了 gobbleMemory()方法,它在个循环中 惆用ma11OC.直到几乎所有本机内存都已耗尽。完成任务之后,它通过以下方式输出分配给标准错误的字节数: Allocated 1953546736 bytes of native memory before running out 针对在32位 Windows运行的Sun和 IBM Java运行时的每次演示,其输出都已被捕获。提供的二进制文件已在以下操作系统上进行了测试 o Linux x86 Linux 390 31 Windows x86 使用以卜 Sun java运行时本捕获输出: java version 1.5.0_11 Java(TM2 Runtime Env ir onment, Standard Edition (build 1.5.0 11-b03) Java Hotspot(TM) Client VM (bui Id 1.5.0_11-b03, mixed mode) 使用的 IBM Java运行时版木为: java version 1.5.0 lava (TM)2 Runtime nvironment, standard Dition (hui ld pwi32devifx-20071025 (SR IBM J9 vM (build 2.3, J2RE 1.5.0 IBM J9 2.3 Windows XP x8G-32 j9vnwi3223-200710 7 (JIT enabled) J9WM-2007100414218_ HdSMR JIT-200708201846i「x1r8 GC-20070810) CL-20071025 在耗尽本机内存时尝试启动线程 COm.ibm.jtc.dem0os. StartingAThreadUnderNativestarvation类尝试在耗尽进程地址空间 时启动一个线程。这是发现Java进程已无尽内仁的一种常用方式,因为亡多应用程序都会在其整个生仔期启动线程。 当在 IBM Java运行时上运行时, StartingAthreadunderNativestarvation演示的输如下 Allocated 1019394912 bytes of native memory be fore r unn ing out JVMDUMPDD6I Processing Dump Eventsysthrow, detai I a/l ang/outofMemoryError -Please wait JVMDUMPOD7I JVM Requesting snap Dump using 'C: \Snapo0o1. 20080323 182114-5172trc JVMDUMPO10I Snap dump written to c: \Snap0001 20080323 1821145172trc JVMDUMP0D7I JVM Requesting Heap Dump us ing 'C: heapdump 20080323 182114.5172 phd JVMDUMPUIOI Heap Dump written to C: \ heapdump 20080323.182114 5172 phd JVMDUMP007I JVM Requesting Java Dump using C: javacore 20080323 1821145172txt JVMDUMPO10I Java Dump written to c: \javacore 20080323 182114 5172txt JVMDUMP013I Processed Dump Event systhrow", detailjava/lang/outofMemoryError java. l ang. outofMemoryError: ZIPO06: outofMeror yError, ENOMEN error in ZipFile upen at java util zipZipFile open (Nati ve Method) at java util zip ZipFile. <init(zi pFile. java: 238) at java util. jar. JarFile<init(JarFile java: 169) at java util. jar. JarFile <init>(JarFile java: 107) at com. ibn. oti. vm Abstract] assLoader fillcache(AbstractclassLoader. java: 69) at com. ibm oti. vm. AbstractcI assLoader getResourceAsStream(Abstractcl assLoader java: 113) at java util. ResourceBundleSl run (resourceBundle. java: 1101) at java security Accesscontroller doprivileged(Accesscontroller. java: 197) at java util. ResourceBundle. loadBundle(ResourceBundle. java: 1097) at java util. ResourceBundle. findBundle(ResourceBundle java: 942) at java util. ResourceBundle. getBundleImpl(RescurceBundle java: 779) at java util. ResourceBundle. getBundle(ResourceBundle java: 716 t ccm. ibmoti. vm. MSgHelp setlocale(MsgHelp java: 103) at cam. ibm oti util. MsgSl run(Msg. java: 44) at java security Accesscontroller do privi leged (accesscontroller java: 197) at com. ibmoti util. Msg.<clinit>(Msg. java: 41) at java. lang. J9VMInternals, initializeImpl(Native Method) at java. lang ]9VMTnternals initiali7eC19vMTnternals java: 194) at java. lang. ThreadGroup. uncaughtException (ThreadGroup java: 764) at java. lang. ThreadGroup. uncaughtException(ThreadGroup. java: 758) at java. lang Thread. uncaughtException THread java: 1315) K0319java. lang. OutofMemaryError: Failed to fork os thread at java. lang Thread. startImpl(Nat ive Method) at java. lang Thread. start(Thread. java: 979) at ccm. ibm. itc. demos, Starti ngAThreadUnderNativestarvati on main startingAThreadunderNativestarvation. java: 22) 開用java.1ang. Thread. start()来尝试为一个新的操作系浅分内存。此崇试会失败并出

...展开详情
试读 24P jvm内存详解
立即下载 低至0.43元/次 身份认证VIP会员低至7折
    抢沙发
    一个资源只可评论一次,评论内容不能少于5个字
    上传资源赚积分,得勋章
    最新推荐
    jvm内存详解 1积分/C币 立即下载
    1/24
    jvm内存详解第1页
    jvm内存详解第2页
    jvm内存详解第3页
    jvm内存详解第4页
    jvm内存详解第5页
    jvm内存详解第6页
    jvm内存详解第7页
    jvm内存详解第8页

    试读已结束,剩余16页未读...

    1积分/C币 立即下载 >