共计 2926 个字符,预计需要花费 8 分钟才能阅读完成。
前言
为什么须要垃圾回收
- 首先咱们来聊聊为什么会须要垃圾回收,假如咱们不进行垃圾回收会造成什么结果,咱们举一个简略的例子
- 咱们住在一个房子外面,咱们每天都在外面生存,而后垃圾都丢在房子外面,又不清扫,最初房子都是垃圾 咱们是不是就没法住上来了。
- 所以 JVM 垃圾回收机制也是一样的,当咱们创立的对象占据堆空间要满了的的时候咱们就对他进行垃圾回收,留神 java 的垃圾回收是不定时的,c 语言的是须要去调用垃圾回收办法
- 刚刚也说到 下面举的例子也说到 假如一个房子都被垃圾堆满了 那么咱们没法住人了 那么咱们是不是会通知他人这个房子没法住人了 而 java 也是如此当咱们堆空间满了的时候 此时它就会抛出异样 OutOfMemoryError(简称 OOM)
什么中央须要进行垃圾回收
刚刚咱们说了为什么要回收垃圾,和什么是 OOM 那么咱们上面就给大家介绍,咱们 JVM 中什么中央须要进行垃圾回收。
垃圾回收要思考的点
**1)是否会产生垃圾
2)哪些内存须要回收
3)什么时候回收
4)如何对他进行回收 **
程序计数器
jvm 中 惟一 一个 不须要垃圾回收 的中央。
栈 本地办法栈
这个中央会因为栈帧存满了导致内存溢出,所以须要垃圾回收
办法区 (元空间)
这个中央也须要进行垃圾回收
堆
这个中央是咱们垃圾回收最频繁的中央,咱们简直咱们所有的对象都存储在堆中,也是咱们明天要着重讲的中央
堆 GC
堆,可能大家都不生疏,可是如同又间隔咱们很远,明天它来了
从下面的图咱们能够看出,咱们的堆空间被次要被划分为了二块区域,新生代,老年代 java 堆是咱们 JVM 中治理区域最大的一块,java 堆是一个线程共享的区域,在虚拟机启动时创立,简直所有的对象都在此调配,java 虚拟机标准中有过形容,所有的实例对象以及数组都在堆中进行分配内存,但目前因为 JTI 编译器的倒退,和逃逸剖析技术的逐步欠缺,在堆中调配对象也不是那么的相对了。
堆的内存在物理上能够是不间断的,然而在逻辑上是即可。
新生代
- 咱们对象的创立到完结,简直是 ”朝生夕死“ 的一个过程差不多 90% 的对象都在新生代被回收了,所以新生代的 gc 也是产生最为频繁的一个区域。新生代产生 gc 咱们称为 Y -GC
- 每产生一次 y -gc 咱们对象的年龄就加一岁,直到 15 岁后进入老年代。当然这是失常状况,那么有没有非凡状况勒当然有
空间担保
- 当咱们创立的对象大于 Eden 的时候,此时怎么办,此时他会先产生一次 Y -GC 如果还是无奈存储下新创建的对象,那么咱们就会通过空间担保策略进入老年代。
- 还有一种状况,对象创立也会间接进入老年代,当咱们的 Surivivor 区满了的时候,此时它不会被动产生 gc 只会依赖于 Eden,但咱们的对象又不能被摈弃,所以它也被调配到了老年代
- 当然咱们理论开发工作中须要尽量的去防止这种状况的诞生
动静年龄
- 什么是动静年龄勒,这是堆中的另一个,担保策略了,它会去判断咱们 Surivivor 的区中,雷同年龄的对象大于 Surivivor 区一半的时候,那么他就会断定此时这些对象曾经可能很好的存活了,所以他们就个体被丢到老年代了
对象如何分配内存
老年代
- 咱们老年代寄存的都是一些老对象了,大对象,都是存活工夫较长的对象这里个别很少产生 FGC 这里一旦产生 FGC 那么所产生 GC 的耗时将会是 YGC 的 10 倍时耗,而咱们老年代快要存满时进入了一个对象,这时会产生一次 FGC 如果 GC 完结后,还是无奈寄存对象的话此时就会报 OOM 异样。
垃圾回收算法
分代算法
分代算法,其实也就是将咱们堆空间划分为了一个个不同的区域,新生代,老年代,不让它回收的时候对整个堆进行一个回收。缩小 GC 所进展的工夫,咱们称之为 STW (Stop The World), 假如咱们堆整个堆进行垃圾回收,是不是每次都须要去把整个堆的垃圾标记一次,十分的那么用户线程进行的工夫就十分长,你想一下,如果你的电脑每应用 1 个小时就卡 10 秒,那么你是不是十分操蛋。
标记革除
算法执行过程
堆空间垃圾清理前
垃圾革除后
算法介绍
标记革除是最开始 jvm 抉择的一种垃圾回收算法,这个算法就和他的名字一样,分为标记,和革除二个过程,首先他会标记所有须要回收的对象,标记完结后对标记的对象进行一个垃圾回收。
毛病
这种形式会有什么毛病呢!它会导致内存空间的节约,产生大佬不间断的内存碎片,当咱们须要一个间断的内存空间寄存大对象的时候,因为间断的内存空间不够,导致咱们不得又产生一次 GC,进步了咱们 GC 产生的频率。
标记整顿
算法执行过程
算法介绍
标记整顿,的执行过程,于标记革除相同,标记革除是标记须要回收的对象,而标记整顿却是标记存活的对象,而后把他们全副向一段进行位移,而后革除端边界以外的所有对象。
适用范围
老年代垃圾回收
复制算法
算法执行过程
算法介绍
复制算法也是在标记革除上的一个改良,它补救了标记革除呈现大量不间断内存碎片的毛病。它将一个可用的内存空间划分为大小相等的二块区域,每次只应用其中一块区域,当这块区域用完了就把存活的对象放到,另一块空着的区域区,而后把本人革除洁净,变成一块空着的区域。这样就解决了内存碎片的问题
毛病
那么这样的算法是不是太过于刻薄了,每次都须要一块空着的区域用于寄存对象,就义掉了大量的内存。
适用范围
新生代 Surivivor 区域
堆中对象内存的调配策略
指针碰撞
这种调配形式其实是复制算法,标记整顿中的携带的一种对象调配策略,咱们如何辨别什么是用过的,什么是没用过的,这时咱们通过一个指针,作为一个分界点指示器,那所须要调配的内存,就仅仅是把指示器指针向闲暇空间那边移动一段与对象大小相等的间隔,这种调配形式称为 ” 指针碰撞 ”(Bump the Pointer)。
闲暇列表
闲暇是标记革除中对对象调配的一个策略,因为标记革除中咱们的内存划分的随机的,已应用内存和未应用内存互相交织,那么咱们如何把他们关联起来,虚拟机针对这种交织的内存保护了一个列表,记录哪些内存块是可用的,在调配的时候找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种调配形式成为 ” 闲暇列表 ”(Free List)。
OOM 异样
其实 OOM 在下面介绍了堆内存的划分和收集过程中,大家也应该对它有了肯定的意识了,OOM 异样是产生在老年代 Old 中的一个异样,当咱们老年代中无奈在寄存对象的时候,就会报 OOM 内存溢出异样
public class HeapOomError {public static void main(String[] args) {List<byte[]> list =new ArrayList<>();
int i=0;
while (true){
try {Thread.sleep(100);
} catch (InterruptedException e){e.printStackTrace();
}
list.add(new byte[5 * 1024 * 1024]);
//System.out.println("count is:"+(++i));
}
}
}
设置堆空间的大小
最初咱们失去的后果如下
总结
总而言之咱们须要的优化的 GC 的损耗和防止内存溢出的呈现,从而进步我用户良好应用体验。
最初
感激你看到这里,看完有什么的不懂的能够在评论区问我,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!