您的位置:首页 > 编程语言 > Java开发

java垃圾收集与内存分配策略笔记

2017-10-04 23:33 561 查看

什么是垃圾收集(Garbage Colletion)?

程序员不必手动回收不用的对象,而是由JVM自动回收不再使用的对象,清除它们占用的

内存。

gc需要解决的3个问题:

* 那些内存需要回收?

* 什么时候需要回收?

* 如何回收?

gc管理的内存区域

Java内存运行时区域中程序技术器、虚拟机栈、本地方法栈这三个区域是线程私有的.

随着方法的进入、退出,栈的栈帧进行入栈和出栈操作。每一个栈帧分配多少内存基本

是在编译生成Class文件后确定了,因此不要过多考虑几个区域的内存分配和回收问题。

主要考虑的是Java堆区和方法区(包括运行时常量池)。因为对象的创建和销毁必须在

运行时才能确定,因此这些对象使用的内存的分配和回收都是在运行时动态分配和销毁

的。

JVM对堆中的对象的管理?

引用计数算法(Reference Counting)

给对象添加一个引用计数器,当有一个引用时,计数器加1。当计数器为0时,该对象就是

没有使用的对象。

弊端:无法解决循环引用的问题。A、B、C三个对象,A引用B,B引用C,C引用A。除此之外,

这3个对象就没有其他引用。实际上这3个对象已经不会再被访问了,但是引用计数算法

却无法回收他们。

根搜索算法(GC Roots Tracing)

通过一系列被称为”GC Roots”的点作为起始向下搜索,当一个对象到GC Roots没有任何

引用链(Reference Chain)相连,则证明此对象是不可用的。

在Java中,GC Roots包括:

1. 在虚拟机栈(帧中的本地变量)中的引用对象

2. 方法区中的类静态属性引用的对象

3. 方法区中的常量引用的对象

4. JNI(native方法)中的引用

根搜索算法判断对象是否存活与引用有关。java将引用分为四类:**强引用、软引用、

弱引用、虚引用**,这四种引用强度依次逐步减弱。

根搜索算法中不可达的对象并非“非死不可”,这时候它们暂时处于“缓刑”阶段,

真正宣告一个对象死亡,至少要经历两次标记过程。

如果通过根搜索后发现没有与GC Roots相连的引用链相连。它将会被第一次标记并且

会进行筛选,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟

机调用过,虚拟机将这两种情况都视为没有必要执行finalize()方法。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个

名为F-Queue的队列之中,并在稍后有一条虚拟机自动建立、低优先级的 finalize的线

程去执行。虚拟机会触发finalize()方法,但并不承诺等待它执行结束。finalize()

方法是对象逃脱死亡的最后一次F-Queue中的对象机会。GC将会对F-QUEUEF-Queue中的

对象进行第二次小规模的标记,如果某个对象重新与GC Roots引用链上的对象建立关联

关系,那么第二次标记时它将被移除F-Queue。

任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收

,它的finalize()方法不会被再次执行。

JVM对方法区的管理

方法区即永久代,在这一个内存区域进行GC的目的是回收废弃常量和无用类

判定废弃常量的方式与判定对象是否存活的方式类似。

判定无用类的3个条件:

1. 该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例

2. 加载该类的ClassLoader已经被GC

3. 该类对应的java.lang.Class对象没有在任何地方被引用。不能通过反射访问该类的

方法。

在大量使用反射、动态代理、CGLib等bytecode框架、动态生成JSP以及OSGi这类频繁

自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证永久代不会溢出。

是否对类进行回收可以使用-XX:+ClassUnloding参数进行控制,还可以使用

-verbose:class或者-XX:TraceClassLoaing 、 -XX:+TraceClassUnLoading查看

类加载、卸载信息

垃圾收集算法的实现

标记-清除(Mark-Sweep)

算法分成”标记”和”清除”两个阶段。先标记处需要回收的对象,然后回收所有需要回收

的对象。

标记清除算法是最基础的收集算法,后续的收集算法都是基于这种思路优化后得到的。

缺点:

效率问题:标记和清除的过程效率都不高

空间问题:标记清理之后会产生大量不连续的内存碎片。空间碎片太多,可能导致

后续使用后无法找到足够的连续内存而提前出发另一次的垃圾收集。

标记-复制(Mark-Copying)

为了解决效率和内存碎片的问题,提出了一种”Mark-Copying”的收集算法,可以将

内存划分为两块,每次使用其中一块,当半区的内存用完了,可以将还存活的对象

复制到另外一块上,然后就把原来整块内存空间一次清理掉。这种方法的优点是每次

内存回收都是对整个半区的回收,内存分配时也是在连续的半个内存区域上分配,不用

考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存。这种算法实现简单,

运行高效,但是闲置了一半的内存。

商业虚拟机中用标记-复制算法回收新生代。由于新生代只能怪的对象,存活时间

很短,不需要按1:1的比例来划分内存,而是将内存分为一块较大的eden和2块较小的

survivor空间,每次使用eden和其中一块survivor。当回收时将eden和survivor中还

存活的对象一次拷贝到另一个survivor空间上,然后清理掉eden和正在使用survivor。

虚拟机默认eden和survivor的大小比例是8:1,该比值可以通过-XX:SurvivorRatio调

整。

缺点:

如果对象存活率较高,survivor区不够用,就需要依赖其他内存区域(譬如老年代)进行分配担保(Handle Promotion).

标记-压缩(Mark-Compact)

先标记需要回收的对象,然后将所有存活的对象一端移动,然后清理需要回收的对象。

缺点:

复制收集算法在对象存活率高的时候,效率有所下降

需要额外的内存空间进行分配担保,当对象存活率高导致survivor区不够用,以后

可以将存活时间较长的对象放到其他区域。

内存分代

将堆区分为新生代和老年代,方法区分为永久代

垃圾收集器

Serial收集器

  单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop TheWorld,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代

收集器。

ParNew收集器

  ParNew收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一模一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。

Parallel Scavenge收集器

  Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,

允许较长时间的STW换取总吞吐量最大化

Serial Old收集器

  Serial Old是单线程收集器,使用标记-压缩算法,是老年代的收集器,上面三种都是使用在新生代收集器。

Parallel Old收集器

  老年代版本吞吐量优先收集器,使用多线程和标记-压缩算法,

JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除

Serial Old外别无选择,因为PS无法与CMS收集器配合工作。

CMS(Concurrent Mark Sweep)收集器

  CMS是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来说至关重要,这类应用对于长时间STW一般是不可容忍的(因此CMS不可以与PS配合使用)。CMS收集器使用的是标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启

CMS收集结束后再进行一次内存压缩。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: