Java(JVM)内存模型 - Java中的内存管理
了解JVM内存模型,如果您想了解Java垃圾收集的工作,Java内存管理非常重要。今天我们将研究Java中的内存管理,JVM内存的不同部分以及如何监视和执行垃圾收集调优。
目录[ 隐藏 ]
Java(JVM)内存模型
如上图所示,JVM内存分为不同的部分。从广义上讲,JVM Heap内存在物理上分为两部分 - Young Generation和Old Generation。
Java中的内存管理 - 年轻一代
年轻一代是创造所有新物体的地方。当年轻一代被填满时,进行垃圾收集。此垃圾收集称为Minor GC。Young Generation分为三个部分--Eden Memory和两个Survivor Memory空间。
关于年轻代空间的重点:
-
大多数新创建的对象都位于Eden内存空间中。
-
当Eden空间充满对象时,执行Minor GC并将所有幸存者对象移动到其中一个幸存者空间。
-
次要GC还会检查幸存者对象并将其移动到其他幸存者空间。所以,一次,幸存者空间之一总是空的。
-
在多个GC循环后幸存的对象将被移动到旧一代存储空间。通常,它是通过在年轻一代物体有资格向老一代推广之前设定年龄的年龄阈值来完成的。
Java中的内存管理 - 旧一代
旧生成内存包含长期存在且在多轮次要GC之后存活的对象。通常,垃圾收集在Old Generation内存中完成时执行。旧一代垃圾收集称为主要GC,通常需要更长的时间。
停止世界大赛
所有垃圾收集都是“停止世界”事件,因为所有应用程序线程都会停止,直到操作完成。
由于Young生成器保留了短暂的对象,因此Minor GC非常快,并且应用程序不会受此影响。
但是,主要GC需要很长时间,因为它会检查所有活动对象。主要GC应该最小化,因为它会使您的应用程序对垃圾收集持续时间没有响应。因此,如果您有响应式应用程序并且发生了大量主要垃圾收集,您会注意到超时错误。
垃圾收集器所用的持续时间取决于用于垃圾收集的策略。这就是为什么有必要监视和调整垃圾收集器以避免高响应应用程序中的超时。
Java内存模型 - 永久生成
Permanent Generation或“Perm Gen”包含JVM描述应用程序中使用的类和方法所需的应用程序元数据。请注意,Perm Gen不是Java堆内存的一部分。
Perm Gen在运行时由JVM根据应用程序使用的类填充。Perm Gen还包含Java SE库类和方法。Perm Gen对象在完整的垃圾收集中被垃圾收集。
Java内存模型 - 方法区域
方法区域是Perm Gen中空间的一部分,用于存储类结构(运行时常量和静态变量)以及方法和构造函数的代码。
Java内存模型 - 内存池
内存池由JVM内存管理器创建,以便在实现支持时创建不可变对象池。String Pool是这种内存池的一个很好的例子。内存池可以属于Heap或Perm Gen,具体取决于JVM内存管理器实现。
Java内存模型 - 运行时常量池
运行时常量池是类中常量池的每类运行时表示。它包含类运行时常量和静态方法。运行时常量池是方法区域的一部分。
Java内存模型 - Java堆栈内存
Java堆栈内存用于执行线程。它们包含方法特定的值,这些值是短暂的,并且引用了从该方法引用的堆中的其他对象。您应该阅读堆栈和堆内存之间的区别。
Java中的内存管理 - Java堆内存交换机
Java提供了许多内存开关,我们可以使用它们来设置内存大小及其比率。一些常用的内存开关是:
VM SWITCH | VM SWITCH描述 |
---|---|
-Xms | 用于在JVM启动时设置初始堆大小 |
-Xmx | 用于设置最大堆大小。 |
-Xmn | 为了设定年轻一代的大小,剩下的空间用于老一代。 |
-XX:PermGen的 | 用于设置永久代存储器的初始大小 |
-XX:MaxPermGen | 用于设置Perm Gen的最大尺寸 |
-XX:SurvivorRatio | 为了提供Eden空间和Survivor Space的比率,例如,如果Young Generation大小为10m且VM开关为-XX:SurvivorRatio = 2,则为Eden Space保留5m,为Survivor空间各保留2.5m。默认值为8。 |
-XX:NewRatio | 用于提供旧/新一代尺寸的比率。默认值为2。 |
大多数情况下,上述选项已足够,但如果您想查看其他选项,请查看JVM选项官方页面。
Java中的内存管理 - Java垃圾收集
Java垃圾收集是从内存中识别和删除未使用对象的过程,以及要分配给将来处理中创建的对象的可用空间。Java编程语言的最佳功能之一是自动垃圾收集,与其他编程语言(如C)不同,其中内存分配和释放是手动过程。
垃圾收集器是在后台运行的程序,它查看内存中的所有对象,并找出程序任何部分未引用的对象。将删除所有这些未引用的对象,并回收空间以分配给其他对象。
垃圾收集的基本方法之一涉及三个步骤:
-
标记:这是垃圾收集器识别正在使用哪些对象以及哪些对象未使用的第一步。
-
正常删除:垃圾收集器删除未使用的对象并回收要分配给其他对象的可用空间。
-
使用压缩删除:为了获得更好的性能,在删除未使用的对象后,可以将所有幸存的对象移动到一起。这将提高内存分配给新对象的性能。
简单的标记和删除方法存在两个问题。
-
首先是它没有效率,因为大多数新创建的对象将被废弃
-
其次,用于多个垃圾收集周期的对象最有可能在未来的周期中使用。
使用简单方法的上述缺点是Java垃圾收集是分代的,并且我们在堆内存中有Young Generation和Old Generation空间。我已经在上面解释过如何根据Minor GC和Major GC扫描对象并将其从一代空间移动到另一代空间。
Java中的内存管理 - Java垃圾收集类型
我们可以在应用程序中使用五种类型的垃圾收集类型。我们只需要使用JVM开关来为应用程序启用垃圾收集策略。让我们一个一个地看看它们。
-
串行GC(-XX:+ UseSerialGC):串行GC使用简单的mark-sweep-compact方法,用于年轻和老一代的垃圾收集,即Minor和Major GC。
串行GC在客户端机器中非常有用,例如我们的简单独立应用程序和CPU较小的机器。它适用于内存占用少的小型应用程序。
-
并行GC(-XX:+ UseParallelGC):并行GC与串行GC相同,不同之处在于为年轻代垃圾收集生成N个线程,其中N是系统中的CPU核心数。我们可以使用
-XX:ParallelGCThreads=n
JVM选项控制线程数。并行垃圾收集器也称为吞吐量收集器,因为它使用多个CPU来加速GC性能。并行GC使用单个线程进行旧代垃圾回收。
-
并行旧GC(-XX:+ UseParallelOldGC):这与Parallel GC相同,只是它为Young Generation和Old Generation垃圾收集使用多个线程。
-
并发标记扫描(CMS)收集器(-XX:+ UseConcMarkSweepGC):CMS收集器也称为并发低暂停收集器。它为老一代做垃圾收集。CMS收集器尝试通过与应用程序线程同时执行大多数垃圾收集工作来最小化由于垃圾收集而导致的暂停。
年轻一代的CMS收集器使用与并行收集器相同的算法。此垃圾收集器适用于我们无法承受更长暂停时间的响应式应用程序。我们可以使用
-XX:ParallelCMSThreads=n
JVM选项限制CMS收集器中的线程数。 -
G1垃圾收集器(-XX:+ UseG1GC):垃圾优先或G1垃圾收集器可从Java 7获得,其长期目标是替换CMS收集器。G1收集器是并行,并发和递增压缩的低暂停垃圾收集器。
垃圾第一收集器不像其他收集器那样工作,并且没有年轻和老一代空间的概念。它将堆空间划分为多个大小相等的堆区域。当调用垃圾收集时,它首先收集具有较少实时数据的区域,因此“垃圾优先”。您可以在Garbage-First Collector Oracle Documentation中找到有关它的更多详细信息。
Java中的内存管理 - Java垃圾收集监控
我们可以使用Java命令行以及UI工具来监视应用程序的垃圾收集活动。对于我的示例,我使用Java SE下载提供的演示应用程序之一。
如果要使用相同的应用程序,请转到Java SE下载页面并下载JDK 7和JavaFX演示和示例。我使用的示例应用程序是Java2Demo.jar,它存在于jdk1.7.0_55/demo/jfc/Java2D
目录中。但是,这是一个可选步骤,您可以为任何Java应用程序运行GC监视命令。
我用来启动演示应用程序的命令是:
pankaj@Pankaj:~/Downloads/jdk1.7.0_55/demo/jfc/Java2D$ java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar
jstat
我们可以使用jstat
命令行工具来监视JVM内存和垃圾收集活动。它附带标准JDK,因此您无需执行任何其他操作即可获得它。
要执行,jstat
您需要知道应用程序的进程ID,您可以使用ps -eaf | grep java
命令轻松获取它。
pankaj@Pankaj:~$ ps -eaf | grep Java2Demo.jar 501 9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar 501 14073 14045 0 9:48PM ttys002 0:00.00 grep Java2Demo.jar
所以我的java应用程序的进程ID是9582.现在我们可以运行jstat命令,如下所示。
pankaj@Pankaj:~$ jstat -gc 9582 1000 S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT1024.0 1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.6541024.0 1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.6541024.0 1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.6541024.0 1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.6541024.0 1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.6541024.0 1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.6561024.0 1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656
jstat的最后一个参数是每个输出之间的时间间隔,因此它将每1秒打印一次内存和垃圾收集数据。
让我们一个一个地浏览每一列。
-
S0C和S1C:此列显示Survivor0和Survivor1区域的当前大小(KB)。
-
S0U和S1U:此列显示Survivor0和Survivor1区域的当前使用情况,以KB为单位。请注意,其中一个幸存区域始终为空。
-
EC和EU:这些列以KB为单位显示Eden空间的当前大小和用法。请注意,欧盟规模正在增加,一旦跨越欧共体,就会调用次要GC并减少欧盟规模。
-
OC和OU:这些列以KB为单位显示旧代的当前大小和当前使用情况。
-
PC和PU:这些列显示Perm Gen的当前大小和当前使用情况,以KB为单位。
-
YGC和YGCT:YGC列显示年轻一代发生的GC事件数。YGCT列显示Young生成的GC操作的累计时间。请注意,由于较小的GC,它们都在同一行中增加,其中EU值被删除。
-
FGC和FGCT:FGC列显示发生的完整GC事件的数量。FGCT列显示完整GC操作的累计时间。请注意,与年轻一代GC时序相比,完整GC时间过长。
-
GCT:此列显示GC操作的总累计时间。请注意,它是YGCT和FGCT列值的总和。
jstat的优点是它可以在我们没有GUI的远程服务器中执行。请注意,通过-Xmn10m
JVM选项指定的S0C,S1C和EC之和为10m 。
使用Visual GC的Java VisualVM
如果要在GUI中查看内存和GC操作,则可以使用jvisualvm
工具。Java VisualVM也是JDK的一部分,因此您无需单独下载它。
只需jvisualvm
在终端中运行命令即可启动Java VisualVM应用程序。启动后,您需要从Tools - <Plugins选项安装Visual GC插件,如下图所示。
安装Visual GC后,只需从左侧列打开应用程序,然后转到Visual GC部分。您将获得JVM内存和垃圾收集详细信息的图像,如下图所示。
Java垃圾收集调整
Java垃圾收集调优应该是您应该用于提高应用程序吞吐量的最后一个选项,并且只有当您看到由于更长的GC时序导致应用程序超时而导致性能下降时。
如果您java.lang.OutOfMemoryError: PermGen space
在日志中看到错误,请尝试使用-XX:PermGen和-XX:MaxPermGen JVM选项监视和增加Perm Gen内存空间。您也可以尝试使用-XX:+CMSClassUnloadingEnabled
并检查它与CMS垃圾收集器的性能。
如果您看到很多Full GC操作,那么您应该尝试增加旧代内存空间。
整体垃圾收集调整需要花费很多精力和时间,并且没有严格的规则。您需要尝试不同的选项并进行比较,以找出适合您应用的最佳选项。
这就是Java内存模型,Java中的内存管理和垃圾收集,我希望它能帮助您理解JVM内存和垃圾收集过程。