Java核心技术36讲笔记(1)java平台、final、引用类型

1 Java 是解释执行吗?

“Java 是解释执行”这句话,这个说法不太准确。我们开发的 Java 的源代码,首先通过 Java 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。

JIT简单理解:
通常javac将程序源码编译,转换成java字节码,JVM通过解释字节码将其翻译成相应的机器指令逐条读入,逐条解释翻译。非常显然,经过解释运行,其运行速度必定会比可运行的二进制字节码程序慢。为了提高运行速度,引入了JIT技术。
在执行时JIT会把翻译过的机器码保存起来,已备下次使用,因此从理论上来说,採用该JIT技术能够,能够接近曾经纯编译技术.
快在减少从字节码到机器码翻译的时间
参考:https://blog.csdn.net/qq_36042506/article/details/82976586

2 谈谈final、finally、 finalize有什么不同?

  1. finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成 特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为deprecated。因为他会拖慢gc的速度
  2. final 也许会有性能的好处.类似的,final 字段对性能的影响,大部分情况下,并没有考虑的必要。
  3. 防御性拷贝:如果一个类的常量引用是可变对象(比如Date)。则我们不能在构造方法里面,直接用成员变量执行传进来的变量。而是要重新构建一个局部变量(和传进来的值相同)。能否使用clone,取决于该对象是否是不可变(比如Date不是fianl的,所以不要用clone方法)
  4. java.lang.ref.Cleaner 来替换掉原有的 finalize 实现,Cleaner 的实现利用了幻象引用(PhantomReference),

3 引用类型

不同类型的引用有什么区别

不同的引用类型,主要体现的是对象不同的可达性(reachable)状态和对垃圾收集的影响

强引用

就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相强引用赋值为 null,就是可以被垃圾收集的了,当然具体回收时机还是要看垃圾收集策略

软引用(SoftReference)

是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出OutOfMemoryError之前清理软引用指向的对象。软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存

String str=new String("abc");                                     // 强引用
SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

弱引用(WeakReference)

并不能使对象豁免垃圾收集,仅仅是提供一种访问在弱引用状态下对象的途径。这就可以用来构建一种没有特定约束的关系,比如,维护一种非强制性的映射关系,如果试图获取时对象还在,就使用它,否则重现实例化。它同样是很多缓存实现的选择。

下面的代码会让str再次变为一个强引用

String  abc = abcWeakRef.get();

弱引用锁指向的对象是是否临近finalize状态的,当弱引用被清除时,就会符合finalize的条件

幻象(虚)引用

你不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制,比如,通常用来做所谓的 Post-Mortem 清理机制,我在专栏上一讲中介绍的 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的创建和销毁

虚引用与软引用和弱引用的一个区别在于:虚引用必须和·引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

作用
它的作用在于跟踪垃圾回收过程,在对象被收集器回收时收到一个系统通知。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前不会彻底销毁该对象。 所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收了。

如果一个对象没有强引用和软引用,对于垃圾回收器而言便是可以被清除的,在清除之前,会调用其finalize方法,如果一个对象已经被调用过finalize方法但是还没有被释放,它就变成了一个虚可达对象

与软引用和弱引用不同,显式使用虚引用可以阻止对象被清除,只有在程序中显式或者隐式移除这个虚引用时,这个已经执行过finalize方法的对象才会被清除。想要显式的移除虚引用的话,只需要将其从引用队列中取出然后扔掉(置为null。感觉如果只是poll没有赋值。也没有设置null的地方。只需要取出即可)即可

适用场景
使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。

事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。

对比

在这里插入图片描述

引用队列

当gc(垃圾回收线程)准备回收一个对象时,如果发现它还仅有软引用(或弱引用,或虚引用)指向它,就会在回收该对象之前,把这个软引用(或弱引用,或虚引用)加入到与之关联的引用队列(ReferenceQueue)中。
如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象回收了。

什么时候放入队列?
其他引用时回收内存之后,虚引用是之前.注意是和内存回收比,不是和finalize比

引用对象本身是强引用
当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。

用法一-清除引用本身

package javalearning;
 
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
 
public class ReferenceQueneDemo {
     
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args){
        /*创建引用队列*/
        ReferenceQueue<SoftReference<int[]>> rq = 
                new ReferenceQueue<SoftReference<int[]>>();
         
        /*创建一个软引用数组,每一个对象都是软引用类型*/
        SoftReference<int[]>[] srArr = new SoftReference[1000];
         
        for(int i = 0; i < srArr.length; i++){
            srArr[i] = new SoftReference(new int[300000], rq);
        }
         
        /*(可能)在gc前保留下了三个强引用*/
        int[] arr1 = srArr[30].get();
        int[] arr2 = srArr[60].get();
        int[] arr3 = srArr[90].get();
         
        /*占用内存,会导致一次gc,使得只有软引用指向的对象被回收*/
        int[] strongRef = new int[200000000];
         
        Object x;
        int n = 0;
        while((x = rq.poll()) != null){
            int idx = 0;
            while(idx < srArr.length){
                if(x == srArr[idx]){
                    System.out.println("free " + x);
                    srArr[idx] = null; /*手动释放内存*/
                    n++;
                    break;
                }
                idx++;
            }
        }
         
        /*当然最简单的方法是通过isEnqueued()判断一个软引用方法是否在
         * 队列中,上面的方法只是举例
         int n = 0;
         for(int i = 0; i < srArr.length; i++){
            if(srArr[i].isEnqueued()){
                srArr[i] = null;
                n++;
            }
         }  
        */     
        System.out.println("recycle  " + n + "  SoftReference Object");
    }
}

关于虚引用的GC行为

在上一节,并没有列出虚引用的使用场景,因为它的使用场景十分单一。PhantomReference设计的目的就是可以在对象被回收之前收到通知(通过注册的队列),所以它没有不含注册队列的构造器(只有public PhantomReference(T referent, ReferenceQueue<? super T> q),但你仍可以传null进去),但这种场景在JDK里并没有出现,也很少有开发者使用它。

从PhantomReference类的源代码可知,你永远无法通过它获取到它引用的那个对象(其get()方法永远返回null),但是它又可以阻止其引用的目标对象被GC回收。从上文可知,通常一个不可达(强不可达、软不可达、弱不可达)的对象会被finalize,然后被回收。但如果它在被回收前,GC发现它仍然是虚可达,那么它就不会回收这块内存,而这块内存又不能被访问到,那么这块内存就泄漏了。

想要虚引用的「目标对象」被回收,必须让「引用对象」本身不可达,或者显式地清除虚引用。所以如果使用不当,很可能会造成内存泄漏,这也是它使用范围不广的原因之一。

参考

  1. 你不可不知的Java引用类型【总结篇】
  2. Java中的四种引用以及ReferenceQueue和WeakHashMap的使用示例
  3. https://www.jianshu.com/p/77dfeccac85d
发布了82 篇原创文章 · 获赞 1 · 访问量 1977

猜你喜欢

转载自blog.csdn.net/m0_38060977/article/details/103001248