Java内存管理

来源:互联网 发布:io域名申请 编辑:程序博客网 时间:2024/06/06 00:06

Java内存管理

Java 中所谓的自动内存管理最终可以归结为自动化的解决了两个问题

  • 给对象分配内存
  • 回收分配给对象的内存

要弄清楚Java的内存管理机制,首先要了解Java虚拟机对内存区域的划分,然后弄清楚不同区域中的内存管理机制。

运行时数据区域

Java虚拟机在执行Java程序时会把它所管理的内存划分为不同的数据区域,各个区域有不同的创建销毁时间,有的随着虚拟机进程而创建销毁,有的则随着虚拟机中的用户线程而创建销毁

根据Java虚拟机规范的规定,Java虚拟机中的数据区域分为以下几种:

  • 方法区
  • 堆区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

程序计数器

这是一块较小的内存空间,它可以被看做为当前线程所执行的字节码行号指示器。在虚拟机概念模型中,虚拟机就是通过改变它的值来读取下一条要执行的字节码指令。为了能够支持多线程并发执行,每一条线程都拥有各自的程序计数器。

Java虚拟机栈

这一块内存空间是线程私有的。虚拟机栈描述的是Java的方法调用模型,每一个方法被调用的时候都要创建一个栈帧(Stack Frame),用来存储局部变量表、操作栈、动态连接、方法出口等信息。局部变量表再编译期就已经确定了内存分配的大小。虚拟机规范中规定了两种异常:

  • 当线程请求的栈深度大于虚拟机允许的栈深度时,抛出StackOverFlowError异常
  • 当虚拟栈动态扩展时,内存不够了,抛出OutOfMermeryError异常

本地方法栈

这块内存区域的作用与虚拟机栈类似,只不过它执行的是本地方法,具体的虚拟机有不同的实现,甚至Sun 的 HotSpot 虚拟机把它直接与虚拟机栈合为一个。

堆区

Java堆区是Java虚拟机所管理的内存中最大的一块,也是垃圾回收器管理的主要区域,它被各个线程所共享。虚拟机规范中,所有的对象都在堆区进行分配,实现中可能没有这么严格。堆区可以是在物理上不连续的内存区域,但是在逻辑上是连续的,为了更好的进行垃圾回收,堆区往往分代划分,大多数以可扩展的方式实现,当堆无法扩展内存时,会抛出OutOfMermeryError的异常。

方法区

它与堆区一样,也是被各个线程所共享的,主要存放的是被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。它有另外一个别名叫做非堆,还有的地方称为永久代。Java虚拟机对这个区域的限制非常宽松,它除了和堆区一样不需要屋里上连续的内存外,还可以不实现垃圾收集。

运行时常量池

是方法区的一部分,用来存储编译期生成的各种字面常量和符号引用。

直接内存

它并不是虚拟机运行时所管理的数据区域的一部分,也不是Java虚拟机中所定义的内存区域,但是它会被频繁的使用到。JDK1.4引入了NIO(new I/O)类,加入了一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配内存,然后通过一个Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著的提高性能。因为避免了在Java堆和Native堆中来回的复制数据。

垃圾收集算法

标记-清除法

标记清除算法是最基本,最简答的垃圾收集算法。它的思想很简单,把不能存活的的对象进行标记,然后在内存分配不足,进行垃圾回收的时候清除掉这些对象。它的优点是非常简单,容易实现,缺点是容易产生很多的内存碎片。

复制算法

复制算法是对标记清除算法的改进,常用于堆中的新生代垃圾回收。它将内存分为Eden和Survivor两类,其中Survivor有两块,一般比例为8:1 ,在垃圾收集时,先将Eden中和一块Survivor中的存活对象复制到另外一块中Survivor中,然后清除掉另外两块内存中的数据,这样可以保证有90%的空间在新生代是可用的,但是同时也有缺点就是当存活兑现的大小大于10%则会实施分配担保将对象分配到老年代。

标记整理法

标记整理算法常用于老年代的垃圾收集。它的思想是先将不能存活的对象进行标记,然后在垃圾回收的时候将对象挨个往前复制,最后清理端之外的内存。

分代收集算法

分代收集算法并不是一种具体实现,没有新的思想,而是说将堆内存分为不同的代,在每个代中采用最适合的算法实现垃圾收集。

垃圾收集器

Java虚拟机规范中并没有规定垃圾收集器的具体实现。在每种不同的虚拟机中有着不同的实现。JDK1.7 update14中就实现了7种垃圾收集器。到目前为止,并不存在完美的垃圾收集器,否则jdk中就不会实现如此多的垃圾收集器了,只能根据应用场景,对不同的内存分区选择不同的垃圾收集器。

Serial收集器

这是最基本的垃圾收集器,它是一个单线程(不仅仅是使用单线程去GC,更重要的是它在进行GC的时候会要求暂停所有的用户进程)的收集器,实现了复制算法,因此常用于新生代的垃圾回收。它是运行在Client模式虚拟机的默认新生代垃圾收集器。

优点:简单高效,单线程专心于垃圾回收,少了多线程切换的代价。

ParNew收集器

Serial收集器的多线程版本,跟Serial收集器拥有相同的实现。它是许多运行在Server模式下的虚拟机的首选的新生代收集器。主要原因是由于只有ParNew收集器可以与CMS收集器一起工作。

Parallel Scavenge收集器

同样是实现了复制算法的新生代收集器,但是 Parallel Scavenge 关注点与其他的垃圾收集器不同,其他的如 CMS 垃圾收集器都倾向于减少GC时用户线程停顿的时间来提高响应速度。而 Parallel Scavenge 专注于提高吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间)。适合于后台交互较小,计算较多的环境使用,可以实现CPU的最大利用率。

Serial Old 收集器

这是老年代的垃圾收集器。Serial收集器的老年代版本,它实现了标记-整理算法。通常作为Client模式下的默认老年代收集器。如果用在Server模式下,它的用途主要有两种:

  1. 在JDK1.5版本之前与 Parallel Scavenge 收集器配合使用。
  2. 作为CMS 垃圾收集器的后备预案。当CMS 垃圾收集器出现Concurrent Mode Failure的时候使用。

Parallel Old收集器

Parallel Scavenge收集器的老年代版本,它使用多线程和标记-整理算法。使得Parallel Scavenge垃圾收集器总算是有了一个有共同目标的“伙伴”。专注于提高吞吐量。

CMS收集器

实现了最小回收暂停时间,适合于Web环境下多交互的场景。

G1收集器

新的垃圾收集器

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记-清理”算法不同,G1从整体看来是基于“标记-整理”算法实现的收集器,从局部(两个Region之间)上看是基于“复制”算法实现,无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 可预测的停顿:这是G1相对于CMS的另外一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器特征了。

内存的分配

对象分配的规则并不是确定的,细节上取决于当前使用的哪一种垃圾收集器组合,还有虚拟机中和内存相关的参数的设置。下面是Serial/Serial Old收集器下的策略。

  • 对象优先在Eden中分配

    大多数情况下,对象优先在新生代的Eden区进行分配,如果Eden区空间不够,虚拟机将会发起一次Minor GC,如果Minor GC后空间仍然不够,则通过分配担保机制分配到老年代中。

  • 大对象直接进入老年代

    如果对象过大,比如很长的数组,如果超过了-XX:PretenureSizeThreshold参数中指定的值,则会直接在老年代进行分配。这样可以避免GC时频繁的在EdenSurvivor之间复制数据。

  • 长期存活的对象将进入老年代

    每个对象的对象头中都对象年龄的设置,Eden中的对象在Survivor区中每熬过一次Minor GC,年龄就会增长一岁。当年龄增长到一定的岁数,默认是15岁,就会被晋升到老年代。

  • 动态对象年龄判断

    为了更好的适应不同的内存状况,虚拟机也不是完全按照年龄来决定对象是否应该被转移到老年代。如果某个时候,相同年龄的对象大小已经超过了Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代。

  • 空间分配担保

    在发生 Minor GC 之前,虚拟机会检查老年代的最大可用连续空间是否大于新生代的所有对象的空间,如果大于,则可以进行一次 Minor GC,如果小于,则会检查 HandlePromotionFailure 设置值是否允许担保失败。如果允许担保失败,则会取每一次晋升到老年代对象大小的平均值作为经验值与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多的空间。

0 0
原创粉丝点击