JVM

Java 发表评论
一.jvm运行时内存区域

 包含堆,虚拟机栈,本地栈(调用native方法时用到),方法区(perm区),程序计数器。

 假设32位操作系统,这时系统限制每个进程大小为2G。这样上述这些区域(对于本地栈及程序计数器来讲,是无法设置的,hotspot有提供-Xoss参数用于设置本地方法栈。但实际是无效的)可以用相应参数设置,共同划分全部2G内存。

注意还有一块直接内存,不属于JAVA运行时区域。但是它的空间用得太多的话,还是受限于物理内存和虚拟内存总大小,因此也会抛出OutOfMemory。典型的redis中有大量使用直接内存。redis负责使用System.gc()告诉虚拟机要回收直接内存(触发FULL GC)。如果将 DisableExplicitGC打开,这时如果大量用直接内存将发生 OutofMemmory Directory Memonry。原因是虚拟机只会在FULL GC的时候触发对直接内存的清理。

二.垃圾回收

垃圾回收围绕两个问题:

1.怎样认为一个对象是该回收了的对象?如何找出?

2.找出之后,如何做到有效清除,并使内存碎片最少,回收效率更高?

对于第一个问题,我们很自然的想到引用计数,但引用计数存在循环引用的问题。
还有一种就是根搜索算法,也就是说从当前Root区域开始(使用到对象的区域,如线程栈,方法区中静态引用,常量引用等)标记出所有可达的对象。
最后那些没有被标记的对象就认为是需要回收的对象。
现在主流虚拟机实现用的都是这种方法。

对于第二个问题,找出之后,如何做到有效清理,内存碎片最少,回收效率更高?
首先要回收快,比如给你一个8G的内存区域,这时每次垃圾回收都要扫描8G内存必定使回收效率很低。
虚拟机根据对象存活期不同的特点,想到将区域分为年轻代和老年代。这时假设将年轻代和老年代都是4G,这时可以分别对这两块区域进行回收。
年轻代中对象生命周期短,对它进行回收效益比老年代的大。

考虑只将年轻代分成一块4G会有什么问题?
这时你会发现当对年轻代做完垃圾清除后,会有很多碎片存在(由于很多对象被清除了)。
整理碎片是相当耗时的一个过程。

那怎么解决?

虚拟机将年轻代分为eden及两块survisor区域。每次只使用eden及一块survisor。
每次新建的对象先进入eden区,如果这时engen区不够放了,这时触发minor gc。将扫描eden及一块survisor。将要回收的对象清理掉,还存活的放入到另一块survisor。这样每次回收后对于eden区都不会有内存碎片(将存活的复制到survivor后,eden区全部清除,故不会有碎片?)。

上述讲的对于年轻代的垃圾回收使用的就是标记-复制算法(缺点是需要一块备用区域)。

对于老年代来讲,如果还使用标记-复制算法,这时需要另一块区域作为备用。
这时候由于老年代对象存活率高,因此每次回收后,需要大量的对象拷贝到备用区域,不太合算。

因此对于老年代来讲,使用的是标记-清除算法(或标记整理),即先找出所有要清理的对象,然后清除掉。
这时必然会有碎片在,因此对于像CMS这样的并行老年代回收算法,可以设置多少次FULL GC后触发一次内存整理。

下面列下现有的JVM垃圾回收器:
对于年轻代有:
 serial(单线程,回收时停止所有用户线程),
  par New(多线程,停止所有用户线程)
 parallel scavange(多线程,停止所有用户线程,与 par New类似。但可以控制响应时间,通过缩小年轻代大小,进而减少回收空间来达到)
 
对于老年代有:
 serial old(单线程,停止所有用户线程)
 parallel old(多线程,停止所有用户线程。通常与parallel scavange一起使用)
 CMS(多线程,部分回收阶段不需要回收用户线程,达到可控的响应时间)
 G1

由于par New与CMS采用了全新架构,因此par New与CMS一起使用。
parallel scavange与parallel old通常一起使用。

上一篇:
下一篇:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *