C 语言申请内存:malloc;释放内存:free
C++ 申请内存:new;释放内存:delete
Java 申请内存: new;释放内存:自动回收
自动内存回收,编程上简单,系统不容易出错;手动释放内存,容易出两种类型的问题:
- 忘记回收
- 多次回收(delete 可能封装在各种条件语句中,可能多次 delete)
什么是垃圾?
没有任何引用指向的一个对象或者多个对象。
给每个对象分配一个计数器,当有一个引用指向这个对象时,计数器加 1;当指向该对象的一个引用失效时,计数器减 1。当该对象的计数器为 0 时,该对象就会被回收。
缺点:不能解决循环引用的垃圾回收问题。
以根对象(GC Roots)作为起点,向下搜索根对象所连接的目标对象,称这些对象是从根可达的(这些对象就是内存中存活的对象)。剩下的那些不可达的对象就是垃圾,要被回收。
根对象:线程栈变量、静态变量、常量池、JNI指针(调用C、C++本地方法创建的变量)
如下图,o1、o2、o3、o4 是根可达的,o5、o6、o7 不是根可达的,o5、o6、o7 将会被回收。
两个阶段:
先对需要回收的对象进行标记,然后在下一阶段把这些对象清除。
存在的问题:位置不连续,容易产生碎片。
将内存一分为二(假设分为 s0 和 s1),每次只使用其中的一块。当 s1 的内存用完之后,就把 s1 中存活的对象复制到 s2,然后对 s1 进行回收;当 s2 的内存用完之后,再把 s2 中存活的对象复制到 s1,对 s2 进行回收… …
不会产生碎片,并且简单高效,但是浪费了内存空间。
在标记清除的过程中做了一次压缩整理(把存活的对象向前复制,挪到一起)。如下图:
一方面,这样不会产生内存碎片;另一方面,把空闲空间整理在一起,能够为大对象分配足够的内存。当然,效率要比前两种低一些。
Java 将堆内存分为三部分:新生代(new 或者 young)+ 老年代(old)+ 永久代(permanent),其中新生代又进一步划分为 Eden、S0(Survivor0)、S1(Survivor1) 三个区。如下图:
新生代和老年代的内存比例为 1 : 2 或者 1 : 3, Eden、S0、S1 三个区的比例为 8 : 1 : 1。结构如下图所示:
其中,survivor 区使用 Copying 算法进行垃圾回收,old 区使用 Mark-Compact 进行垃圾回收。
新生代垃圾回收过程:
老年代:
垃圾回收器的发展路线,是随着内存越来越大的过程而演进,从分代算法演化到不分代算法。
- Serial 算法 几十兆
- Parallel 算法 几个 G
- CMS 算法 几十个 G
- G1 上百个 G
- ZGC - Shenandoah 4T
下图列出了常见的十种垃圾收集器。其中新生代垃圾收集器有 Serial、ParNew 和 Parallel Scavenge 收集器,老年代垃圾收集器有 Serial Old、Parallel Old 和 CMS 收集器,从 G1 开始往后的收集器是目前最新的收集器,已经开始不分代。
收集器常用的三种组合如下图:
JDK 1.8 默认使用的是 Parallel Scavenge + Parallel Old,简称 PS + PO,又称 ParallelGC。
(1)Serial 收集器(串行回收)
Serial 收集器作用于新生代,是一个单线程收集器,基于 Copying 算法实现。在进行垃圾回收时仅使用单条线程回收并且在回收过程中会暂停所有的用户线程(Stop-the-World,即 STW)。过程如下图:
简单描述:
程序正常运行——》一定程度后,eden 区满,触发 YGC——》这时候,停下所有的用户线程(stop-the-world)——》进行单线程垃圾回收——》回收完成之后,用户线程继续执行
(2)Parallel Scavenge 收集器(并行回收)
Parallel Scavenge 收集器同样作用于新生代。相比于 Serial 收集器,它在垃圾回收时也会暂停所有的用户线程,但在垃圾回收时使用多个线程同时进行垃圾回收。
(3)ParNew 收集器
ParNew 收集器和 Parallel Scavenge 收集器一样,也是一个多线程收集器,也存在 STW。区别在于,ParNew 可以配合 CMS 使用。
(1)Serial Old 收集器
Serial Old 收集器作用于老年代,采用单线程和 Mark-Compact 算法实现垃圾回收。在垃圾回收时同样会暂停所有的用户线程,造成应用的卡顿。
一般来说,老年代的容量都比新生代的大,所以当老年代进行垃圾回收时,STW 所用的时间会比新生代所用的时间长得多。
(2)Parallel Old 收集器
Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,采用多线程和 Mark-Compact 算法来实现垃圾回收。这个收集器主要是为了配合 Parallel Scavenge 收集器的使用,即当新生代选择 Parallel Scavenge 收集器时,老年代就选择 Parallel Old 收集器(这就是 JDK 1.8 默认使用的垃圾回收器组合,即 PS + PO)。
(3)CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一款承上启下的老年代垃圾收集器,实现了垃圾回收和用户线程的并发进行,大大降低了 STW 的时间。
CMS 只在初始化标记和重新标记阶段需要 STW,其它阶段中垃圾回收线程和用户线程可以并发进行,不必暂停用户线程,也就不会造成应用的停顿。
CMS 垃圾回收主要分四个阶段:初始标记——>并发标记——>重新标记——>并发清理。图示如下: