-
- 1.如何判断对象可以回收
-
- 1-1 引用计数法
-
1-2 可达性分析算法
-
1-3 五种引用
-
2.垃圾回收算法
-
- 2-1 标记清除
-
2-2 标记整理
-
2-3 复制
-
3.分代垃圾回收
-
- 3-1 回收流程
-
3-2 GC 分析
-
4.垃圾回收器
-
- 4-1 串行
-
4-2 吞吐量优先
-
4-3 响应时间优先
-
4-4 G1
-
5.垃圾回收调优
-
- 5-1 调优领域
-
5-2 确定目标
-
5-3 最快的 GC
-
5-4 新生代调优
-
5-5 老年代调优
=======================================================================
本文章参考:黑马程序员JVM
1-1 引用计数法
-
当一个对象被其他变量引用,该对象计数加一,当某个变量不在引用该对象,其计数减一
-
当一个对象引用没有被其他变量引用时,即计数变为0时,该对象就可以被回收
缺点:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放
1-2 可达性分析算法
-
JVM中的垃圾回收器通过可达性分析来探索所有存活的对象
-
扫描堆中的对象,看能否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以回收
-
可以作为GC Root的对象
-
虚拟机栈(栈帧中的本地变量表)中引用的对象。
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI(即一般说的Native方法)引用的对象
-
所有被同步锁(synchronized关键字)持有的对象。
1-3 五种引用
- 强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
- 软引用
-
仅有【软引用】引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
-
可以配合【引用队列】来释放软引用自身
- 弱引用
-
仅有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
-
可以配合【引用队列】来释放弱引用自身
- 虚引用
-
必须配合【引用队列】使用,主要配合
ByteBuffer
使用,被引用对象回收时,会将【虚引用】入队, 由Reference Handler
线程调用虚引用相关方法释放【直接内存】 -
如上图,B对象不再引用
ByteBuffer
对象,ByteBuffer
就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner
放入引用队列中,然后调用它的clean
方法来释放直接内存
- 终结器引用
-
无需手动编码,但其内部配合【引用队列】使用,在垃圾回收时,【终结器引用】入队(被引用对象暂时没有被回收),再由
Finalizer
线程通过【终结器引用】找到被引用对象并调用它的finalize
方法,第二次 GC 时才能回收被引用对象 -
如上图,B对象不再引用A4对象。这时终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的
finalize
方法。调用以后,该对象就可以被垃圾回收了
软引用使用:
public class Demo1 {
public static void main(String[] args) {
final int _4M = 410241024;
//使用软引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
}
}
软引用_引用队列使用:
public static void main(String[] args) throws IOException {
///使用引用队列,用于移除引用为空的软引用对象
ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
//使用软引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己就会加入到引用队列queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
//获取队列中第一个软引用对象
Reference<? extends byte[]> poll = queue.poll();
//遍历引用队列,如果有元素,则移除
while(poll!=null){
list.remove(poll);
poll=queue.poll();
}
System.out.println(“=============”);
System.out.println(“循环结束:” + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
弱引用使用:
弱引用的使用和软引用类似,只是将 SoftReference
换为了 WeakReference
public static void main(String[] args) {
//使用弱引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是弱引用
List<WeakReference<byte[]>> list=new ArrayList<>();
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> ref=new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> w : list) {
System.out.print(w.get()+" ");
}
System.out.println();
}
System.out.println(“循环结束:”+list.size());
}
2-1 标记清除
定义:在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间
注意:这里的清除并不是将内存空间字节清零,而是记录这段内存的起始地址,下次分配内存的时候,会直接覆盖这段内存。
优点:速度快
缺点:容易产生内存碎片。一旦分配较大内存的对象,由于内存不连续,导致无法分配,最后就会造成内存溢出问题
2-2 标记整理
定义:在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后整理剩余的对象,将可用的对象移动到一起,使内存更加紧凑,连续的空间就更多。
优点:不会有内存碎片
缺点:速度慢
2-3 复制
定义:将内存分为等大小的两个区域,FROM和TO(TO中为空)。将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间。
优点:不会有内存碎片
缺点:会占用双倍的内存空间。
将堆内存分为新生代和老年代,新生代有划分为伊甸园,幸存区To,幸存区From。
3-1 回收流程
对象首先分配在伊甸园区域
新生代空间不足时,触发 Minor GC,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to
再次创建对象,若新生代的伊甸园又满了,则会再次触发 Minor GC(minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行),这时不仅会回收伊甸园中的垃圾,还会回收幸存区中的垃圾,再将活跃对象复制到幸存区TO中。回收以后会交换两个幸存区,并让幸存区中的对象寿命加1
当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
当老年代空间不足,会先尝试触发Minor GC,如果之后空间仍不足,那么触发 Full GC,stop the world的时间更长
3-2 GC 分析
相关VM参数
| 含义 | 参数 |
| — | — |
| 堆初始大小 | -Xms |
| 堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
| 新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
| 幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
| 幸存区比例 | -XX:SurvivorRatio=ratio |
| 晋升阈值 | -XX:MaxTenuringThreshold=threshold |
| 晋升详情 | -XX:+PrintTenuringDistribution |
| GC详情 | -XX:+PrintGCDetails -verbose:gc |
| FullGC 前 MinorGC | XX:+ScavengeBeforeFullGC |
大对象处理策略
遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代
/**
- 演示内存的分配策略
*/
public class Main {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;
// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
public static void main(String[] args) throws InterruptedException {
ArrayList<byte[]> list=new ArrayList<>();
list.add(new byte[_8MB]);
}
}
线程内存溢出
某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行。这是因为当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常
/**
- 演示内存的分配策略
*/
public class Main {
private static final int _512KB = 512 * 1024;
private static final int _1MB = 1024 * 1024;
private static final int _6MB = 6 * 1024 * 1024;
private static final int _7MB = 7 * 1024 * 1024;
private static final int _8MB = 8 * 1024 * 1024;
// -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc -XX:-ScavengeBeforeFullGC
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
ArrayList<byte[]> list = new ArrayList<>();
list.add(new byte[_8MB]);
list.add(new byte[_8MB]);
}).start();
//主线程还是会正常执行
System.out.println(“sleep…”);
Thread.sleep(1000L);
}
}
相关概念:
-
并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态
-
并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上
-
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 )),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
4-1 串行
-
单线程
-
堆内存小,适合个人电脑
开启串行回收器:
XX:+UseSerialGC = Serial + SerialOld
,新生代**-Serial** ,老年代-SerialOld
安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象。
阻塞:因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态
Serial 收集器:
-
定义:Serial收集器是最基本的、发展历史最悠久的收集器
-
特点:单线程收集器。采用复制算法。工作在新生代
Serial Old收集器:
-
定义:Serial Old是Serial收集器的老年代版本
-
特点:单线程收集器。采用标记-整理算法。工作在老年代
4-2 吞吐量优先
-
多线程
-
堆内存较大,多核cpu
-
让单位时间内暂停时间(STW)最短
-
JDK1.8默认使用的垃圾回收器
开启吞吐量优先回收器:
Parallel 收集器:
-
定义:与吞吐量关系密切,故也称为吞吐量优先收集器
-
特点:并行的,工作于新生代,采用复制算法
Parallel Old 收集器:
-
定义:是Parallel 收集器的老年代版本
-
特点:并行的,工作与老年代,采用标记-整理算法
4-3 响应时间优先
-
多线程
-
堆内存较大,多核cpu
-
尽可能让单次的暂停时间(STW)最短
-
初始标记:标记GC Roots能直接到的对象。速度很快,存在Stop The World
-
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
-
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。存在Stop The World
-
并发清理:对标记的对象进行清除回收
CMS收集器:
-
定义:Concurrent Mark Sweep(并发,标记,清除)
-
特点:基于标记-清除算法的垃圾回收器。是并发的。工作在老年代。
ParNew 收集器:
-
定义:ParNew收集器其实就是Serial收集器的多线程版本
-
特点:工作在新生代,基于复制算法的垃圾回收器。
4-4 G1
定义:Garbage First
- JDK 9以后默认使用,而且替代了CMS 收集器
适用场景:
-
同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
-
超大堆内存,会将堆划分为多个大小相等的 Region
-
整体上是 标记+整理 算法,两个区域之间是 复制 算法
相关参数:JDK8 并不是默认开启的,所需要参数开启
垃圾回收阶段:
新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>混合收集,回收新生代伊甸园、幸存区、老年代内存——>新生代伊甸园垃圾回收(重新开始)
Young Collection:
存在Stop The World
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习
还有更多学习笔记面试资料也分享如下:
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-LeGvXtER-1713298154769)]
[外链图片转存中…(img-D1N2uUwJ-1713298154769)]
[外链图片转存中…(img-l6XLaiON-1713298154769)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
总而言之,面试官问来问去,问的那些Redis知识点也就这么多吧,复习的不够到位,知识点掌握不够熟练,所以面试才会卡壳。将这些Redis面试知识解析以及我整理的一些学习笔记分享出来给大家参考学习
还有更多学习笔记面试资料也分享如下:
[外链图片转存中…(img-1zRVWhQM-1713298154769)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!