我是菜鸟:GC和内存分配策略

本文详细介绍了Java中的垃圾收集(GC)机制,包括被回收对象的确定、各种垃圾收集算法(如引用计数器、可达性分析、标记-清除、复制、标记整理和分代算法)以及HotSpot JVM的实现。还探讨了各种垃圾收集器,如Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old和CMS收集器,以及它们的特点和适用场景。最后,文章讨论了对象的内存分配策略,涉及对象分配的主要区域和特殊情况。
摘要由CSDN通过智能技术生成

在上一章中复习了java运行时,内存的各个分区。下面复习GC(垃圾收集),请时刻牢记下面几个问题:
1. 哪些内存需要回收?(java堆和方法区,那么为什么是这2个区域?分配和回收都是动态的。)
2. 什么时候回收?
3. 如何回收?
下面逐步复习并回答。

被回收对象的确定—-对象已死?

引用计数器

给对象添加一个引用计数器。当计数器为0时,对象就不可以使用。(大概像操作系统中,文件的软链接为0时,才能安全删除。忘记了。哎。)
注意:该方法很少被使用的,原因是:很难解决对象之间相互循环使用的问题。(为什么呢?举个例子。)

可达性分析算法—常用的算法

idea: 该对象到GC roots 节点不可到达。 (某个对象到根节点不连通。)
思考:什么样的对象可以作为GC roots?
总的来说,可作为GC roots的对象包括栈中引用的对象(java栈 和 本地方法栈jni引用的对象。)和方法区引用的对象(常量引用的对象和类静态属性引用的对象。)

垃圾收集算法

标记-清除算法

该算法分为两个阶段:标记阶段清除阶段,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,未被标记的对象就是未被引用的垃圾对象,然后再清除阶段,清除所有未被标记的对象。这个思想的意思是,在程序的起点开始就是一个根节点,随着程序的运行就会引用不同的节点,被引用的做标记,未被引用的做清除标记,做清除标记的作为垃圾回收。这种算法的缺点就是会产生空间碎片。

复制算法

将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中存活的对象复制到未使用的内存块中,之后,清除正在使用的内存块的所有对象,然后再交换两个内存的角色,这样就完成了垃圾回收。其缺点是当内存中大部分都是存活对象,那么这种算法都是在做无用功。

标记整理算法

这是一种老年代的回收算法,在标记-清除算法做了改进,也是从根节点开始,对所有对象做一次标记,对标记清除的对象不是简单的清除,而是先把存活对象压缩到内存的一端,确定好边界之后,再把所有的标记清除的对象全部删除,这样就避免了碎片的产生,同时又不需要频繁交换空间。

分代算法

它将内存区间根据对象的特点分为几块,根据每块内存区间的特点,使用不同的算法,从而提高垃圾回收效率。一般内存区域分为老生代和新生代,新建的对象建在新生代,常用的对象建在老生代,新生代用复制算法,老生代用标记-压缩算法。这样两边的效率最高,这是目前最常用的垃圾回收算法。

HotSpot算法的实现

目前主流的JVM都是使用的准确的GC(Exact VM),在回收垃圾的时候并不需要对所有的对象扫描一次。在HotSpot的实现中,使用了OopMap对象来记录对象各个偏移量上对应的数据类型(注意:什么时候记录合适?类加载),同时也会在特定的位置(特定的位置?即安全点和安全区,也就是说程序在执行的过程中停顿的位置。程序在执行的时候,只能在安全点才能够停顿。 安全区是指引用关系不会发生变化的代码区。)记录下栈和寄存器中哪些位置是引用。(在栈和寄存器中的引用什么时候记录?JIT编译的时候。)其实这样,记录各个位置中的引用,GC就可以直接得到这些信息,显而易见,这样的效率将会大大提升。

常见的垃圾收集器

Serial收集器

最基本的收集器,在执行GC时,必须由JVM在后台自动的暂停其余的所有线程。其具有简单高效的特点。在桌面应用场景中新生代内存管理比较好(原因?管理的内存比较小,停顿时间较小。)
Serial收集器运行示意图

ParNew收集器

为Serial的多线程版本。其余的行为和Serial收集器是一样的。其为Server模式下虚拟机首选的新生代收集器。其可以与CMS收集器(老年代的收集器)配合使用,其实也是唯一一个除了Serial收集器外的收集器可以和CMS配合使用。

Parallel Scavenge收集器

该收集器是一个新生代收集器,使用复制算法,而且也是并行的收集器。该收集器关注的焦点是系统的吞吐量。(用户代码时间/总的时间),说白了也就是关注的是CPU的利用率,而不是停顿的时间,即交互的影响。这样适合对于后台任务垃圾的收集。
思考:对于吞吐量的控制应该有哪些参数来控制?(显而易见,直接设置吞吐量,当然,还可以是最大垃圾收集停顿时间。)
当然也可以设置为 动态调整, 即自适应调节策略。这样可以节约很多不必要的麻烦,而这对于之前的2中收集器是没有的功能。

Serial Old收集器

之前的Serial收集器是针对新生代的收集器,显而易见,这个收集器是针对老年代的版本。是一个使用 标记-整理 算法的单线程收集器。主要也是用于Client模式下的虚拟机使用。若是给Server使用,那么主要有以下用途:
1. 在JDK1.5及之前与PS收集器配合使用;
2. 作为CMS收集器的备胎,在并发收集失败的时候使用;

Parallel Old 收集器

给出以下几个关键字:老年代版本; 多线程; 标记-整理;
PO收集器运行过程
其通常与PS收集器配合。

CMS收集器(Concurrent Mark Sweep)

一种获取最短回收停顿时间为目的的收集器。(对服务响应要求高的系统中使用。)并发收集,低停顿。
关键字:标记-清除
具体步骤:
1. 初始标记
必须要停止其他的线程(以后就用STW表示这句话了。),仅仅标记GC ROOTs 能够直接关联的对象。
2. 并发标记
在此阶段就是 进行GC roots Tracing. 
3. 重新标记
关键字: STW
修正并发标记期间因为用户程序继续运作而导致标记产生变动的那部分对象的标记。
4. 并发清除
可以和用户线程一起工作。

CMS收集器

想想其有什么缺点(我是当初看的时候,没有想到,太笨了吧。):
1. CMS对CPU资源太过于敏感。(当CPU数量较少时候,会严重用户程序,使得用户程序变得很慢)
2. CMS收集器无法收集 浮动垃圾, 可能会出现 Concurrent Mode Failure(何时出现该情况?) 而导致另一次full gc的出现(会启动备胎,SO GC)。
浮动垃圾? 在并发清理的过程中,用户线程可能产生新的垃圾,只能在下一次GC的时候进行清理。
CMS在何时启动?能在老年代内存全部用完的时候启动吗?(不能,为什么呢?(因为还要留下一部分的空间用来运行GC使用。而对于的其它收集器则没有此要求。(如果不留出空间会发生什么?(CMF。 哈哈))))。
3. 想想该收集器是什么算法收集?标记-清除。缺点就不说了。(空间碎片)

G1收集器

明天再来吧。不想看了。
关键字:标记-整理(好处一?相对于标记清理);精确控制停顿; 高吞吐低停顿;区域划分;优先级
下面对精确的控制停顿进一步说明:能够让使用者能够明确的在一个长度为M的毫秒时间片内,消耗在垃圾收集上的时间不得超过N毫秒。(实时Java(RTSJ))
能够实现高吞吐低停顿的原因:
能够避免全区域的垃圾回收。(之前的收集器都是对整个新生代或者老年代进行回收。)G1将各个区域(哪些区域?)划分为多个大小固定的独立区域,并且跟踪这些区域中的垃圾堆积程度,在后退维护一个优先列表,每次根据允许的时间收集,优先回收垃圾最多的区域。

对象的分配再叙

必须明确的是,在大多数情况下,对象的分配是在堆上进行分配,但是也有例外,就是:可能在栈上间接的分配。
总的思想:对象的分配主要在新生代的eden区域,如果启动了本地线程缓冲,则优先在TLAB上分配;在少数的情况下也可能在老年代中分配。在哪儿分配主要取决于当前使用的什么垃圾回收器以及JVM和内存中的相关参数的设置。
以下几个原则:
1. 优先在eden区域上分配
当eden区域没有足够的区域的时候,执行一次minor gc.
2. 大对象(需要连续的内存对象,如长字符串和数组)直接进入老年代进行分配。更加普遍的一种方法还有,设置一个对象大小的阀值,当大于阀值的对象就分配在老年代中,这样可以避免Eden 和 survivor 区域进行复制。(新生代GC采用复制算法进行垃圾收集。)
3. 长期存活的对象进入老年代中。 (长期存活,如何判断,思考下需要哪些数据结构。(没错,就是年龄计数器和对应的阀值。))
不过还有一种情况是:当Survivor空间中同年龄的对象所占的空间大于Survivor空间的一般的时候,年龄不小于该年龄的对象全部进入老年代。
4. 对象担保机制
在Minor gc 时候,jvm会检查之前的每次晋升到老年代中平均大小是否小于老年代中剩余的空间,如果大于就进行一次full gc. 若小于那么就检查是否设置了担保,如果设置了就只是进行minor gc, 否则也要进行full gc. (感觉有点混乱,就这样理解吧。后面再查点资料,补充上来。)
下面解释下minor gc 和 full gc.
Minor gc(新生代gc):发生在新生代的垃圾收集动作。Minor GC比较频繁且速度较快。
Full GC(老年代GC):发生在老年代的gc.

该章的内容复习完了,后面再具体的整理下思路,记下相应的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值