java 垃圾回收机制初涉

虽然以前有了解过些,但了解的不是很深入。
很快就忘记了(字太多懒的看),所以这次决定写个理解;

堆内存分配

首先在最开始聊聊 jvm 的内存分配

java 将堆内存分为了新生代跟老年代
新生代跟老年代的比例为 1:2
新生代又被分成了1个eden(这游戏我还没通呢),2个survivor。比例为2:1:1

大概长这样

回收对象的判定

##1.引用计数算法

给对象添加个计数器统计被引用的次数。
引用时+1,失效就-1,当为0时,就回收。简单暴力;
这种 objective-c 在用,但有个明显的副作用,就是对象被循环引用时就无法处理

##2.可达性分析算法(根搜索算法)
这是java采用的算法。从一个根节点(GC Roots)作为起点向下搜索引用对象,形成一棵树。树上的节点代表可以到达。反之不可到达,即可回收对象

可达树

Java 语言定义了如下 GC Roots 对象:

虚拟机栈(帧栈中的本地变量表)中引用的对象。
方法区中静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI引用的对象。

另外在回收的时候程序除了 gc 在运行外其他都是暂停的。

垃圾回收算法

##1.标记清除算法 (Mark-Sweep)
标记分为标记跟清除阶段。先标记出需要回收的对象,再清除

清除前

清除后

这会带来几个问题:

  • 清除后会产生内存碎片,若碎片太多会导致大对象无法被分配可用的空间而触发gc;

##2.复制算法 (Copying)
复制算法将内存分成两块大小相等的,然后就使用其中一块,若满了就将存活对象复制到第二块内存中;
缺点很明显:内存少了一半,而且如果存活对象很多的话执行效率低

清除前

清除后

##3.标记整理算法 (Mark-Compact)
将可回收对象标记出来,然后将存活对象移到一侧,再将可回收对象全部回收。
这个算法解决了内存少的问题,但同时如果回收对象过多,效率不高,总的来说是跟上面形成了互补;

清除前

清除后

##分代回收

分代回收算法其实不算一种新的算法,而是根据复制算法和标记整理算法的的特点综合而成。这种综合是考虑到java的语言特性的。

  • 复制算法:适用于存活对象很少。回收对象多
  • 标记整理算法: 适用用于存活对象多,回收对象少

因为新生代每次垃圾回收都要回收大部分,所以采用复制算法,而老年代因为每次回收对象少采用标记整理算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 分配了一个又一个对象
放到Eden区
// 不好,Eden区满了,只能GC(新生代GC:Minor GC)了
把Eden区的存活对象copy到Survivor A区,然后清空Eden区(本来Survivor B区也需要清空的,不过本来就是空的)
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor A区的存活对象copy到Survivor B区,然后清空Eden区和Survivor A区
// 又分配了一个又一个对象
放到Eden区
// 不好,Eden区又满了,只能GC(新生代GC:Minor GC)了
把Eden区和Survivor B区的存活对象copy到Survivor A区,然后清空Eden区和Survivor B区
// ...
// 有的对象来回在Survivor A区或者B区呆了比如15次,就被分配到老年代Old区
// 有的对象太大,超过了Eden区,直接被分配在Old区
// 有的存活对象,放不下Survivor区,也被分配到Old区
// ...
// 在某次Minor GC的过程中突然发现:
// 不好,老年代Old区也满了,这是一次大GC(老年代GC:Major GC)
Old区慢慢的整理一番,空间又够了
// 继续Minor GC
// ...
// ...

0%