Java内存—堆内存管理

概述

堆具有以下特性:

  • 堆区是一个共享的运行时数据区,可以分配给所有类实现与数组的真实对象。
  • 依赖于系统的配置,堆的尺寸可以设置为动态或者是固定的。它在Java虚拟机的启动期间被初始化。
  • JVM允许用户控制其初始化或按各自所需改变其大小,当一个new关键字被使用以后,对象在堆中会被分配一定的内存空间,但是对象的引用却被存放在堆栈。
  • 运行中的JVM进程存在一个并且只存在一个堆
  • 分配给对象的堆内存可以通过GC回收。

例如:

Scanner sc = new Scanner(System.in);

上面的语句创建了Scanner类的一个对象,该对象在堆中分配了内存空间,引用sc被推入堆栈。

生代

Java SE平台的一个优点是它屏蔽了开发者对于内存分配与垃圾回收的复杂度。然而,垃圾回收是最重要的瓶颈,理解这个隐藏实现对于Java的性能调优是很有用的。

在程序运行的过程中,当一个对象不再有指向它的指针的时候就会被回收。最直接了当的算法是直接遍历每一个可到达(可以通过对应的引用访问)的对象。那些剩余的对象就可以垃圾回收了。这个垃圾回收方法所花费的时间是和存活的对象的数量是成正比的,这对于维护很大数量存活对象的应用程序是不可能实现的。

虚拟机集成了许多不同的垃圾回收算法,它们都与分代收集结合使用的。当垃圾回收器检查堆中的每一个存活对象的时候,垃圾回收器利用观察大多数应用程序而得到特性,以最小的代价回收不再使用(回收)的对象。这些观察到的特性中最重要的是弱生代假说,大多数的对象存活的时间都很短。

内存是按代(内存池持有不同年龄的对象)管理的。垃圾回收发生成在每一个被填满的代中。绝大多数的对象都被分配到一个年轻对象的专用池(年轻代)中,并且大多数的对象会死在这里。当年轻代被填满时,会导致一个只回收年轻代的次要回收,其他代中的垃圾不会被回收。这样的回收所花费的时间可以归结为:与被回收的存活对象的数量成正比;回收填满了死亡对象的年轻代被很快的回收。在每一次的次要回收期间,都有一定比例从年轻代中幸存(继续处于存活状态)的对象被移到老年代。最终,当老年代被填满后必须进行回收,结果会导致一次主要回收,整个堆都要进行回收。主要回收通常会比次要回收时间长得多,因为涉及到的对象的数量大大增加。

年轻代(The Young Generation)

年轻代是所有新对象开始的地方,一个对象通过new操作符被创建后,就会在年轻代中专门的一小块叫作伊甸区的空间中为其分配内存。最终,伊甸区被对象填满后,就会发生次(小规模的)垃圾回收。这里会使用标记算法,有些对象(被引用的)被标记,有些对象(未被引用)不被标记。那些被标记的对象随后会被移动到年轻代中被称为幸存区的S0区域(注意,幸存区自身分成两个区域,分别是S0和S1)中。那没有标记的会被垃圾回收器自动回收。

保持这种状态一直到伊甸区再次被填满,这时候,新一轮开始。上面图中的事情再次发生,但是这一轮有一些不同。S0是存在一些对象的,所以,所有从伊甸区和S0区幸存下来的标记对象都要被移到幸存区中的S1区。如下图:

通过上图,我们可以将幸存区中的S0及S1区理解为From幸存区及To幸存区,很显然,上图中的S0就是From区,而S1区就是To区,将图片进行更改:

有两个非常值得注意的事情:

  • 所有对幸存区的对象都会用一个年代计数器将其标记,标记算法会检查这些对象是否达到了去下一个阶段的阈值——老年代。
  • To区与From区不是固定的,S0和S1会按照一定的规则和算法进行变更。

老年代(The Old Generation)

老年代可以被认为是长时间存活对象的存放地。基本上,如果一个对象在年轻代中经历了多次垃圾回收,达到了一个特定的年龄阈值以后,它会就会被移动老年代中。只有发生主要垃圾回收事件的时候,老年代中的对象才能被回收。

什么是”stop the world“

当发生次要垃圾回收(针对于年轻代)或者主要垃圾回收(针对老年代)时,会发生世界停止。这意味着,所有应用程序的线程完全停止,并且一直要等到垃圾回收的完成为止。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注