`

深入理解JVM学习笔记-垃圾收集器和内存分配策略

    博客分类:
  • JVM
阅读更多

如果不了解java虚拟机内存分区,可参考上篇:

http://chuninsane.iteye.com/blog/2161803

 

GC的区域

程序计数器 、本地方法栈、虚拟机方法区,这三个是随线程的消失而覆灭的, 随着方法的结束或线程的结束内存自然就会跟着被回收。java堆和方法区则不一样, 一个接口中的对个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,平时说的GC大多是对java堆来进行的。

 

可达性的不同算法

一、引用计数算法 (皱眉

如果有引用指向该对象,则计数器++, 如果引用减少一个的话则--,但是如果变量中自身有field的指向就是该对象本身, 则会产生该对象一直被假引用。

二、可达性算法

通过一系列的称为GC Roots的对象作为起始点, 当一个对象到GC Roots没有任何引用链相连,就表明该对象是不可达的。

能作为GC Roots的对象包括下面几种:

1、虚拟机栈中引用的对象

2、方法区中类静态属性应用的对象

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

4、本地方法栈中JNI引用的对象

 

我们希望可以描述这样一类对象, 如果内存空间还足够时,则保留在内存中, 如果内存空间在进行垃圾回收之后还是很紧张的话, 则可以抛弃这样一些对象, 很多系统中的缓存对象都符合这样的应用场景,由此便产生了java的4类引用。

java中4类引用

1. 强引用  垃圾回收永远不会回收掉被引用的对象

2. 软引用  描述一些有用但是不是必须的对象, 如果第一次GC后内存还是很紧张, 则在第二次GC中才会清理这些对象, 如果第二次GC内存还是没有足够的空间, 才会抛出内存溢出异常

3. 弱引用  用来描述非必须的对象, 被弱引用的对象只能存活到下一次GC来临之前

4. 虚引用  这个是最弱的一种引用关系  为一个对象设置虚引用关联的唯一目的就是能让这个对象被收集器回收时受到一个系统的通知

 

finalize  (该方法的优先级较低)

如果一个对象是不可达的, 这个对象并非是facebook(非死不可)的,这个对象会进行一次筛选, 筛选的条件是此对象是否有必要执行finalize方法   当对象没有覆盖finalize方法(Object实现了该方法) 或者是虚拟机已经调用过该对象的finalize方法  虚拟机将这两种情况视为没必要执行。如果有必要执行finalize,则加入队列中,如果finalize拯救失败的话, 该对象就真的facebook了。

 

回收方法区

永久代的垃圾收集主要回收两部分的内容 :废弃的常量和无用的类

常量只要满足无引用即可判定为废弃的常量

无用的类判断需要满足下面三个条件

1.该类的所有实例都已经被回收了 

2.加载该类的ClassLoader已经被回收了

3.该类的java.lang.Class对象没有在任何地方被引用

如果满足了上述这三个条件的还不一定被回收,只是表示其满足别回收的条件。

如果大量使用反射,动态代理 CGLib等ByteCode框架,动态生成JSP已经OSGI这类频繁自定义ClassLoader的场景都需要虚拟机具有类卸载的功能, 保证永久代不会溢出

 

垃圾收集算法

一、 标记清除算法

对需要进行GC的区域进行标记,然后进行清理。

该算法有两个问题 ,一个是效率问题, 标记和清除两个过程的效率都不会太高,另一个就是会产生大量不连续的内存碎片。

二、复制算法

将内存分为两块, 每次只会使用其中的一块,将未标记的对象复制到另外一个未使用的内存中,这种方法会牺牲掉一半的内存,代价实在太高了

现代的商业虚拟机都采用这种算法来回收新生代,并不是按照1:1的比例来分配Eden和Survivor空间

三、标记整理算法

将所有存活的对象都向一端移动,进行整理。

四、分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”,根据对象存活周期的不同将内存划分为几块, 一般是将其分为新生代和老年代。(Hotspot虚拟机使用的是这种算法然后加上复制算法处理新生代)

 

分析执行时间的敏感还体现在GC停顿上, 因为这个分析工作必须在一个能确保一致性的快照中进, 无论是什么收集器, 枚举跟节点时也是必须要停顿的。

安全点

在OopMap的协助下(这个我也不大清楚),HotSpot可以快速且准确完成GC的枚举,

如果对每一条指令都生成对应的OopMap,那将会需要大量的额外空间, 虚拟机也没这样做, 而是设置了一些成为安全点的位置,在安全点生成OopMap, 线程只有在到达安全点时才能暂停

 

在执行gc时, 所有的线程都跑到最近的安全点上停顿下来, 两种可供选择的方式,抢先式中断、主动式中断

 

另外值得注意的是,safepoint是针对正在执行的进程而言, 如果线程处于sleep或者block状态时,此时就要用到saftregion了, 在这个区域中的任意地方GC都是安全的。

 

垃圾收集器

Serial收集器

此收集器是单线程的,单线程不仅仅说明他只会使用一个cpu或一个收集线程进行gc, 在gc过程中会停止掉其他的线程, Serial收集器的口号就是Stop the world,很明显也体现了这个特征。

ParNew收集器

这个收集器和Serial有很多共同点, 在收集的时候使用多线程进行处理, 此收集器也是需要Stop The world

Parallel Scavenge收集器

该收集器关注的是达到一个可控制的吞吐量,而高吞吐量则可以高效率地利用cpu时间,尽快完成程序的运算程序,如果需要大的吞吐量的话可以使用该收集器。

Serial Old收集器

该收集器是Serial的老年区版本, 也是采用的单线程的方式, 不过采用的收集算法是标记整理

Parallel Old收集器

该收集器是Parallel Scavenge的老年代版本

CMS收集器

是一种以获取最短回收停顿时间为目标的收集器,CMS是基于标记-清除算法实现的,所以会出现一些内存碎片。CMS无法处理浮动垃圾,可能会出现Concurrent Mode Failure而导致需要进行一次Full Gc。老年代区域需要预留空间给CMS的清理线程

G1收集器(理念很先进,但是貌似效率不大乐观,不常用)

运作大致可分为以下几个步骤

①初始标记

②并发标记

③最终标记

④筛选回收

 

 

内存分配与回收策略(java中的对象大多数都是朝生夕灭的,如果在新生代发生gc(Minor GC)次数最为频繁)

1、对象优先在Eden进行分配

大多数情况,对象在新生代的Eden区分配,当Eden没有足够空间时,虚拟机将会进行一次Minor GC

2、大对象直接进入老年代

这里的大对象是指需要大量连续内存空间的java对象,直接将大对象分配在老年代,可以避免在Eden区及两个Survivor区之间发生大量的内存复制。

3、长期存活得对象将进入老年代

采用了分代的思想来管理内存,那么内存回收就必须能识别哪些对象应放在新生代,哪些应该放在老年代,故虚拟机为每个对象设置了一个age计数器,在Eden区出生并经过一次Minor GC后仍然存货,并且能别survivor容纳,则将被移到Survivor空间中,每次年龄都会增长1,当到达一定年龄就会升级到老年区(默认为15)

4、动态对象年龄判定

为了更好适应不同程序的内存状况,虚拟机并不是永远要求对象的年龄达到限定值才会晋升到老年代。

如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需受限定值的限定。

5、空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果成立可保证此次Minor GC是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,则会继续检查老年代中最大连续空间是否大于历次晋升到老年代对象的平均大小,如果是则尝试进行一次Minor GC(尽管此次GC是否危险的)。如果小于或者老年代中最大连续空间小于历次晋升到老年代对象的平均大小,则会进行一次Full GC。

 

 

 

 

 

 

2
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics