1. 如何判断对象可以回收
1-1. 引用计数法
引用计数法
引用计数法弊端
1-2. 可达性分析算法
根对象:一些肯定不能作为垃圾的对象
-
在垃圾回收之前,会对堆内存所有的对象进行扫描
-
如果是,则不能被垃圾回收
-
如果不是,则可以被垃圾回收
1-3. 四种引用
实线代表强引用
强引用
- 所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
- Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收 obj = null; //手动置null后会被回收
软引用
- 只有【软引用】引用该对象时,在垃圾回收后,内存仍不足 则会回收软引用对象
- 可以配合【引用队列】来释放软引用自身,因为软引用自身也占用内存
- 在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
- 非核心业务资源(比如:图片)被强引用特别多时,有可能报OOM异常,因为强引用是不会被回收的,内存满直接抛异常
- 那么就可以用【软引用】来指向这些资源,当内存不足时,回收这些资源。
- 以后如果再使用图片资源,重新读取一遍。
- 这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
- 在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
弱引用
- 只有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合【引用队列】来释放弱引用自身,因为弱引用自身也占用内存
- 在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
虚引用
- 必须配合【引用队列】使用
- 例如
ByteBuffer
对象不再【强引用】时,ByteBuffer
对象本身可以被垃圾回收,但是占用的直接内存
是属于操作系统的,无法被回收。 - 那么就可以将【虚引用】放入【引用队列】, 由
Reference Handler
线程调用虚引用相关方法释放【直接内存】 - 如上图,B对象不再引用
ByteBuffer
对象,ByteBuffer
就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner
放入引用队列中,然后调用它的clean
方法来释放直接内存 - 总结:【虚引用】引用的对象被垃圾回收时,【虚引用】被放入【引用队列】,从而由一个线程可以调用【虚引用】对象中的方法执行一系列操作。
- 在 JDK1.2 之后,用 PhantomReference 类来表示
终结器引用
Object
类中有finallize()
方法- 当一个类重写了
Object
类中有finallize()
方法,并且该对象没有被【强引用】时,就可以进行垃圾回收 - 第一次垃圾回收时,将【终结器引用】放入【引用队列】,并且由一个优先级很低的Finalizer线程去寻找【终结器引用】的对象,找到后执行该对象的
finallize()
方法。 - 直到第二次垃圾回收时,才将该对象进行垃圾回收。
软引用使用:
public class Demo1 {
public static void main(String[] args) {
final int _4M = 4*1024*1024;
//list是强引用,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<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是强引用,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. 垃圾回收算法
2-1. 标记清除
在垃圾回收的过程中
优点: 速度快
缺点: 容易产生内存碎片。一旦分配较大内存的对象,由于内存不连续,导致无法分配,最后就会造成内存溢出问题
2-2. 标记整理
优点:不会有内存碎片
缺点:速度慢
2-3. 复制
复制算法
- 将内存分为等大小的两个区域,FROM和TO(TO中为空)。
- 将被GC Root引用的对象从FROM放入TO中,然后回收不被GC Root引用的对象。
- 回收完之后交换FROM和TO两个区域。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间。
优点:不会有内存碎片
2-4. 总结
3. 分代垃圾回收
堆内存分为新生代和老年代,新生代有划分为伊甸园,幸存区To,幸存区From。
新生代
老年代
新生代和老年代会进行不同的垃圾回收算法
3-1. 回收流程
新创建的对象分配在伊甸园
如果新生代空间不足时,触发 Minor GC(minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行)回收伊甸园
和 幸存区from
中的垃圾,伊甸园
和 from
存活的对象使用 copy 复制到 to
中,存活的对象年龄加 1并且交换 from
和 to
,并让幸存区from
中的对象寿命加1
当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit) 不同的垃圾回收器阈值会不同
如果老年代空间不足,会先尝试触发Minor GC,回收新生代的垃圾。如果之后空间仍不足,那么触发 Full GC,回收新生代和老年代的垃圾,stop the world的时间更长
如果 Full GC之后,空间仍不足。则触发OutOfMemory – Java heap space0000000
3-2. VM参数分析
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);
}
}
4. 垃圾回收器
串行垃圾回收器
- 是多线程
- 适合堆内存较大,多核cpu
- 响应时间优先,即尽可能让单次STW的时间最短
- 例如:1h内发生了5次垃圾回收,0.1 + 0.1 +0.1 + 0.1 + 0.1 = 0.5 总耗时0.4,单次STW 0.1
4-1. 串行
XX:+UseSerialGC = Serial + SerialOld
Serial 收集器:
安全点:即发生垃圾回收时,让所有线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象。
阻塞:因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态
4-2. 吞吐量优先
JDK1.8
默认使用的垃圾回收器
4-3. 响应时间优先
- 初始标记:标记GC Roots能直接到的对象。速度很快,存在Stop The World
- 并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
- 重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。存在Stop The World
- 并发清理:对标记的对象进行清除回收
CMS收集器:
ParNew 收集器:
4-4. G1
Garbage First
- JDK 9以后默认使用,而且替代了CMS 回收器
适用场景:
- 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
- 超大堆内存,会将堆划分为多个大小相等的 Region
- 整体上是 标记+整理 算法,两个Region之间是 复制 算法
开启G1参数:
G1垃圾回收阶段
Young Collection: 对新生代的垃圾回收
Young Collection + Concurrent Mark: 对新生代的垃圾回收 同时执行并发标记
Mixed Collection: 对新生代老年代进行垃圾回收
- 当伊甸园内存不足时,触发新生代垃圾回收
- 将存活的对象以复制算法放入到幸存区S
- CM:并发标记
- 在 Young GC 时会对 GC Root 进行初始标记
- 当老年代占用堆内存的比例达到阈值时,对进行并发标记(不会STW),阈值可以根据用户来进行设定
- 默认45%,当老年代占堆内存的45%则会进行并发标记
Mixed Collection
会对E S O 进行全面的回收
- 最终标记(Remark)会STW,最终标记是因为并发标记时其他线程还在运行,所以有可能产生新的垃圾,所有要暂停其他线程进行最终标记。
- 拷贝存活(Evacuation)会STW,拷贝存活就是在最终标记完后,将存活的对象拷贝到指定的区域
-XX:MaxGCPauseMills:xxx
:用于指定G1垃圾回收时
最长的停顿时间
Full GC
-
SerialGC
-
ParallelGC
-
CMS
-
G1
Young Collection 跨代引用
新生代回收的跨代引用(老年代引用新生代)问题
- 卡表:老年代被划为一个个卡表
- Remembered Set:Remembered Set 存在于E(新生代)中,用于保存新生代对象对应的脏卡
- 脏卡:O被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡
- 在引用变更时通过post–write barried + dirty card queue
- concurrent refinement threads 更新 Remembered Set
remark
白色:还未处理的对象
在并发标记过程中,有可能A被处理了以后未引用C,但该处理过程还未结束,在处理过程结束之前A引用了C,这时就会用到remark
- 之前C未被引用,这时A引用了C,就会给C加一个写屏障,写屏障的指令会被执行,将C放入一个队列当中,并将C变为
处理中
状态 - 在并发标记阶段结束以后,重新标记阶段会STW,然后将放在该队列中的对象重新处理,发现有强引用引用它,就会处理它
JDK 8u20 字符串去重
String s1 = new String("hello"); // char[]{'h','e','l','l','o'}
String s2 = new String("hello"); // char[]{'h','e','l','l','o'}
JDK 8u40 并发标记类卸载
所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类
-XX:+ClassUnloadingWithConcurrentMark
默认启用
JDK 8u60 回收巨型对象
- JDK 8u60 回收巨型对象一个对象大于 region 的一半时,称之为巨型对象
- G1 不会对巨型对象进行拷贝
- 回收时被优先考虑回收巨型对象
- G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0的巨型对象就可以在新生代垃圾回收时处理掉
JDK 9 并发标记起始时间的调整
5. 垃圾回收调优
5-1 调优领域
5-2 确定目标
5-3 最快的 GC
5-4 新生代调优
-
新生代的特点:
-
新生代内存越大越好么?
原文地址:https://blog.csdn.net/weixin_44147535/article/details/134584488
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_26364.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!