Java内存池是如何划分的?

259

我目前在使用jconsole监控Java应用程序。内存选项卡可以让你选择:

Heap Memory Usage
Non-Heap Memory Usage
Memory Pool “Eden Space”
Memory Pool “Survivor Space”
Memory Pool “Tenured Gen”
Memory Pool “Code Cache”
Memory Pool “Perm Gen”

它们之间有什么区别?


假设您正在使用Sun JDK,最好的答案可以在他们的文档中找到:调整垃圾回收(JDK 1.5)垃圾回收FAQ(JDK 1.4) - kdgregory
5个回答

366

堆内存

堆内存是Java虚拟机运行时数据区,用于为所有类实例和数组分配内存。堆可以是固定大小或可变大小。垃圾回收器是自动内存管理系统,用于回收对象的堆内存。

  • Eden空间:大部分对象最初分配内存的池。

  • Survivor空间:包含在Eden空间垃圾回收后幸存下来的对象。

  • 老年代Old Gen:包含已在Survivor空间存活一段时间的对象。

非堆内存

非堆内存包括共享于所有线程的方法区和Java虚拟机内部处理或优化所需的内存。它存储每个类的结构,例如运行时常量池、字段和方法数据以及方法和构造函数的代码。方法区在逻辑上是堆的一部分,但根据实现,Java虚拟机可能不会对其进行垃圾回收或压缩。与堆内存一样,方法区的大小可以是固定或可变的。方法区的内存不需要连续。

  • 永久代:包含虚拟机本身的所有反射数据,例如类和方法对象。对于使用类数据共享的Java虚拟机,此代被分为只读区域和可读写区域。

  • 代码缓存:HotSpot Java虚拟机还包括一个代码缓存,其中包含用于编译和存储本地代码的内存。

这里有一些关于如何使用Jconsole的文档。


4
我不确定 @dfa 完全正确,因为 Java 虚拟机规范明确说明: “虽然方法区逻辑上属于堆,但简单的实现可能选择不对其进行垃圾回收或压缩。” 然而,很明显 jconsole 显示代码缓存和永久代是非堆的,这似乎与规范相矛盾。 有人可以对这个矛盾提供更多的澄清吗? - James Bloom
@JamesBloom - 我也在想同样的问题。尽管基本定义说明了哪个内存池属于哪种类型(堆/非堆),但它是否可以明确地改变状态呢? - Umang Desai
2
这篇文档似乎是从以下网址复制的:http://docs.intergral.com/pages/viewpage.action?pageId=22478944。该文档包含一些有关JVM的其他有用信息,值得一读。 - Steve Siebert
1
尽管有很多赞,但这个回答实际上并不那么有意义。例如,“在伊甸园垃圾收集器中幸存的对象”是什么意思?这些对象在幸存者空间中幸存后是否从伊甸园移动到幸存者空间,或者它们在伊甸园中的空间被视为幸存者空间?除了伊甸园空间之外的池中的垃圾收集会发生吗?完全不清楚。 - Mikhail Batcer
请不要忘记堆栈(在非堆侧):) - Toothless Seer
Mikhail,很明显,对象从Eden空间出来到非终身空间(eden+survivor(s))的第一次扫描时会进入幸存者区域。对象在幸存者区域之间不断移动,直到达到MaxTenuringThreshold(默认为16)。然后它们进入终身堆。一旦进入终身堆,它们就会一直保留,直到被标记和清除。请参见此页面上的下面/更新的图表。 - BodhiOne

93
The new关键字在Java堆上分配内存。堆是主要的内存池,可供整个应用程序访问。如果没有足够的内存可用于分配该对象,则JVM尝试从堆中回收一些内存进行垃圾回收。如果仍然无法获得足够的内存,则会抛出OutOfMemoryError错误,并退出JVM。
堆被分成几个不同的部分,称为代。随着对象在更多的垃圾回收中幸存下来,它们将晋升到不同的代。较老的代不会经常进行垃圾回收。因为这些对象已经被证明寿命更长,所以它们不太可能被垃圾回收。
当对象首次构造时,它们被分配在伊甸园空间。如果它们在垃圾回收中幸存下来,它们将晋升到Survivor Space,如果它们在那里生存足够长的时间,它们将被分配到Tenured Generation。这一代进行垃圾回收的频率要少得多。
还有第四代,称为永久代(Permanent Generation,PermGen)。驻留在这里的对象不符合垃圾回收的条件,通常包含JVM运行所必需的不可变状态,例如类定义和字符串常量池。请注意,计划在Java 8中删除PermGen空间,并将其替换为一个名为Metaspace的新空间,该空间将保存在本机内存中。 参考资料:http://www.programcreek.com/2013/04/jvm-run-time-data-areas/

Diagram of Java memory for several threads Diagram of Java memory distribution


这个图看起来非常自我解释... 这对于任何垃圾回收算法都有效吗?G1有不同的集合。 - Venkateswara Rao
@Pythoner 我认为深紫色的标志应该是-XX:PermSize而不是-XX:MaxPermSize,因为它已经在上面定义过了。 - Anurag

35

在Java8中,非堆区不再包含PermGen而是Metaspace,这是Java8的重大变化,旨在通过增加元空间的大小(取决于JVM为类数据所需的空间)来消除Java内存不足错误。


1
实际上,有元空间和类空间:http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/considerations.html#sthref66 - mrswadge

25

Java Heap Memory(Java堆内存)是操作系统分配给JVM的一部分内存。

对象驻留在称为堆的区域中。堆在JVM启动时创建,应用程序运行时可能会增加或减小。当堆变满时,垃圾将被收集。

enter image description here

您可以在下面的SE问题中找到有关 Eden Space、Survivor Space、Tenured Space和Permanent Generation 的更多详细信息:

Young , Tenured and Perm generation

自Java 8版本以来,PermGen已被Metaspace所取代。

关于您的查询:

  1. Eden Space、Survivor Space和Tenured Space是堆内存的一部分
  2. Metaspace和Code Cache属于非堆内存。

Codecache:JVM生成本地代码并将其存储在称为codecache的内存区域中。 JVM出于各种原因生成本地代码,包括针对动态生成的解释器循环,Java Native Interface(JNI)存根以及通过即时(JIT)编译器将Java方法编译为本地代码。 JIT是codecache的最大使用者。


4
堆被分为年轻代和老年代,如下所示:
年轻代:这是一个对象短期存在的地方,它被分为两个空间:
- Eden空间:使用new关键字创建对象时,内存分配在此空间。 - Survivor空间(S0和S1):这是一个池,包含在Eden空间进行Java垃圾回收后幸存下来的对象。
老年代:这个池基本上包含tenured和虚拟(保留)空间,并将保存在年轻代垃圾回收后幸存下来的对象。
- Tenured空间:这个内存池包含在Survivor空间进行多次垃圾回收后幸存下来的对象。

enter image description here

说明

假设我们的应用程序刚刚启动。

因此,此时这三个空间(Eden、S0、S1)都是空的。

每当创建新对象时,它都会被放置在Eden空间中。

当Eden空间变满时,垃圾回收过程(minor GC)将在Eden空间上进行,并将任何幸存的对象移动到S0中。

然后我们的应用程序继续运行,添加新对象时会在Eden空间中创建。下一次垃圾回收过程运行时,它会查看Eden空间和S0中的所有内容,并将幸存的对象移动到S1中。

PS:根据配置,对象在Survivor空间中应存活多长时间,对象可能也会在S0和S1之间来回移动。当达到阈值时,对象将移动到旧的生成堆空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接