2.JVM垃圾收集器与内存分配策略

JVM垃圾收集器与内存分配策略

Java的程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;(生命周期)

这几个区域的内存分配和回收都具备确定性,因为方法结束的时候,内存就跟着回收了。

Java堆和方法区和他们不同,我们只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收是动态的!

如何判断对象是否存活?

1.引入技术算法

给对象添加一个引用计数器,每当有一个地方引用他们的时候,计数器值就加1;当引用失效的时候,计数器值就减1。任何时候,计数器为0的对象是不可以再被使用的。

这个算法实现很简单,应用也非常广泛。*但是!***java虚拟机并没有!使用这个方式去管理内存!主要原因就是:它很难解决对象之间互相引用的问题!

2.可达性分析算法

基本思路是通过一系列的称为“GC Roots”的对象最为起始点,从节点开始向下搜索,搜索时所走过的路径称为引用链。当对象达到CG Roots没有任何引用链相连的时候,证明此对象不可用。于是认定其是可回收的。

GC Roots对象包括以下几种:1.虚拟机栈中引用的对象。2.方法区中类静态属性引用的对象。3.方法区常量引用的对象。4.本地方法栈中JNI引用的对象。

引用

我们希望能描述这样一类对象:当内存空间还足够,能保留在内存之中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。

JDK1.2后,对引用类型概念进行了扩充,分为了强引用,软引用,弱引用,虚引用。引用强度依次减弱。

1.强引用:代码中普遍存在的东西,只要强引用还在,垃圾收集器就不会回收被引用的对象。

2.软引用:描述一些还有用,但是并非必需的对象。对于软引用关联着的对象,在系统要发生内存溢出之前,会把这些对象列进二次回收。如果回收了还没有足够内存,则抛出内存溢出异常。

3.弱引用:描述非必须对象,强度比软引用还弱,被弱引用关联的对象。

4.虚引用:称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间够长影响。那它为啥存在?!答案是:在这个对象被收集器回收时能收到一个系统通知!

一个对象的死亡

一个对象如果要确定死亡,需要两次标记过程:

1.如果对象在可达性分析后发现没有与GC Roots相连的引用链,标记第一次!

2.对象没有覆盖finalize()方法,或者该方法被虚拟机调用过了,则这个对象已经要死了。

如果判定对象有必要执行finalize()方法,那么将会把它放在一个F-Queue队列中,稍后会有一个虚拟机建立的,低优先级别的Finalizer线程去执行它。

也就是说finalize()方法给了对象一个最后的机会:

​ GC会对F-Queue中的对象进行二次标记,如果对象想存活,只需要重新与引用链上任何一个对象建立关系即可!比如说:将自己赋值给某个类变量或者对象的成员变量~这时候他就会被送出即将被回收的集合,死里逃生!

但是注意!任何对象的finalize()方法都只会被系统调用1次,再被回收就只能Say GoodBye了。

回收方法区

永久代回收两部分:1.废弃常量 2.无用的类

废弃常量的回收很简单,只要关注是否被引用即可。

回收无用的类比较麻烦,需要满足三点:

1.java堆不存在该类的实例

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

3.无法再任何地方通过反射的方式访问该方法

想想就知道,只要满足以上三点,那么这个类不用多说,他基本上已经是个废类了✧(≖ ◡ ≖✿)

垃圾收集算法

(敲黑板!这里很重要很重要!)

垃圾已经确定了,我们需要一个方式去收集他们,那么这时候就需要一些算法:

1.标记-清除算法——最基础的收集算法

​ 分为“标记”和”清除”两个阶段:首先标记需要回收的对象,在标记完成之后统一回收所有被标记的对象。

后续的收集算法都是基于这个思路并改进的。它有两点不足:1.效率低下 2.浪费空间

2.复制算法

​ 将可用内存按容量划分大小相等的两块,每次只用一块。这一块内存用完了,就将还存活的对象复制到另一块上面,然后把使用过的内存空间一次清理掉。这个算法很高效,实现起来也很简单:每次都是半区进行回收,内存分配也不用考虑碎片的情况。但是也有缺点:将内存缩小到原来的一半,有点浪费性能。

新生代的对象基本都是”朝生夕死“,生命周期很短,所以基本上对新生代都用复制算法。

3.标记-整理算法

​ 复制收集算法在对象存活率高的情况下要进行较多的复制操作,效率会变低。更关键的第一点:若不想浪费50%的空间,需要额外空间进行分配担保,以应对被使用的内存中所有对象都100%存活的情况。所以老年代,一般不能用它。

根据老年代的特点,有人提出了标记-整理算法,标记过程和”标记-清除算法一样“,但是后面不是直接回收,而是让所有存活对象向一端进行移动,然后直接清理到边界以外的内存。

4.分代收集算法

​ 思路差不多,根据对象存活周期不同将内存分块,一般是把java堆分成新生代和老年代。对于新生代(每次收集都有大批对象死去)就自动去选用复制算法。对于老年代(对象存活率高,没有额外空间进行担保)必须使用”标记-清理“或者”标记-整理“算法。

​ 这个分代收集算法是当前商业虚拟机主流。

垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

可以说新生代基本采用“复制算法”。老生代采用“标记-整理”算法。CMS采用“标记-清除”算法。

1.Serial

Serial收集器是最基本,发展历史最久的收集器,曾经是虚拟机新生代收集器的唯一选择!(复制算法)

Serial是个单线程的收集器。它在进行回收垃圾的动作时候,其他的工作线程必须都要停止,等着它收集结束。这样一点都不酷!设想你在打LOL团战,打到一半突然卡那不动两分钟,你说难受不难受?但是它依然是Client模式下的默认新生代收集器~~~

但是它的优点也很明显:简单、效率高

2.ParNew

它是并行收集器,是Serial的多线程版本,(复制算法)与Serial的区别就在于它是多线程的,其余都一样。然而ParNew在单CPU环境并没有比Serial强,线程的切换还会增加开销。

3.Parallel Scavenge

“吞吐量优先”收集器,它是并行收集器,是个新生代收集器(采用复制算法),它与众不同的一点在于它的目的和其他收集器不太一样——达到一个可控制的吞吐量。等等。。。什么是吞吐量?

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾回收时间)

高吞吐量就是CPU可以高效的利用CPU时间,从而完成运算任务,适合后台运算多,交互少的任务。

4.Serial Old

Serial收集器的老年代版本,是串行的,利用了单线程和“标记-整理”算法。它可以和Parallel Scavenge配合使用,也可以作为CMS收集器的备选。

5.Parallel Old

是Parallel Scavenge的老年代版,并行收集器,用多线程和“标记-整理”算法。这个收集器的出现解决了Parallel Scavenge的尴尬局面,因为Parallel Scavenge必须和Serial Old配合,但是Serial Old还在性能上拖了后腿!以至于Parallel Scavenge吞吐量优化的优势没法发挥出来~但是!!!Parallel Old出现了,解决了这个问题,完全可以使用Parallel Old和Parallel Scavenge进行组合。

6.CMS

CMS收集器是基于“标记-清除”算法,它的目标是:获取最短回收停顿时间。这对于B/S系统服务端来说非常重要!因为这是与用户体验息息相关的!

它工作流程比较复杂:

  • 初始标记(需要Stop the World)
    • 标记一下GC Root直接关联的对象!时间很短~
  • 并发标记(需要Stop the World)
    • 开始可达性分析
  • 重新标记
    • 修正因为用户操作导致标记的改变
  • 并发清除
    • 就是清除掉~

并发标记和并发清除的时间会多一些,但是他们是并发的,并不影响用户线程的正常工作。

CMS很强大但是!!但是它也有缺点:

  • CMS收集器对CPU资源太过于敏感。会占用部分线程导致吞吐量降低
  • CMS收集器无法处理浮动垃圾,可能会导致Full GC
  • 既然用“标记-清除“算法,就要承担它的缺点——收集结束后会产生大量的空间碎片,给分配对象带来麻烦
7.G1

G1收集器是最新的!整体基于”标记-整理”,局部使用”复制算法“

特点:

1.并行与并发:用多CPU缩短停顿时间,仍然可以通过并发,让Java程序继续执行!

2.分代收集:G1可以不需要其他收集器配合,独立管理GC堆!

3.空间整合:整体上使用“标记-整理”算法,局部使用“复制”算法。

4.可预测停顿:可以建立可停顿的时间模型。

G1收集器将整个Java堆划分为多个大小相等的独立区域,取代了新生代和老年代的物理隔离!它避免了在整个Java堆进行全区域的垃圾收集。

G1收集器的工作流程:

  • 初始标记
    • 标记GC Roots,修改TAMS的值
  • 并发标记
    • 可达性分析,找出存活的对象。
  • 最终标记
    • 做标记修正
  • 筛选回收
    • 指回收一部分Region,时间是用户可控的

参考资料:
  《深入理解Java虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值