扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
对象被判定为垃圾的标准:
十多年的密山网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销的优势是能够根据用户设备显示端的尺寸不同,自动调整密山建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“密山网站设计”,“密山网站推广”以来,每个客户项目都认真落实执行。
判定对象是否为垃圾的算法:
可达性分析算法遍历引用链如图:
可以作为GC Root的对象:
光有垃圾标记算法还不行,JVM还需要有垃圾回收算法来将这些标记为垃圾的对象给释放回收掉。主要的回收算法有以下几种:
1.标记 - 清除算法(Mark and Sweep):
缺点:由于标记 - 清除不需要进行对象的移动,并且仅对不可达的对象进行处理,因此使用该回收算法之后会产生大量不连续的内存碎片。而内存碎片过多可能会导致以后在程序运行过程中,需要分配内存给较大的对象时,无法找到足够大的连续内存空间,从而不得不再次触发垃圾回收工作,若依旧无法分配内存的话就会触发内存溢出异常。
1.复制算法(Copying):
优点:解决内存碎片化问题,顺序分配内存,简单高效。该算法适用于对象存活率低的场景,所以普遍应用在新生代中,因为新生代里的对象存活率通常情况下只有10%左右
3.标记 - 整理算法(Compacting):
优点:避免了标记 - 清除算法所带来的内存不连续性问题,以及不需要像复制算法那样需要设置两块内存互换。该算法适用于对象存活率较高的场景,所以普遍应用在老年代中,因为老年代里对象存活率较高
4.分代收集算法(Generational Collector):
在JDK7及之前的JVM版本共有三个分代,即新生代、老年代和永久代(注意,永久代不存在于堆中,而是存在于方法区):
而JDK8及以后的版本只有新生代和老年代:
分代收集算法的GC分为两种:
新生代用于尽可能快速地收集掉那些生命周期短的对象,新生代分为两个区:
对象如何晋升到老年代:
常用的调优参数:
综上,老年代用于存放生命周期较长的对象,老年代采用的是标记 - 整理算法。
Full GC和Major GC:
触发Full GC的条件:
注:promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的。
在了解垃圾收集器之前,我们需要知道一个概念“Stop-the-World”:
除此之外,我们需要知道什么是Safepoint:
JVM的运行模式:
各垃圾收集器之间的联系,即可以搭配使用关系:
Serial收集器(启动参数:-XX:+UseSerialGC,采用复制算法):
ParNew收集器(启动参数:-XX:+UseParNewGC,采用复制算法):
Parallel Scavenge收集器(启动参数:-XX:+UseParallelGC,采用复制算法):
Serial Old收集器(启动参数:-XX:+UseSerialOldGC,采用标记 - 整理算法):
Parallel Old收集器(启动参数:-XX:+UseParallelOldGC,采用标记 - 整理算法):
CMS收集器(启动参数:-XX:+UseConcMarkSweepGC,采用标记 - 清除算法):
CMS收集器收集流程:
CMS收集器图示:
G1收集器(启动参数:-XX:+UseG1GC,采用复制 + 标记 - 整理算法):
1.Object的finalize()方法的作用是否与C++的析构函数作用相同:
示例代码:
package com.example.demo.gc;
/**
* @author 01
* @date 2019-07-18
**/
public class Finalization {
public static Finalization finalization;
@Override
protected void finalize() throws Throwable {
System.out.println("finalize");
finalization = this;
}
public static void main(String[] args) {
Finalization f = new Finalization();
System.out.println("First print: " + f);
f = null;
System.gc();
System.out.println("Second print: " + f);
System.out.println(f.finalization);
}
}
执行结果:
从执行结果可以看到,Finalization对象被GC回收时finalize()方法会被调用,finalize()方法里将当前对象this赋值给了静态属性finalization实现了对象的“重生”,所以在GC之后依旧能打印到该对象的地址信息
注:finalize是个不太可控的方法因此并不常用,并且在JDK9+版本被标注为过时方法
2.Java中的强引用,软引用,弱引用及虚引用有什么用:
所谓强引用,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,当然具体回收时机还是要看垃圾收集策略。总结:
- 最普遍的引用,例:
Object obj = new Object();
- JVM宁可抛出OutOfMemoryError终止程序也不会回收具有强引用的对象
- 通过将对象设置为null来弱化引用,使其被回收
是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。总结:
- 对象处在有用但非必须的状态
- 只有当内存空间不足时,GC会回收该引用的对象内存
- 软引用通常用来实现内存敏感的高速缓存
- 可以配合引用对象使用(ReferenceQueue)
弱引用并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。总结:
- 用于描述非必须的对象,比软引用更弱一些
- 发生GC时就会被回收掉,不过被回收的概率也不大,因为GC线程优先级比较低
- 适用于引用偶尔被使用且不影响垃圾收集的对象
- 可以配合引用对象使用(ReferenceQueue)
对于虚引用,你不能通过它访问对象。虚引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制,比如,通常用来做所谓的 Post-Mortem 清理机制,例如 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的创建和销毁。总结:
- 不会决定对象的生命周期
- 虚引用的对象任何时候都可能被垃圾收集器回收,就像是没有引用的对象一样
- 虚引用通常用来跟踪对象被垃圾收集器回收的活动,起哨兵作用
- 与软引用和弱引用不同的是,该引用必须与引用对列(ReferenceQueue)联合使用
软引用代码示例:
// 强引用
String str = new String("abc");
// 转换为软引用
SoftReference softReference = new SoftReference<>(str);
弱引用代码示例:
String str = new String("abc");
// 弱引用
WeakReference weakReference = new WeakReference<>(str);
虚引用代码示例:
String str = new String("abc");
// 引用队列
ReferenceQueue queue = new ReferenceQueue<>();
// 转换为虚引用
PhantomReference phantomReference = new PhantomReference<>(str, queue);
// GC在回收一个对象时,如果发现该对象存在虚引用,那么在回收之前会先将该对象的虚引用添加到与该对象关联的引用队列中;程序代码可以通过判断引用队列是否已加入虚引用来得知被引用的对象是否已经被回收
引用队列(ReferenceQueue):
引用强度关系:
下面流程图简单总结了对象生命周期和不同可达性状态,以及不同状态可能的改变关系:
上图的具体状态,实际是 Java 定义的不同可达性级别(reachability level),在之前也说过判断对象可达性,是 JVM 垃圾收集器决定如何处理对象的一部分考虑。可达性具体含义如下:
各引用包装类的继承关系图:
下面我们来用一个例子演示引用包装对象及引用队列的使用,首先定义一个普通的类,并且实现finalize方法以便我们在测试时可以看到该对象是否被GC回收了:
package com.example.demo.gc;
/**
* @author 01
* @date 2019-07-18
**/
public class NormalObject {
public String name;
public NormalObject(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing obj: " + name);
}
}
然后定义一个WeakReference的子类,目的是扩展name属性,以便我们在测试时能够得知是哪个对象的引用对象:
package com.example.demo.gc;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* @author 01
* @date 2019-07-18
**/
public class NormalObjectWeakReference extends WeakReference {
public String name;
public NormalObjectWeakReference(NormalObject referent, ReferenceQueue queue) {
super(referent, queue);
this.name = referent.name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("finalizing NormalObjectWeakReference: " + name);
}
}
最后编写一个测试类:
package com.example.demo.gc;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* @author 01
* @date 2019-07-18
**/
public class ReferenceQueueTests {
// 引用队列
private static ReferenceQueue queue = new ReferenceQueue<>();
/**
* 检查引用队列里有没有引用对象,有的话则打印相关信息
*/
private static void checkQueue() {
Reference extends NormalObject> reference;
while ((reference = queue.poll()) != null) {
// 存在于引用队列中的引用对象
System.out.println("In Queue: " + ((NormalObjectWeakReference) (reference)).name);
// 获取引用的对象实例
System.out.println("reference object: " + reference.get());
}
}
public static void main(String[] args) throws InterruptedException {
List> weakReferenceList = new ArrayList<>();
// 创建引用对象
for (int i = 0; i < 3; i++) {
NormalObject normalObject = new NormalObject("Weak-" + i);
NormalObjectWeakReference reference = new NormalObjectWeakReference(normalObject, queue);
weakReferenceList.add(reference);
System.out.println("Create weak: " + reference);
}
System.out.println("\nbefore gc ------");
checkQueue();
System.out.println("\ngc ing... ------");
System.gc();
// 让线程休眠一会,以保gc能够正常执行完毕
Thread.sleep(1000);
System.out.println("\nafter gc ------");
checkQueue();
}
}
运行结果:
可以看到在GC执行之前调用checkQueue方法没有打印任何信息,因为此时引用队列中没有任何引用对象。而当GC执行之后,引用队列中就被添加了与之相关联的引用对象,所以就能够打印出引用对象的相关信息
GC相关参考文章:
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流