三垃圾回收算法
1哪些变量引用的对象是不能回收的?
JVM中使用了一种可达性分析算法来判定哪些对象是可以被回收的,哪些对象是不可以被回收的。
这个算法的意思,就是说对每个对象,都分析一下有谁在引用他,然后一层一层往上去判断,看是否有一个GC Roots。
2哪些可以作为GC Roots的对象
- 虚拟机(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)中引用的对象
3.Java中对象不同的引用类型
- 强引用
- 软引用
软引用就是说有的对象可有可无,如果内存实在不够了,可以回收他。 - 弱引用
发生垃圾回收,就会把这个对象回收掉。 - 虚引用
4.finalize()方法的作用
有GC Roots引用的对象不能回收,没有GC Roots引用的对象可以回收,如果有GC Roots引用,但是如果是软引用或者弱引用的,也有可能被回收掉。
-
回收的环节了,假设没有GC Roots引用的对象,是一定立马被回收吗?
不是的,这里有一个finalize()方法可以拯救他自己,假如这个对象重写了Object类中的finialize()方法,此时会先尝试调用一下他的finalize()方法,看是否把自己这个实例对象给了某个GC Roots变量,如果重新让某个GC Roots变量引用了自己,那么就不用被垃圾回收了
5.一种不太好的垃圾回收思路,标记清除算法
直接对被使用的那块内存区域中的垃圾对象进行标记。标记出哪些对象是可以被垃圾回收的,然后就直接对那块内存区域中的对象进行垃圾回收,把内存空出来。
垃圾回收后,保留了一些被人引用的存活对象,存活对象在内存区域里东一个西一个,非常的凌乱,而且造成了大量的内存碎片,内存碎片太多会造成内存浪费。
6.一个合理的垃圾回收思路,复制算法
真正的复制算法会把新生代内存区域划分为三块,1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存,8:1:1默认
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bQvm8v61-1572926008971)(/Users/laiyanxin/Library/Application Support/typora-user-images/image-20191028214245387.png)]
- 刚开始对象都是分配在Eden区内的,如果Eden区快满了,此时就会触发垃圾回收,
- 会把Eden区中的存活对象都一次性转移到一块空着的Survivor区。接着Eden区就会被清空,然后再次分配新对象到Eden区里,然后就会如上图所示,Eden区和一块Survivor区里是有对象的,其中Survivor区里放的是上一次Minor GC后存活的对象。
- 接着新对象继续分配在Eden区和另外那块开始被使用的Survivor区,然后始终保持一块Survivor区是空着的,就这样一直循环使用这三块内存区域。
这么做最大的好处,就是只有10%的内存空间是被闲置的,90%的内存都被使用上了
无论是垃圾回收的性能,内存碎片的控制,还是说内存使用的效率,都非常的好。
7什么时候进入老年代
-
默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,他就会转移到老年代里去。可以通过JVM参数“-XX:MaxTenuringThreshold”来设置,默认是15岁
-
动态对象年龄判断
另外一个规则可以让对象进入老年代,不用等待15次GC过后才可以。大致规则就是,假如说当前放对象的Survivor区域里,一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代了,际这个规则运行的时候是如下的逻辑:年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。
-
大对象直接进入老年代
有一个JVM参数,就是“-XX:PretenureSizeThreshold”,可以把他的值设置为字节数,比如“1048576”字节,就是1MB,创建一个大于这个大小的对象就直接把这个大对象放到老年代里去
8Minor GC后的对象太多无法放入Survivor区怎么办?
这个时候就必须得把这些对象直接转移到老年代去
9年代空间分配担保规则
如果新生代里有大量对象存活下来,确实是自己的Survivor区放不下了,必须转移到老年代去
那么如果老年代里空间也不够放这些对象呢?这该咋整呢?
1. 首先,在执行任何一次Minor GC之前,JVM会先检查一下老年代可用的可用内存空间,是否大于新生代所有对象的总大小,
2. 如果说发现老年代的内存大小是大于新生代所有对象的,此时就可以放心大胆的对新生代发起一次Minor GC了,因为即使Minor GC之后所有对象都存活,Survivor区放不下了,也可以转移到老年代去。
3. 假如执行Minor GC之前,发现老年代的可用内存已经小于了新生代的全部对象大小了,就会看一个“-XX:-HandlePromotionFailure”的参数是否设置了
4. 如果有这个参数,那么就会继续尝试进行下一步判断。下一步就是看看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小。平均都有10MB左右的对象会进入老年代,那么此时老年代可用内存大于10MB。这就说明,很可能这次Minor GC过后也是差不多10MB左右的对象会进入老年代,此时老年代空间是够的
5. 如果上面那个步骤判断失败了,或者是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接触发一次“Full GC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。
6. 如果上面两个步骤都判断成功了,那么就是说可以冒点风险尝试一下Minor GC。此时进行Minor GC有几种可能。
第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor区域即可。
第二种可能,Minor GC过后,剩余的存活对象的大小,是大于 Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
第三种可能,很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触发一次“Full GC”。
如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的“OOM”内存溢出了
10老年代垃圾回收算法
老年代采取的是标记整理算法,这个过程说起来比较简单,首先标记出来老年代当前存活的对象,这些对象可能是东一个西一个的,接着会让这些存活对象在内存里进行移动,把存活对象尽量都挪动到一边去,让存活对象紧凑的靠在一起,避免垃圾回收过后出现过多的内存碎片,然后再一次性把垃圾对象都回收掉
老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍
11JVM的运行原理
所谓JVM优化,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。
12真实实战
假如有个系统是日处理数据量在上亿的规模,生产环境部署了多台机器,每台机器大概每分钟负责执行100次数据提取和计算的任务,每次会提取大概1万条左右的数据到内存里来计算,平均每次计算大概需要耗费10秒左右的时间
每台机器是4核8G的配置,JVM内存给了4G,其中新生代和老年代分别是1.5G的内存空间
这里每条数据都是比较大的,大概每条数据包含了平均20个字段,可以认为平均每条数据在1KB左右的大小。那么每次计算任务的1万条数据就对应了10MB的大小。
如果新生代是按照8:1:1的比例来分配Eden和两块Survivor的区域,那么大体上来说,Eden区就是1.2GB,每块Survivor区域在100MB左右。
基本上按照这个内存大小而言,大家会发现,每次执行一个计算任务,就会在Eden区里分配10MB左右的对象,那么一分钟大概对应100次计算任务,其实基本上一分钟过后,Eden区里就全是对象,基本就全满了(100MB*10=1000M),新生代里的Eden区,基本上1分钟左右就迅速填满了。
13垃圾回收器简介
在新生代和老年代进行垃圾回收的时候,都是要用垃圾回收器进行回收的,不同的区域用不同的垃圾回收器。
**Serial和Serial Old垃圾回收器:**分别用来回收新生代和老年代的垃圾对象
工作原理就是单线程运行,垃圾回收的时候会停止我们自己写的系统的其他工作线程,让我们系统直接卡死不动,然后让他们垃圾回收,这个现在一般写后台Java系统几乎不用。
**ParNew和CMS垃圾回收器:**ParNew现在一般都是用在新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发的机制,性能更好,现在一般是线上生产系统的标配组合
**G1垃圾回收器:**统一收集新生代 和老年代,采用了更加优秀的算法和设计机制,
14Stop the World
VM的痛点:Stop the World,因为在垃圾回收的时候,尽可能要让垃圾回收器专心致志的干工作,不能随便让我们写的Java系统继续对象了,所以此时JVM会在后台直接进入“Stop the World”状态.也就是说,他会直接停止我们写的Java系统的所有工作线程,让我们写的代码不再运行!然后让垃圾回收线程可以专心致志的进行垃圾回收的工作.
15到底什么时候会尝试触发Minor GC?
当新生代的Eden区和其中一个Survivor区空间不足时。
触发MinorGC情况有:
- 新生代现有存活对象小于老年代剩余内存 ,即老年空间代足以支撑可能晋升的对象
- 情况1不成立,查看设置了空间担保且可以担保成功
16触发Minor GC之前会如何检查老年代大小,涉及哪几个步骤和条件?
- 先判断新生代中所有对象的大小是否 小于 老年代的可用区域 true 则 触发Minor GC,false则继续进行下面2中的判断
- 如果设置了-XX:HandlePromotionFailure这个参数,那么进入第3步 如果没有设置-XX:HandlePromotionFailure参数,那么触发Full GC
- 判断Minor GC历次进入老年代的平均大小是否 小于 老年代的可用区域 true 则触发Minor GC,false则会触发Full GC
17什么时候在Minor GC之前就会提前触发一次Full GC?
当判断 新生代历次进入老年代对象的平均大小 大于 老年代的可用区域就会触发一次Full GC,让老年代腾出一些空间,腾出空间后再进行Minor GC。
18Full GC的算法是什么?
标记整理算法,速度很慢
19Minor GC过后可能对应哪几种情况?
(1)小于Survivor区域,进入Survivor区域
(2)大于survivor区域,小于老年代可用空间,进入老年代
(3)大于survivor区域,大于老年代可用空间,进行FullGC,如果FullGC后,老年代可用空间仍小于存活对象,抛出OOM
20哪些情况下Minor GC后的对象会进入老年代?
经过15次(默认,可以设置)MinorGC的
某个年龄的对象大于survivor区域的