JVM优化系列-Java对象引用与可触及强度
导语
垃圾回收机制本身依托于对象的可触及性,也就是从根节点开始是否可以访问到这个对象,如果可以则说明这个对象是可触及的,也就是可达的。如果访问不到说明这个对象已经不能被使用到了。到这个时候这个对象就要被进行回收了。那么一个对象是否可以回收就要对其可触及性进行分析。保证可以在相对安全的情况下进行垃圾回收。
文章目录
- 引言
- 对象复活
- 引用和可触及强度
- 强引用
- 软引用
- 软引用实例
- 弱引用
- 虚引用
- 总结
引言
判断什么情况下对象进行回收,下面给出了3种情况
- 从根节点开始,可以达到这个对象
- 对象的所有应用都被释放,但是对象有可能在finalize()函数中复活
- 对象的finalize()函数被调用,并且没有复活,那就进入到了不可及的状态,不可及对象不能被复活,因为finalize()函数只能被调用一次。
对于以上的三种情况,只有在对象不可及时才可以被回收。
对象复活
示例代码
public class CanReliveObj {public static CanReliveObj obj;@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("CanReliveObj finalize called");obj = this;}@Overridepublic String toString() {return "I am CanReliveObj";}public static void main(String[] args) throws InterruptedException {obj = new CanReliveObj();obj = null;System.gc();Thread.sleep(1000);if (obj == null){System.out.println("obj is null");}else {System.out.println("obj is used");}System.out.println("第2次GC");obj = null;System.gc();Thread.sleep(1000);if (obj == null){System.out.println("obj is null");}else {System.out.println("obj is used");}}
}
运行结果
从上面代码中可以看到第一次将obj 设置为null的时候,结果obj对象又被复活了,在第二次运行GC的时候才被回收掉。这是因为第一次GC的时候在finalize()函数调用之前,系统中的引用虽然已经被清除了,但是作为实例方法的finalize(),对象的this引用依然被传入到了方法内部,如果引用外泄,对象就会被复活。这个时候对象的引用又存在了,对象又复活了。但是这个方法只能被调用一次,所以第二次清除的时候对象就完全被清除了。
注意
- finalize()不推荐使用这个函数来释放资源
- finalize()会导致引用外泄,在无意中复活对象
- finalize()是被系统所调用,调用的时间不确定,所以不推荐使用
- 推荐使用资源释放方法可以采用try-catch-finally进行资源释放
引用和可触及强度
在Java基础中提到过四种引用方式,分别是强引用、软引用、弱引用和虚引用。但是除了强引用之外,其他引用在java.long.ref包中可以找到。
如图三种引用类型都可以找到,其中FinalReference意味这是最终引用,它用来实现对象的finalize()方法进行实现。
对于强引用来说就是在程序中一般的引用类型,强引用的对象一般都是可触及的,不会被回收。相对软引用、弱引用和虚引用的对象是软可触及的,弱可触及、软可触及的,在一定条件下都可以被回收。
强引用
public class ReferenceTest {public static void main(String[] args) {StringBuffer stringBuffer = new StringBuffer("Hello World!");stringBuffer.toString();}
}
运行上面代码,变量stringBuffer会被分配到栈内存中,而new StringBuffer则会被分配到堆内存中,在栈内存中的stringBuffer的变量会指向堆内存中的new StringBuffer。这个时候stringBuffer 就是StringBuffer 的强引用实例。
如果将上面代码改为如下
public class ReferenceTest {public static void main(String[] args) {StringBuffer stringBuffer = new StringBuffer("Hello World!");StringBuffer str = stringBuffer;stringBuffer.toString();str.toString();}
}
这个时候stringBuffer所指向的堆内存也会被str所指向,同时会在栈上的局部变量变中分配str变量,如图所示,如果此时使用"==" 则表示两个对象所指向的内存空间是否相等,如果相同则表示两这指向同一个对象,如果不相同则表示两个对象不相同。
总结
强引用特点
- 强引用可以直接访问目标对象
- 强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常也不会回收强引用对象回收
- 强引用可能导致内存泄露
软引用
软引用是比强引用弱一点的引用类型,一个对象只持有软引用,那么当堆空间不足的时候,就会别回收,软引用使用java.lang.ref.SoftReference类实现。这里由于要使用到引用包,所以笔者使用了IDEA工具。
public class SoftRef {public static class User{public String name;public int age;public User(String name,int age){this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}public static void main(String[] args) {User u = new User("nihui",12);SoftReference<User> userSoftReference = new SoftReference<User>(u);u = null;System.out.println(userSoftReference.get());System.gc();System.out.println("After GC");System.out.println(userSoftReference.get());byte[] b = new byte[1024*531*7];
// byte[] c = new byte[1024*1024*7];System.gc();System.out.println(userSoftReference.get());}
}
调整启动参数
运行结果
从上面的运行结果可以看到在执行分配之后如果出现了一块较大的内存分配,这个时候系统会认为是资源紧张,这个时候执行GC操作,但是第二次GC操作是多余的,因为系统自己会在资源紧张的时候执行GC操作,所以说当系统资源紧张的时候软引用就会被清理。
从上面可以知道,GC未必会回收软引用的对象,但是当内存资源紧张的时候,软引用对象会被回收,所以软引用对象不会引起内存溢出的异常。
软引用实例
每个软引用都是附带一个引用队列,当对象可达性状态发生变化的时候,软引用对象就会进入引用对象,可以跟踪对象的回收情况。
public class SoftRefQ {public static class User{public String name;public int age;public User(String name,int age){this.name = name;this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}}static ReferenceQueue<User> userReferenceQueue = null;public static class CheckRefQueue extends Thread{@Overridepublic void run() {while (true){if (userReferenceQueue!=null){UserSoftReference obj = null;try {obj = (UserSoftReference) userReferenceQueue.remove();}catch (Exception e){e.printStackTrace();}if (obj!=null){System.out.println("user id "+obj.name+" is delete");}}}}}public static class UserSoftReference extends SoftReference<User>{String name;public UserSoftReference(User referent, ReferenceQueue<? super User> q) {super(referent, q);name = referent.name;}}public static void main(String[] args) throws InterruptedException {Thread t = new CheckRefQueue();t.setDaemon(true);t.start();User u = new User("nihui",12);userReferenceQueue = new ReferenceQueue<>();UserSoftReference userSoftReference = new UserSoftReference(u,userReferenceQueue);u = null;System.out.println(userSoftReference.get());System.gc();System.out.println("After GC");System.out.println(userSoftReference.get());System.out.println("New ========= Heap===========Max");byte[] b = new byte[1024*532*7];System.gc();System.out.println(userSoftReference.get());Thread.sleep(1000);}
}
运行上述代码保证参数调整为第一个小例子参数相同,会发现执行结果如下。
执行结果
弱引用
弱引用是一种比软引用较弱的引用类型,只要发现弱引用,不管系统堆内存的使用情况,都会将对象进行回收,但是由于垃圾回收器的线程优先级通常较低,所以并不能很快的发现持有弱引用的对象。所以弱引用的对象可以存在很长的时间。但是如果一个弱引用对象被回收,就会被加入到一个注册的引用队列中,这个与软引用类似。弱引用使用的是java.lang.ref.WeakReference类来实现。
public class WeakRef {public static void main(String[] args) {Student student = new Student("nihiu",123);WeakReference<Student> studentWeakReference = new WeakReference<Student>(student);student = null;System.out.println(studentWeakReference.get());System.gc();System.out.println("After GC");System.out.println(studentWeakReference.get());}
}
由于是弱引用所以在任何情况下都会被回收,所以当去除强引用之后,进行一次的GC操作,之后再次获取该对象时候就为空了。可以看到GC之后对象立马被清空了。
弱引用和软引用一样,在构造弱引用的时候,也可以指定一个引用队列,当弱引用对象被回收的时候,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况。这个可以参考上面软引用的代码将软引用队列换成弱引用队列就可以了。
注意
在一定程度上,软引用、弱引用都非常适合保存一些可有可无的缓存数据,如果这样的话,在系统资源不足的时候这些数据将会被回收,不会导致内存溢出的异常,当内存资源充足的时候又可以长时间存在。在一定程度上充当了对象缓存的功能。
虚引用
虚引用是存在于所有引用类型中最弱的一个,在程序中持有虚引用的对象和没有被引用的对象几乎是一样的处理。一个小小的System.gc()就可以搞定一大部分的虚引用对象。如果使用get()方法来获取其强引用,大概率的是失败的。并且在使用虚引用的时候,大概率是与队列一起使用。对于它的操作存在于整个的垃圾回收过程。
怎么与引用队列结合使用?当进行垃圾回收的时候,如果发现对象还有虚引用,会在回收对象之后将其虚引用加入到引用队列中,从而通知应用程序对象的回收情况。
实例代码
public class PhantomRef {public static PhantomRef obj;static ReferenceQueue<PhantomRef> phantomRefReferenceQueue = null;public static class CheckRefQueue extends Thread{@Overridepublic void run() {while (true){if (phantomRefReferenceQueue!=null){PhantomReference<PhantomRef> obj = null;try{obj = (PhantomReference<PhantomRef>) phantomRefReferenceQueue.remove();}catch (Exception e){e.printStackTrace();}if (obj!=null){System.out.println("PhantomRef is delete by GC");}}}}}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println(" PhantomRef finalize called");obj = this;}@Overridepublic String toString() {return "I am PhantomRef";}public static void main(String[] args) throws InterruptedException {Thread thread = new CheckRefQueue();thread.setDaemon(true);thread.start();phantomRefReferenceQueue = new ReferenceQueue<PhantomRef>();obj = new PhantomRef();PhantomReference<PhantomRef> phantomReference = new PhantomReference<>(obj,phantomRefReferenceQueue);phantomReference.get();obj =null;System.gc();Thread.sleep(1000);if (obj==null){System.out.println("obj is null");}else {System.out.println("obj is not null");}System.out.println("第二次 GC操作");obj = null;System.gc();Thread.sleep(1000);if (obj == null){System.out.println("obj is null");}else {System.out.println("obj is not null");}}
}
执行结果
通过上面执行的代码可以看出在finalize()方法中对对象进行了复活。然后调用了对象的虚引用。由于finalize()方法只能被调用一次,所以说在第二次GC的时候对象就被回收了。
总结
从上面的分析可以看出来,在Java中除了强引用会造成内存溢出异常,其他的三种引用类型都不会造成内存溢出的异常。引用的强度和对象可达性分析是对如何更高效的使用Java语言编写出高性能的应用有很大的帮助,以上是笔者结合网上资料以及个人理解来分享。还需要更多的努力。
JVM优化系列-Java对象引用与可触及强度相关推荐
- JVM优化原理—Java架构师必须要知晓的知识
想要成为一名出色的Java架构师,必须要彻底了解Java的一个重要的特点那就JVM ...
- JVM优化系列-String对象在虚拟机中的实现
导语 String字符串在是各种编程语言中都是重头戏.各种语言中对字符串的操作都是进行有特殊化的处理,例如在C语言中根本没有字符串这个概念,在C语言中的字符串是用字符数组来表示的.在Java中,S ...
- JVM优化系列-JVM内存溢出的原因
导语 内存溢出(OutOfMemory)OOM,通常情况下出现在某一块内存空间快要消耗完的时候.在Java程序中,导致内存溢出的原因有很多,下面就来分享关于内存溢出的一些问题.其中包括堆内存.直接 ...
- JVM优化系列-常用GC参数总结
导语 在之前的分享中,提到了很多的JVM的参数这篇分享就来总结一下在实际中常用到的一些JVM的参数 文章目录 基本参数 1.与串行回收器相关的参数 2.与并行GC相关的参数 3.与CMS回收器相关 ...
- JVM优化系列-对象内存分配和回收的细节
导语 通过之前的分享,了解了关于垃圾回收算法以及垃圾回收器以及其使用,下面介绍的就是在实际使用中或者说是在处理问题过程中会出现的一些问题. 文章目录 禁用System.gc() System.gc ...
- JVM优化系列-JVM G1 垃圾收集器
导语 G1回收器是在JDK1.7中正式使用的一种全新的垃圾回收器,它的目标是为了取代CMS回收器.G1回收器拥有独特的垃圾回收策略,和之前的任意的一种垃圾回收器都有所不同,但是从分代策略上来说依然 ...
- JVM优化系列-JVM垃圾收集器介绍
导语 既然是串行顾名思义就是使用单线程的方式进行执行,每次执行回收的时候,串行回收器只有一个工作线程,这样对于并行能力较弱的计算机,串行回收器更多的独占线程专一执行的方面有着良好的实现,也就是说在 ...
- JVM优化系列-Stop-The-World实战
导语 垃圾收集器的主要任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可正常高效执行,在大部分的情况下会请求系统进入到一个停顿阶段,在这个停顿阶段对所欲应用进程进行终止,然后执行垃圾清理操作 ...
- JVM优化系列-详解JDK1.8 Metaspace 参数配置
导语 在JVM中除了有堆内存参数配置以外,还有一些其他内存例如方法区.线程栈直接内存等等.他们与堆内存来说是相对比较独立的内存空间.与堆内存相比较这些内存与应用程序本身的关系不大,但是如果将其放到 ...
最新文章
- 重磅!苹果祭出大招:史上最强 Mac 发布,iPad OS 惊艳问世
- DOM获取元素位置的三大系列offset/scroll/client
- python3列表_Python3 列表List(十一)
- spring rmi_Spring远程支持和开发RMI服务
- Python中Dict的查找
- 基于智能手机Android平台音乐播放器全程开发实战
- day18正则表达式 的介绍和模块运用
- SQL Server_SQL Server Windows NT - 64 bit
- 直流电机驱动c语言程序,单片机PWM控制直流电机驱动程序+仿真+报告
- 知识图到文本的生成(十一)
- 程序员撩妹,你得看我教你的小技巧
- C51 基本函数、中断函数和库函数的详解
- 运动世界校园刷跑的简单方法应用
- 【罗塞塔石碑】—My Lover(One.iso)
- 腾讯云服务器和cdn,腾讯云服务器开启CDN及CDN开启HTTPS详细配置教程
- 什么是Socket?websocket和socket区别?
- 计算机的击键方法教学教案,2.2 敲击键盘 教案
- 干式离合器与湿式离合器有什么区别(转载)
- BFF——服务于前端的后端
- jdk1.5之后的新特性之可变参数