哪些网站做平面单页好看/搜索引擎公司排名
文章目录
- JVM GC
- 1 哪些内存需要回收?
- 2 如何回收?
- 2.1 “判决”
- 2.2 ”缓刑“
- 2.3 常见垃圾收集算法
- 2.3.1 标记-清除算法(Mark-Sweep)
- 2.3.2 复制算法(Copying)
- 2.3.3 标记-整理算法(Mark-Compact)
- 2.3.4 分代收集算法(Generational Collection)
- 3 什么时候回收?
JVM GC
虽然内存分配和GC都已经“自动化”了,但内存回收和GC在很多时候都是影响系统性能、并发能力的主要因素之一,并且当我们需要排查各种内存溢出和内存泄露时,你就需要深入底层去了解JVM GC的原理了,以实施对它的监控和调节。本次主要讲解JVM GC的相关内容。
理解JVM GC的方式只需要知道三个问题就行了:
- 那些内存需要回收?
- 怎么回收?
- 什么时候回收?
1 哪些内存需要回收?
先回忆一下Java内存区域的划分,共有:虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、程序计数器(Program Counter Register)、Java堆(Heap)、方法区(Method Area)。
我们知道虚拟机栈、本地方法栈、程序计数器①所占用的内存基本上在类结构确定下来的时候就明确了,②且都是线程私有的,生命周期与线程相同。所以它们的内存分配和内存回收都是确定的,不需要我们过多的去考虑。
而Java堆、方法区则不一样,他们通常只有在程序处于运行期间才能知道到底要创建哪些对象,所以对他们的内存分配和内存回收都是动态的,所以GC主要关注的就是这两个区域。
2 如何回收?
2.1 “判决”
“判决”的目的自然是:确定哪些对象“活着”、哪些对象“死了”。
过去会采用引用计数算法(给对象添加一个引用计数器,每被引用一次就+1;引用失效则-1。当引用为0时,说明对象“死了”,则GC进行回收),但它却解决不了对象间相互循环引用的问题,而容易导致内存泄露。所以很少使用了。
现在主流的算法是——可达性分析算法,思想非常简单:①我们可以将对象看做“节点”,将对象间的引用看做“路径”,那么我们便可以依次画出连通图(图论);②紧接着只要我们找到了一系列必然存活着的“节点”作为根节点(GC Root),再向下进行搜索,就能够将可达和不可达的节点区分出来;这样就解决了对象间相互循环引用的问题,由此我们就确定出来了哪些对象“活着”,哪些对象“死了”(并不一定会马上被回收掉,进行”缓刑“)。
那么哪些对象可作为GC Root呢?
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
2.2 ”缓刑“
在可达性分析算法中被确定不可达的对象并不会马上就被回收掉,而是先执行“缓刑”,即它们还需经过至少两次标记才会被GC回收掉。
大致流程:确定没有与GC Root相连的引用链时,此对象会执行第一次标记;筛选此对象是否需要执行finalize()方法;如果是,则进入F-Quene队列,等待执行finalize()方法;否则,执行第二次标记,被回收。执行finalize()方法时,如果此对象重新与引用链上的对象建立起关联,则将其移除“即将回收”集合,存活;否则回收。
**注:**如果对象已执行过一次finalize()方法,并成功“逃脱”回收,那么第二次再执行“缓刑”的时候,则不会再执行finalize()方法了。即在这个过程中每个对象只有一次执行finalize()方法的机会。
附上流程图,以方便理解:
2.3 常见垃圾收集算法
说道算法了,可能好些同学又有些害怕了,其实完全没有必要,因为垃圾收集算法的思想都比较简单,很容易理解!(如果你学过操作系统这门课的话,这对你来说一定是小菜一碟)
2.3.1 标记-清除算法(Mark-Sweep)
顾名思义,分为两个阶段:标记、清除。先标记出来需要回收的内存对象,再进行统一回收。
(用excel画图,颜色选了好长一阵…)
作为最基础的收集算法,自然是跑不了有很明显的缺点了:
- 效率问题:标记和清除两个过程效率都不高。
- 空间问题:清除之后,会产大量不连续的碎片,下次要是来个大一点的对象不方便分配内存空间。
2.3.2 复制算法(Copying)
复制算法是为了解决标记-清除算法中的效率、空间问题出现了。它将内存空间分为大小相等的A、B两块,每次只使用其中的一块,当A块内存用完后,就将还存活的对象复制到B块,然后把A块已使用的内存空间一次清理掉。
这样便完美的解决了标记-清除算法中的效率和空间问题。但最大的缺陷是,内存空间缩小成了以前的一半大小…
2.3.3 标记-整理算法(Mark-Compact)
针对复制收集算法中存活率较高,复制次数增多导致效率降低的问题,提出了标记-整理算法。
与标记-清除算法类似,同样先标记出可回收的对象,但后续是将所有存活的对象向一端移动,然后清理掉端边界以外的内存空间。
2.3.4 分代收集算法(Generational Collection)
当前商业虚拟机的垃圾收集都采用“分代收集“算法,主要是根据对象存活的周期,将Java堆划分为新生代与老年代,再以它们各自的特点采用最适用的算法。算法同上,并没有任何实质改变。
生存周期 | 特征 | 采用算法 |
---|---|---|
新生代 | 对象存活率低 | 复制算法 |
老年代 | 对象存活率高 | 标记-清理算法、标记-整理算法 |
其中对新生代的复制算法有所优化,不再按照1:1的比例来划分内存空间,而是采用Eden(伊甸)区和两个Survivor区,内存空间大小比例为8:1:1的方式来划分。如下图:
①内存分配时,每次使用Eden和其中一块Survivor区;
②当回收时,将Eden和Survivor中还存活的对象,复制到另一块Survivor区中去。当另一块Survivor区域空间不足时,将会分配到老年代中去;
③清理掉Eden和刚才使用过的Survivor区。
3 什么时候回收?
把这个问题放在最后的原因是,弄清楚如何回收是前提。
GC分为Minor GC与Full GC,只要清楚了它们的触发条件,也就清楚了什么时候进行GC了!
- Minor GC:
- 当Eden区中没有足够空间时。
- Full GC:Full GC一般都会有一次Minor GC
- 调用System.gc时,不一定执行;
- 老年代空间不足;
- 方法区空间不足;
- 通过Minor GC后进入老年代的平均大小 > 老年代的可用内存。
- 由Eden区、From Space区向To Space区复制时,对象大小 > To Space可用内存,则把该对象转存到老年代,且老年代的可用内存 < 该对象大小。
(以上内容均整理自《深入理解Java虚拟机:JVM高级特性与最佳实践 第二版》)