1.如何判断对象可以被回收
1.1引用计数法
只要一个对象被其他变量所引用,让对象的技术加1。如果不再引用,那么就让他的计数减一。引用计数变为0时,当作垃圾回收。
但是像这种循环引用的,其引用计数不能归零,无法垃圾回收。
1.2 可达性分析算法
所以所有的可达性算法都会有起点,那么这个起点就是GC Root。
也就是需要通过GC Root 找出所有活的对象,那么剩下所有的没有标记的对象就是需要回收的对象。
首先要确定一系列根对象(那些肯定不能当成垃圾的被回收的对象)
垃圾回收之前对堆内存中的所以对象进行一遍扫描,看看每一个对象是否被根对象引用,如果是就不能被回收,反之可以回收。
- 利用可视化工具找GC root
工具:Memory Analyzer(MAT)
1.查看进程id
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020092810120 - List item
2978.png#pic_center)
2.抓取堆内存快照
什么对象可以作为GC root的对象?
- a.java虚拟机栈中的引用的对象
-
b.方法区中的类静态属性引用的对象(一般指被static修饰的对象,加载类的时候就加载到内存中。)
-
c.方法区中的常量引用的对象
-
d.本地方法栈中的JNI(native方法)引用的对象
-
注意
1.3四种引用
1.1分类
强引用 弱引用 虚引用 终结器引用
-
强引用:只要能够通过GC Root的引用链找到就不会被垃圾回收,也就是说只有所有的GC Roots对象都不通过强引用引用该对象的时候,该对象才能被垃圾回收
-
弱引用:如果某个对象与弱引用关联且无强引用,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。
-
软引用:java中使用SoftRefence来表示软引用,如果某个对象与软引用关联(前提是没有强引用引用他的时候),那么JVM只会在内存不足的情况下回收该对象
-
虚引用:相当于无引用,使对象无法被使用,必须与引用队列配合使用创建的时候会关联一个引用队列
例如:创建ByteBuffer的时候会创建一个名为Cleaner的虚引用对象,当ByteBuffer没有被强引用所引用就会被jvm垃圾回收,虚引用Cleaner就会进入引用队列,会有专门的线程扫描引用队列,被发现后会调用直接内存地址的方法将直接内存释放掉,保证直接内存不会导致内存泄漏
是所有引用类型中最弱的,一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例。 -
终结器引用:创建的时候会关联一个引用队列,当A4对象没有被强引用所引用时,A4被垃圾回收的时候,会将终结器引用放入到一个引用队列(被引用对象暂时还没有被垃圾回收),有专门的线程(优先级较低,可能会造成对象迟迟不被回收)扫描引用队列并调用finallize()方法,第二次GC的时候才能回收掉被引用对象。
这也就是我们为什么不推荐finallize()方法释放资源的理由
实例:
1.强引用的使用
设置JVM的内存为20M -Xmx20M
2.软引用:
一些不重要的资源我们可以用软引用去关联它,
输入jvm指令:
public class Test2 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) throws IOException {
soft();
}
public static void soft(){
List<SoftReference<byte[]>> list=new ArrayList<>();
for(int i=0;i<5;i++){
SoftReference<byte[]> reference=new SoftReference<>(new byte[_4MB]);
System.out.println(reference.get());
list.add(reference);
System.out.println(list.size());
}
System.out.println("循环结束:"+list.size());
for(SoftReference<byte[]> reference:list){
System.out.println(reference.get());
}
}
}
运行结果
[B@1b6d3586
1
[B@4554617c
2
[B@74a14482
3
[GC (Allocation Failure) [PSYoungGen: 1744K->488K(6144K)] 14032K->12932K(19968K), 0.0427814 secs] [Times: user=0.00 sys=0.00, real=0.05 secs]
[B@1540e19d
4
[GC (Allocation Failure) --[PSYoungGen: 4696K->4696K(6144K)] 17140K->17140K(19968K), 0.0030222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 4696K->4574K(6144K)] [ParOldGen: 12444K->12414K(13824K)] 17140K->16988K(19968K), [Metaspace: 3118K->3118K(1056768K)], 0.0095409 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) --[PSYoungGen: 4574K->4574K(6144K)] 16988K->16996K(19968K), 0.0010492 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 4574K->0K(6144K)] [ParOldGen: 12422K->587K(8704K)] 16996K->587K(14848K), [Metaspace: 3118K->3118K(1056768K)], 0.0076471 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[B@677327b6
5
循环结束:5
null
null
null
null
[B@677327b6
Heap
PSYoungGen total 6144K, used 4546K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)
eden space 5632K, 80% used [0x00000000ff980000,0x00000000ffdf0940,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 8704K, used 587K [0x00000000fec00000, 0x00000000ff480000, 0x00000000ff980000)
object space 8704K, 6% used [0x00000000fec00000,0x00000000fec92d20,0x00000000ff480000)
Metaspace used 3224K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 349K, capacity 388K, committed 512K, reserved 1048576K
可以看出,在第四次和第五次的时候就触发了垃圾回收,在对集合进行遍历后只有第五个软引用对象存在,也就是说在内存不足的时候将前面的虚引用的对象进行了垃圾回收
引用队列:
public class Test2 {
private static final int _4MB=4*1024*1024;
public static void main(String[] args) throws IOException {
List<SoftReference<byte[]>> list=new ArrayList<>();
//引用队列
ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
for(int i=0;i<5;i++){
//关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己会加入到引用队列中
SoftReference<byte[]> reference=new SoftReference<>(new byte[_4MB],queue);
System.out.println(reference.get());
list.add(reference);
System.out.println(list.size());
}
//从队列获取无用的软引用对象并移除
Reference<? extends byte[]> poll=queue.poll();
while(poll!=null){
list.remove(poll);
poll=queue.poll();
}
System.out.println("循环结束");
for(SoftReference<byte[]> reference:list){
System.out.println(reference.get());
}
}
}
运行结果:
2.弱引用
public class Test2 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) throws IOException {
//list--> WeakReference-->byte[]
List<WeakReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
WeakReference<byte[]> reference = new WeakReference<>(new byte[_4MB]);
list.add(reference);
for (WeakReference<byte[]> w : list) {
System.out.println(w.get());
}
System.out.println();
}
System.out.println("循环结束:" + list.size());
}
}
运行结果:
2.垃圾回收算法
2.1标记清除
速度快,但是会产生内存碎片
2.2标记整理
使得不被清理的对象,紧凑到一边
无内存碎片,由于整理对象时需要移动对象,效率低。
2.3复制算法
把内存区域划成大小相对两块FROM和TO
垃圾清理时,把存活的对象从FROM赋值到TO上。清理FROM,并且交换FROM和TO
不会有内存碎片,但需要占用双倍空间。
3分代垃圾回收
在虚拟机中,采用分代的思想,将上面三种算法结合起来,让他们协同工作
长时间使用的对象放在老年代,用完了就可以丢弃的放在新生代。这样就可以根据不同对象的生命周期特点,执行不同的算法。
Minor GC (新生代空间不足时)
在幸存区中的对象的生命值达到一定的阈值后,就会把他放在老年代中。
Full GC (老年代空间不足时)
总结
- 对象首先分配在伊甸园区域
- 新生代空间不足,出发Minor GC,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加1并且交换from to;
- Minor GC 会引发stop the world(线程礼让,当发生垃圾回收时其他的线程都要先停下来,让GC先行。因为在GC时from to 会发生对象的复制),暂停其他用户的线程,等垃圾回收结束,用户线程菜恢复运行。
- 当对象寿命超过阈值时,会晋升至老年代。寿命最大是15次
- 当老年代空间不足,会先尝试出发MinorGC,如果之后空间仍不足,那么出发Full GC。
3.1 相关VM参数
4.垃圾回收器
4.1串行
- 单线程,GC时其他线程都暂停
- 堆内存较小,适合个人电脑
使用语句:
其中serial工作在新生代,采用的算法时复制
SerialOld工作在老年代,标记+整理算法
4.2吞吐量优先
- 多线程
- 堆内存较大,多核cpu来支持
- 让单位时间内,STW的时间最短 0.2 0.2=0.4(垃圾回收占比时间越低,吞吐量越高)
-XX:UseAdaptiveSizePolicy 动态调整
-XX:GCTimeRatio=ratio 1/(1+ratio)垃圾回收所占的工作时间
-XX:MaxGCPauseMillis=ms 垃圾回收时的最大暂停时间
-XX:ParallelGCThread=n 启用垃圾回收的线程数
4.3响应时间优先
- 多线程
- 堆内存较大,多核cpu来支持
- 尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1=0.5
它是并发的(concurrent)GC线程和用户线程同时工作
4.4G1
Garbage First
(从JDK9开始,G1就取代了之前的CMS垃圾回收器)
1.回收阶段
2.Young Conllection
Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)
(内存紧张了复制进幸存区)
G1把堆内存都划分为了一个个的区域,每一个region都可以独立当作伊甸园、幸存区和老年代
(幸存区对象进入老年代)
3.Young Collection + CM(并发标记)
4.Mixed Collection
5.Full GC
其中CMS和G1老年代内存不足,分两种情况,对于G1当老年代的内存超过阈值的时候就会触发并发标记和混合收集,如果回收的速度高于用户线程产生垃圾的速度的话,这个时候还处于并发垃圾收集的阶段,还不是Full GC。当垃圾回收的速度赶不上垃圾收集的速度,就会退化为串行的收集,触发Full GC
CMS是并发收集失败的时候才会触发Full GC
6.Young Collection 跨代引用
- 新生代回收的跨代引用(老年代引用新生代)问题
7.JDK 8u20 字符串去重
8.并发标记类卸载
所有对象经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类不再使用,则卸载它所加载的所有类
- XX:+ClassUnloadingWithConcurrentMark 默认启用
9.回收巨型对象
(1)定义
一个对象大于region的一半时,称之为巨型对象
(2)特征
- G1不会对巨型对象进行拷贝
- 回收时被优先考虑
- G1会跟踪老年代所有的incoming引用,这样老年代incoming引用为 0的巨型对象就可以在新生代垃圾回收的时候处理掉