1.概述
1.1 为什么需要去了解GC和内存分配?
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些技术进行必要的监控和调节。
1.2 我们需要关心那部分的内存?
Java堆和方法区的内存。
因为一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才知道会创建哪些对象,这部分内存的分配就是我们需要关心的部分。
2.对象回收策略
2.1 引用计数法
给对象添加一个引用计数器,每当有一个地方引用时,计数器就加1;当引用失效,计数器就减1;当计数器数值为0时这个对象就是不再被使用的。
但是JAVA虚拟机并没有用此方式,因为它很难解决对象之间互相循环引用的问题。
2.2 可达性分析算法
这个算法的基本思路就是通过一系列的成为“GC Roots”的对象为起始点,从这些节点开始向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连接时候,则证明此对象是不可用的。
JAVA语言中,可作为GC Roots的对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(一般说的Native方法)引用的对象
2.3 引用四种概念
- 强引用----类似new Object()这类引用,垃圾收集器不会回收
- 软引用----描述一些还有用但非必须的对象。对于此类,在系统将要发生内存溢出前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还是没有足够内存,则抛出内存溢出异常。SoftReference类可以实现软引用
- 弱引用----被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当时内存是否足够,都会回收此类对象。 WeakReference类来实现弱引用
- 虚引用----设置虚引用关联的唯一目的就是能在这个对象被收集器回收时能收到一个系统通知。 PhantomReference实现虚引用
3.垃圾收集算法
3.1 标记-清除算法(Mark-Sweep)
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
不足:一个是效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
3.2 复制算法
将可用内存按容量划分为大小相等的两块,每次只用其中的一块。当这一块的内存用完了,就将还存在的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。(通常用来回收新生代)
不足:将内存缩小为原来的一半。 当对象存活率较高时,要进行较多次数的复制,效率变低。
目前新生代垃圾收集都采用这种算法。
新生代的对象98%是“朝生夕死”的,所以不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSport虚拟机默认Eden和Survivor的大小比例是8:1.
3.3 标记-整理
标记过程和“标记-清除”算法一样,但是后续的操作变成让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
3.4 分代收集算法
根据对象存活周期的不同,将内存分为几块。一般是把JAVA堆分为新生代和老年代,然后根据各个年代特点采用最适当的收集算法。
新生代就用“复制算法” 老年代“标记-整理”
图片引用---https://blog.csdn.net/wuzhiwei549/article/details/80563134