??写在前面
这里是温文艾尔の学习之路 - 👍如果对你有帮助,给博主一个免费的点赞以示鼓励把QAQ
- 👋博客主页🎉 温文艾尔の学习小屋
- ??更多文章👨?🎓请关注温文艾尔主页📝
- 🍅文章发布日期:2021.12.29
- 👋java学习之路!
- 欢迎各位🔎点赞👍评论收藏??
- 🎄新年快乐朋友们🎄
- 👋jvm学习之路!
文章笔记参考黑马程序员
??1.如何判断对象可以回收
??1.1引用计数法
一个对象收到引用时计数+1 ,一个对象失去引用时计数-1 ,一个对象的引用计数为0时,该对象被当作垃圾进行回收 但是这样会有一个问题
循环引用 当A对象和B对象相互引用时,他们的引用计数都是1,但是他们却没有被其他对象所使用,这样因为他们的引用计数不能归0导致两个对象不能作为垃圾被回收,导致内存泄漏
??1.2可达性分析算法
此算法为java虚拟机采用的一种垃圾回收算法,首先要确定一个肯定不会被当成垃圾回收的对象GC Root(根对象)
- java虚拟机中的垃圾回收器采用
可达性分析 来探索所有存活的对象 - 扫描堆中的对象,看是否能够
沿着GC Root对象为起点的引用链找到该对象 ,找不到,表示可以回收
可以作为GC Root的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态变量引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
??2.五种引用
- 强引用
- 软引用
- 弱引用
- 虚引用
- 终结器引用
下面我们分别进行描述
??2.1强引用
我们平时所使用的引用便是强引用,比如我们new一个对象A,把这个对象通过等号(赋值运算符)赋值给一个变量m,那么就称这个变量m强引用了刚刚的对象A
强引用的特点:
只要沿着GC root的引用链能够找到该对象,他就不会被垃圾回收,就比如上图中的C对象能找到A1对象,那么A1对象就不能被回收,当B对象和C对象对A1对象的引用都断开时,A1对象才能被垃圾回收
??2.2 软引用
如图,A2对象使用过软引用对象被C对象间接引用到,只要满足垃圾回收时,并且回收完内存也不够时,那么软引用所引用的A2对象就会被释放掉
所以软引用引用对象被释放 的条件是
- 没有强引用对象引用它
- 发生垃圾回收
- 垃圾回收之后内存不够
软引用的应用案例
public static void soft(){
ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:"+list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
list先引用软引用对象,软引用对象间接的引用byte数组
list和SoftReference之间是强引用,SoftReference和byte数组之间是软引用
内存充足的时候软引用引用的对象保留,但是当内存不足的时候,软引用引用的对象就会被清除
??2.3 弱引用
和软引用的区别是,当弱引用引用该对象时,发生垃圾回收不管内存是否充足,弱引用所引用的对象都会被回收
public class Demo03 {
private static final int _4MB = 4*1024*1024;
public static void main(String[] args) throws IOException {
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> w: list){
System.out.println(w.get()+"");
}
System.out.println();
}
System.out.println("循环结束:"+list.size());
}
}
通过list集合引用WeakReference,通过WeakReference间接的引用byte[]数组,这样进行垃圾回收,就会尝试把WeakReference运用的byte数组所占用的内存做释放
??引用队列
当软引用所引用的对象或弱引用引用的对象被清除之后,软引用和弱引用本身并不会消失,软引用和弱引用本身也是一个对象,此时软引用和弱引用会进入引用队列
软引用和弱引用自身也要占用一定内存,如果想释放软引用和弱引用的内存,需要使用引用队列找到它们,对其做进一步的处理
引用队列操作代码
public class Demo02 {
private static final int _4MB = 4*1024*1024;
public static void main(String[] args) throws IOException {
ArrayList<SoftReference<byte[]>> list = new ArrayList<>();
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
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();
}
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
关联软引用对象和引用队列,当软引用所关联的byte[]被回收时,软引用自己会加入到引用队列queue中去,之后查看引用队列中是否有软引用,有就将其移出
??2.4虚引用
在ByteBuffer 被回收的时候,他分配的直接内存并不能被java的垃圾回收管理,所以我们将虚引用对象Cleaner进入引用队列 ,虚引用所在队列会有一个叫RefrenceHandlel 的线程,来定时到这个引用队列中找新入队的Cleaner,如果有就调用Cleaner中的clean 方法,根据记录的直接内存的地址调用Unsafe.freeMemary 方法来清理直接内存
??2.5终结器引用
所有的java对象都继承自Object 类,Object类有finallize() 终结方法,当对象重写了终结方法并且没有强引用,他就可以被当成垃圾回收,终结方法的调用依靠终结器引用
如上图当A4对象被垃圾回收时,在A4对象被真正清除 之前,终结期引用进入引用队列,再由一个优先级很低的线程(finallizeHandler) 查看引用队列中的终结器引用,通过终结器引用找到将要垃圾回收的对象,调用对象的finallize 方法,调用之后,该对象就可以被垃圾回收了
??2.6引用总结
??3.垃圾回收算法
??3.1标记清除
第一个阶段:标记 顺着GC Root查找没有引用的对象,将其进行标记
第二个阶段:清除 将垃圾对象所占用的空间释放
释放不意味着将每一个字节进行清理操作,清除只是把对象所占用内存的起始与结束地址记录下来,放在一个较空闲的地址链表里,下次分配内存的时候会直接覆盖这部分内存
标记清除算法的优点:
标记清除算法的缺点:
- 容易产生内存碎片,无法满足大对象的内存分配,造成内存溢出问题
??3.2标记整理
第一步:标记,和标记删除算法相同 顺着GC Root查找没有引用的对象,将其进行标记 第二步:整理 清除垃圾的过程中,它会把可用的对象向前移动,使内存更为紧凑,连续的空间更多
优点:
缺点
??3.3复制
将内存区域化成大小相等的两块区域,首先将没有被引用的对象进行标记,然后将from区存活的对象复制到to区,复制的过程中完成碎片的整理
复制完成后清空from区,并且交换from和to
优点
缺点
??4分代垃圾回收
在java中长时间使用的对象就会放在老年代当中(垃圾回收耗时较长。且频率低),用完就会丢弃的对象就会被放入新生代(垃圾回收比较频繁),这样可以针对对象生命周期的不同特点进行不同的垃圾回收策略
分代垃圾回收的流程
当我们创建一个新的对象时,新对象默认采用伊甸园的空间 当伊甸园中存放的对象达到上限,新产生的对象无处安放就会触发一次垃圾回收,我们称为Minor GC 随后沿GC Root引用链,标记未引用的对象,随后采用我们上面说到的复制算法,将存活的对象复制到幸存区To中,并使其寿命+1
回收伊甸园中的对象,随后交换幸存区From和幸存区To的位置 此后产生的新对象会再次放入伊甸园 当又经过了一段时间,伊甸园又满了,就会触发第二次垃圾回收,第二次垃圾回收除了要把伊甸园中存活的对象找到以外,还会寻找幸存区中继续存活的对象,第二次垃圾回收会把伊甸园中幸存的对象转移到幸存区To中
并且该对象寿命+1,原from幸存区中上一次存活的对象转移到to中,并且寿命+1
回收伊甸园和幸存区中剩余的的垃圾对象 交换幸存区From和幸存区To 随后产生的新对象就可以放入伊甸园 幸存区中的对象会有一个默认阈值,例如默认阈值为15次 一旦达到15次,且对象依然存活,我们就认为该对象价值较高,该对象就会晋升到老年代中 如果新生代和老年代内存都达到极限 会先触发一次minor gc,如果此后空间仍不足,会触发一次Full GC,从新生代到老年代都会做一次清理,清理回收新生代和老年代中所有无用对象
大对象处理策略
当向伊甸园内添加一个非常大的对象时,达到即使伊甸园为空也无法容纳该对象,那么这个对象将直接晋升至老年代
??4.1分代垃圾回收总结
- 对象首先分配在伊甸园区域
-
- 伊甸园空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄+1,
并且交换from,to
- minor gc会引发stop the world,暂停其他用户的线程,等垃圾回收结束,用户线程才恢复运行
-
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15次(4bit)
-
- 当老年代空间不足,会先尝试触发minor gc,如果之后空间仍不足,那么触发full gc,STW的时间更长
|