内存分区及垃圾收集

2018-03-19  本文已影响0人  icelovesummer

1. 内存分区

java内存分区

1.1 程序计数器

线程私有,可以看作当前线程所执行的字节码的行号指示器。如果执行native方法,程序计数器值为undefined。此区域不会发生OutOfMemoryError。

1.2 Java虚拟机栈

1.3 本地方法栈

1.4 Java堆

1.5 方法区

class常量池

2. 垃圾收集

  1. 程序计数器、Java虚拟机栈和本地方法栈随线程而生,随线程而灭。栈帧分配多少内存都是编译期可知的(JIT不谈)。不需要考虑回收的问题。
  2. 堆和方法区的分配都是动态的,回收也比较复杂。

2.1 哪些内存需要回收?(对象还存活吗?)

引用计数法
可达性分析

【可作为GC-Roots的对象】:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

【四种引用】

  1. 强引用:代码中普遍存在的,只要强引用存在,就不会GC引用的对象。
  2. 软引用:在系统将要发生内存溢出之前,将会把这些对象列为回收范围进行第二次回收。JDK中有SoftReference类。
  3. 弱引用:被弱引用关联的对象只能生存到下一次GC之前,无论当前内存是否足够,这些对象都会被回收。JDK中有WeakReference类。
  4. 虚引用:一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能够在对象被GC时收到一个系统通知。JDK中有PhantomReference类。

【真正判定对象死亡】

  1. 对象进行可达性分析后发现没有与GC-Roots相关联,那么就进行第一次标记并且进行一次筛选。
  2. 筛选条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize方法或者finalize已经被虚拟机执行过了,那就GG了。
  3. 如果需要执行finalize方法,那么对象进入F-Queue队列中,稍后由虚拟机自动建立的低优先级线程Finalizer去执行。虚拟机并不保证能够执行结束。
  4. 稍后GC将对F-Queue中的对象进行第二次小规模标记,如果finalize方法中成功将自己与引用链相连,那就复活了。
  5. finalize方法只能执行一次,下次就不能复活了。

【方法区回收】

  1. 常量:判定很简单,和堆相似。例如“ABC”,如果当前系统没有一个String对象引用常量池的“ABC”,那么有必要的话,将回收这个常量。
  2. 类:需要同时满足三个条件。代表可以回收,并不一定。
    第一、该类的所有实例已经被回收,就是说Java堆中没有该类的实例了。
    第二、加载该类的ClassLoader已经被回收。
    第三、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

2.2 如何回收?(GC算法)

标记清除

首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

复制

将可用内存按容量分为相等的两块,每次只使用其中一块,当这一块用完了,就将还存活的对象复制到另外一块上,然后把已经使用的内存空间一次清理掉。

【新生代回收】

标记-整理算法
标记-整理算法

2.3 垃圾收集器

垃圾收集器
Serial
ParNew
Parallel Scavenge
Serial Old
  1. JDK 1.5之前与Parallel Scavenge收集器搭配使用;
  2. 作为CMS收集器的后备预案, 在并发收集发生Concurrent Mode Failure时启用。


    Serial Old
Parallel Old
Parallel Old
CMS

【四个步骤】:

  1. 初始标记:仅仅标记GC-Roots直接关联到的对象,需要STW。
  2. 并发标记:进行GC-Roots Tracing。
  3. 重新标记:修正并发标记期间因用户程序继续运作导致的变动。需要STW。
  4. 并发清理:并发清除。

需要STW的两个步骤耗时很短,其它步骤都是和用户线程并发工作的,所以停顿时间很短。

CMS

【缺点】:

  1. 对CPU资源非常敏感。CMS默认回收线程数:(CPU数量+3)/ 4。当CPU数量大于4时,GC线程最多不超过25%资源;小于4时,GC会占用较多CPU资源。
  2. 无法清除浮动垃圾。并发清理阶段,用户线程还在运行,会有新的垃圾出现,这一次GC无法处理它们,只能留在下次GC。因此CMS需要流出一定内存给用户线程使用,如果内存不够用户线程使用,会出现Concurrent Mode Failure导致另一次Full GC。
  3. CMS基于标记清除,容易产生内存碎片。因此CMS提供了-XX:+UseCMSCompactAtFullCollection开关参数, 用于在Full GC后再执行一个碎片整理过程. 但内存整理是无法并发的, 内存碎片问题虽然没有了, 但停顿时间也因此变长了, 因此CMS还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction用于设置在执行N次不进行内存整理的Full GC后, 跟着来一次带整理的(默认为0: 每次进入Full GC时都进行碎片整理).
G1
  1. 并行与并发:G1可以充分利用多CPU优势来缩短STW时间。
  2. 分代收集。
  3. 空间整合:G1整体上基于“标记-整理”,局部上基于复制算法。因此不会产生碎片。
  4. 可预测停顿:有计划的避免在整个堆中进行全区域GC。G1跟踪各个Region里面垃圾的价值大小和回收成本,后台维护一个优先列表,优先回收价值最大的Region。
  5. Remembered Set:用于避免扫描全堆。每个region都有一个与之对应的Remembered Set,虚拟机发现程序在对reference类型的数据进行写操作时,会产生一个write barrier 暂时中断写操作,检查reference引用的对象是否处于不同的region中,如果是,通过cardtable把引用信息记录到被引用对象所属的region中的Remembered Set中,进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆进行扫描。

【四个步骤】

  1. 初始标记:扫描GC-Roots直接关联的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,需要STW。
  2. 并发标记:进行可达性分析。
  3. 最终标记:修正在并发标记期间因用户线程继续运作导致标记产生的变动部分,虚拟机将这段时间内的变化记录在Remembered Set Logs里,最终标记阶段需要把Remembered Set Logs合并到Remembered Set中。需要STW,也可以并发。
  4. 筛选回收:根据回收价值和回收成本进行GC。可以并发,但是由于只回收一部分Region,时间是可控的,停顿线程可以提高效率。

3. 内存分配策略

对象优先在eden区分配

大对象直接进入老年代

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

动态对象年龄判断

-如果survivor中相同年龄所有对象大小总和大于survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年代。不用等待XX:MaxTenuringThreshold。

空间分配担保

上一篇 下一篇

猜你喜欢

热点阅读