精华内容
下载资源
问答
  • java中引用类型和ThreadLocal 大厂面试题: 1.Java中的引用类型有哪几种? 2.每种引用类型的特点是什么? 3.每种引用类型的应用场景是什么? 4.ThreadLocal你了解吗 5.ThreadLocal应用在什么地方? Spring事务方面应用到...

    Java中引用类型和ThreadLocal

    大厂面试题: 
    1.Java中的引用类型有哪几种?
    2.每种引用类型的特点是什么?
    3.每种引用类型的应用场景是什么?
    4.ThreadLocal你了解吗
    5.ThreadLocal应用在什么地方?  Spring事务方面应用到了
    6.ThreadLocal会产生内存泄漏你了解吗?
    

    1. java中引用类型及特点

    • 强 引用: 最普通的引用 Object o = new Object()
    • 软 引用: 垃圾回收器, 内存不够的时候回收 (缓存)
    • 弱 引用: 垃圾回收器看见就会回收 (防止内存泄漏)
    • 虚 引用: 垃圾回收器看见二话不说就回收,跟没有一样 (管理堆外内存) DirectByteBuffer -> 应用到NIO Netty

    finalize(): 当对象被回收时, finalize()方法会被调用, 但是不推荐使用去回收一些资源,因为不知道他什么时候会被调用, 有时候不一定会调用

    public class C {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize");
        }
    }
    

    1.1 强引用

    正常引用,但没有人指向的时候就会被回收.

    import java.io.IOException;
    /**
     * 强引用
     */
    public class R1_NormalReference {
        public static void main(String[] args) throws IOException {
            //正常引用
            C c = new C();
            c = null;//没人指向
            System.gc();//DisableExplicitGC
    
            //阻塞一下,方便看结果
            System.in.read();
        }
    }
    

    1.2 软引用

    垃圾回收器, 内存不够的时候回收 (缓存)

    import java.io.IOException;
    import java.lang.ref.SoftReference;
    
    /**
     * 软引用
     */
    public class R2_SoftReference {
        public static void main(String[] args) {
            SoftReference<byte[]> soft = new SoftReference<>(new byte[1024 * 1024 * 10]);//10M
            System.out.println(soft.get());
            //gc回收
            System.gc();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soft.get());
    
            //再分配一个数组,好heap(堆)放不下, 这个时候系统会回收一次, 如果不够,会把软引用回收
            byte[] bytes = new byte[1024 * 1024 * 15];
            System.out.println(soft.get());
    
        }
    }
    
    结果:
    [B@1540e19d
    [B@1540e19d
    null
    

    前提设置 -Xmx30M 堆内存最大30M 用于测试

    idea里这样设置
    给堆内存分配空间

    1.3 弱引用

    遇到GC就会被回收

    import java.lang.ref.WeakReference;
    /**
     * 弱引用
     */
    public class R3_WeakReference {
        public static void main(String[] args) {
            WeakReference<C> weak = new WeakReference<>(new C());
            System.out.println(weak.get());
            //gc回收
            System.gc();
            //遇到GC就会被回收
            System.out.println(weak.get());
    
        }
    }
    
    结果:
    com.cz.reference.C@3c679bde
    null
    finalize
    

    1.4 虚引用

    不管三七二十一 遇到直接回收

    import java.lang.ref.PhantomReference;
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.util.LinkedList;
    import java.util.List;
    /**
     * 虚引用
     */
    public class R4_PhantomReference {
        private static final List<Object> LIST = new LinkedList<>();
        private static final ReferenceQueue QUEUE = new ReferenceQueue();
    
        public static void main(String[] args) {
    
            PhantomReference<C> phantomReference = new PhantomReference<>(new C(),QUEUE);
    
            new Thread(() -> {
                while (true){
                    LIST.add(new byte[1024*1024]);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Thread.currentThread().interrupt();
                    }
                    System.out.println(phantomReference.get());
                }
            }).start();
    
            new Thread(() -> {
                while (true){
                    Reference<? extends C> poll = QUEUE.poll();
                    if (poll != null){
                        System.out.println("-----虚引用对象被JVm回收了--------" + poll);
                        return;
                    }
                }
            }).start();
        }
    }
    结果:
    null
    null
    finalize
    null
    null
    

    总结: 强软弱虚 1

    • 强 正常的引用
    • 软 内存不够, 进行清除
      • 大对象的内存
      • 常用对象的缓存
    • 弱 遇到GC就会被回收
      • 缓存, 没有容器引用指向的时候就需要清除缓存
      • ThreadLocal
      • WeakReferenceMap
    • 虚 看见就回收, 且看不到值
      • 管理堆外内存

    2. ThreadLocal

    从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变是。这种变畺在多线程环境下访问(通 过get和set方法访问)时能保证各个线程的变星=相对独立于其他线程内的变垦。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。

    我们可以得知ThreadLocal的作用是:提供线程内的周部变星,不同的线程之间不会相互干扰,这种变星在 线程的生命周朗内起作用,减少同一个线程内多个函数或组件之间一些公共变畺传递的复杂度.

    2.1.特点:

    特点 内容
    1.线程并发 在多线程并发场景下
    2.传递数据 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
    (保存每个线程的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题)
    3.线程隔离 每个线程的变量都是独立的, 不会互相影响.(核心)
    (各线程之间的数据相互隔离却又具备并发性,避免同步方式带来的性能损失)

    2.2 ThreadLocal 和Synchronized的区别

    虽然ThreadLocal模式与Synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同

    synchronized ThreadLocal
    原理 同步机制采用以时间换空间的方式,只提供了一份变量, 让不同的线程排队访问 ThreadLocal采用以空间换时间的方式, 为每一个线程都提供了一份变量的副本, 从而实现同访问而相不干扰
    侧重点 多个线程之间访问资源的同步 多线程中让每个线程之间的数据相互隔离

    2.3 ThreadLocal的内部结构

    现在让我们看一下ThreadLocal的内部原理, 探究它能实现线程数据隔离的原理

    JDK 早期设计:

    在这里插入图片描述

    每个ThreadLocal都创建一个Map, 然后用Thread(线程) 作为Map的key, 要存储的局部变量作为Map的value, 这样就能达到各个线程的局部变量隔离的效果, 这是最简单的设计方法. 早期设计

    JDK8 优化设计(现在的设计)

    JDK8中ThreadLocal的设计是 : 每个Thread维护一个ThreadLocalMap, 这个Map的keyThreadLocal实例本身,value才是真正要存储的值Object
    在这里插入图片描述

    具体过程如下:

    1. 每个THreadLocal线程内部都有一个Map(ThreadLocalMap)
    
    2. Map里面存储的ThreadLocal对象(key)和线程变量副本(Value)也就是存储的值
    
    3. Thread内部的Map是由ThreadLocal维护的, 有THreadLocal负责向map获取和设置线程变量值
    
    4. 对于不同的线程, 每次获取value(也就是副本值),别的线程并不能获取当前线程的副本值, 形成了副本的隔离,互不干扰.
    

    对比一下 :
    在这里插入图片描述

    如今设计的好处:

    1. 每个Map存储的Entry数量变少
    2. 当Thread销毁的时候, THreadLocalMap也会随之销毁, 减少内存的使用.(之前以Thread为key会导致ThreadLocalMap的生命周期很长)

    2.4 THreadLocalMap源码分析

    2.4.1 基本结构

    ThreadLocalMap是ThreadLocal的静态内部类, 没有实现Map接口, 用独立的方式实现了Map的功能, 其内部的Entry也是独立实现.

    在这里插入图片描述
    在这里插入图片描述

    1. 成员变量
    		/**
             * The initial capacity -- MUST be a power of two.
             * 初始化容量,必须是2的整数次幂
             */
            private static final int INITIAL_CAPACITY = 16;
    
            /**
             * 存放数据的table, 同样数组长度必须是2的整数次幂
             * The table, resized as necessary.
             * table.length MUST always be a power of two.
             */
            private Entry[] table;
    
            /**
             * 数组里entrys的个数,可以判断table是否超过阈值 (存储的格式)
             * The number of entries in the table.
             */
            private int size = 0;
    
            /**
             * 阈值 进行扩容的阈值,表使用大于他的时候,进行扩容
             * The next size value at which to resize.
             */
            private int threshold; // Default to 0
    
    2.存储结构-Entry
    		/**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             翻译:
             * Entry继承WeakReference, 并且用ThreadLocal作为key
             * 如果key为null(entry.get() == null)意味着key不在被引用,因此这时候entry也可以从tab
             *中清除(被垃圾回收器回收) 
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    (ThreadLocal) key是弱引用, 其目的就是讲ThreadLocal对象的生命周期和和线程的生命周期解绑. 减少内存使用

    2.5 强弱引用和内存泄漏

    1.内存泄漏相关概念

    • 内存溢出: Memory overflow 没有足够的内存提供申请者使用.
    • 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出

    2.强弱引用见第一大点. (如上)

    3.如果key是强引用

    假设ThreadLocalMap中的key使用了强引用, 那么会出现内存泄漏吗?
    在这里插入图片描述

    1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
    2. 但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收
    3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread → entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏

      也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的

    4.如果key是弱引用

    假设ThreadLocalMap中的key使用了弱引用, 那么会出现内存泄漏吗?

    在这里插入图片描述

    1. 假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了

    2. 由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null

    3. 在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏

      也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏

    5.内存泄漏的真实原因

    在这里插入图片描述
    出现内存泄漏的真实原因出改以上两种情况,2
    比较以上两种情况,我们就会发现:
    内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?

    细心的同学会发现,在以上两种内存泄漏的情况中.都有两个前提:
    1 .没有手动侧除这个 Entry
    2 · CurrentThread 依然运行
    第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。
    第二点稍微复杂一点,由于ThreodLocalMapThreod 的一个属性,被当前线程所引甲丁所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。

    综上, ThreadLocal 内存泄漏的根源是:

    由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏

    6 为什么使用弱引用

    为什么使用弱引用,根据刚才的分析,我们知道了:

    无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。

    ​ 要避免内存泄漏有两种方式:
    ​ 1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
    ​ 2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束

    ​ 相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的.

    ​ 也就是说,只要记得在使用完ThreadLocal 及时的调用 remove ,无论 key 是强引用还是弱引用都不会有问题.

    那么为什么 key 要用弱引用呢

    ​ 事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null (也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么是会又如 value 置为 null 的.

    ​ 这就意味着使用完 ThreadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏.


    1. 马士兵老师 讲详解强软弱虚 ThreadLocal与内存泄漏 ↩︎

    2. java基础教程由浅入深全面解析threadlocal ↩︎

    展开全文
  • Java 中引用类型都有哪些

    千次阅读 2019-10-19 22:45:47
    Java 中引用类型都有哪些 Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚 引用。 强引用( StrongReference) 如果一个对象被被人拥有强引用,那么垃圾回收器绝会回收它。当...

    Java 中引用类型都有哪些

    Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚 引用。

    强引用( StrongReference)

    如果一个对象被被人拥有强引用,那么垃圾回收器绝会回收它。当内存空间不足, Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意
    回收具有强引用的对象来解决内存不足问题。

    Java的对象是位于 heap 中的, heap 中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到
    达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下代
    码:

    
     String abc=new String("abc"); //1
     SoftReference<String> softRef=new SoftReference<String>(abc); //2
     WeakReference<String> wea kRef = new WeakReference<String>(abc); //3
     abc=null; //4
     softRef.clear();//5
    

    第一行在
    heap 堆中创建内容为“ abc ”的对象,并建立 abc 到该对象的强引用,该对象是强可及的。
    第二行和第三行分别建立对
    heap 中对象的软引用和弱引用,此时 heap 中的 abc 对象已经有 3 个引用,显然此
    时 abc 对象仍是强可及的。
    第四行之后
    heap 中对象不再是强可及的,变成软可及的。第五行执行之后变成弱可及的。
    软引用( SoftReferenc)如果一个对象只具有软引用,那么如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的速缓存。
    软引用可以和一个引用队列(
    ReferenceQueue )联合使用,如果软引用所引用的对象被垃圾回收 Java 虚
    拟机就会把这个软引用加入到与之关联的引用队列中。

    软引用是主要用于内存敏感的高速缓存。jvm 报告内存不足之前会清除所有的软引用,这样以来 gc 就可能收集软可 及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于 gc 的算法和 gc 运行时可用内存的大小。当 gc 决定要收集软引用时执行以下过程 以上面的 softRef 为例:

    1. 首先将 softRef 的 referent abc )设置为 null ,不再引用 heap 中的 new String(“abc”) 对象。
    2. 将 heap 中的 new String(“abc”) 对象设置为可结束的 (finalizable)
    3. 当 heap 中的 new String(“abc”) 对象的 finalize() 方法被 运行而且该对象占用的内存被释放, softRef被添加到它的 ReferenceQueue.注意 对 ReferenceQueue 软引用和弱引用可以有可无,但是虚引用必须有。

    弱引用与软引用的区别在于 :只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

    弱引用

    可以和一个引用队列( ReferenceQueue )联合使用,如果弱引用所引用的对象被垃圾回收 Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    虚引用( PhantomReferenc)

    虚引用 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
    虚引用与软引用和弱引用的一个区别在于:
    虚引用必须和引用队列( ReferenceQueue )联合使用 。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    展开全文
  • java中引用类型的变量和对象的区别

    千次阅读 2017-05-23 09:07:20
    java中引用类型的变量和对象的区别。
    对象:是按照“类”这个模板建立的,建立的位置则是内存
    例如:A是一个类。A a则是创建一个引用变量,a=NEW A()则是在内存中建立一个对象(开辟了1片空间)
    ,对象的模板就是A(可以理解为在空间里复制了A类里的变量到空间里去)。此时就可以使用引用变量a
    去引用对象中的变量了。
    展开全文
  • Java中 引用类型初始化后未赋值之前的值为null 基本数据类型 byte short int long boolean char float double import java.util.Arrays; public class Test { public static byte byte1; public static short s; ...

    Java中

    引用类型初始化后未赋值之前的值为null

    基本数据类型
    byte、short、int、long、boolean、char、float、double
    这里要注意char类型初始化之后的默认值为空白(注意:不是空格)

    import java.util.Arrays;
    public class Test {
    	public static byte byte1;
    	public static short s;
    	public static int i;
    	public static long l;
    	public static char c;
    	public static boolean boolean1;
    	public static float f;
    	public static double d;
    	
    	@org.junit.Test
    	public void test() throws Exception {
    		System.out.println("byte:" + byte1); //byte:0
    		System.out.println("short:" + s); //s:0
    		System.out.println("int:" + i); //i:0
    		System.out.println("long:" + l); //l:0
    		System.out.println("char:" + c); //char:
    		System.out.println("boolean:" + boolean1); //boolean:false
    		System.out.println("float:" + f); //l:0.0
    		System.out.println("double:" + d); //l:0.0
    	}
    }
    
    
    展开全文
  • java中引用类型的数据,传递的是内存地址,像类,数组,接口,String等等都是引用类型!看下面的代码和截图!public class Test2 { // java中引用类型的数据传递的是内存地址 private Map&lt;String, Student&...
  • java中引用类型都有哪些 Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。  强引用(StrongReference) 这个就不多说,我们写代码天天在用的就是强引用。如果一个...
  • Java中引用类型的参数也是值传递

    千次阅读 2019-03-14 20:28:13
    Java引用类型作为参数传递给方法的时候,发生的仅仅是将引用类型变量的值复制一份给方法,让方法的参数变量也指向内存的对象区域。
  • Java中引用类型和值类型的不同

    千次阅读 2016-08-13 22:06:33
    Java编程过程,经常有人会因为没有弄清楚引用类型与值类型的区别而导致各种稀奇古怪的Bug出现,而且出现了还不知道问题在哪里。这里将简单阐述一下两者的区别。 引用数据类型:该类型指向一个对象,而不是原始...
  • java中引用类型和基本类型区别

    千次阅读 2018-12-03 20:06:58
    参考:https://www.cnblogs.com/bekeyuan123/p/7468845.html https://blog.csdn.net/ldsh304/article/details/75332016
  • 2、java中【基本类型】 和 【引用类型】 的比较编译后也都是基本类型和基本类型的比较。 longj=10000l; Long i=new Long(10000); System.out.println(i==j);//true 所以引用类型和基本类型比较,编译成...
  • 什么情况下使用定义好的类或者接口来定义 变量或者方法,为什么要这样做?求大神解释,解释的越详细越好
  • 引用类型表示你操作的是同一数据,传入一个参数给另一个方法,如果在方法修改该变量,当再次调用这个方法传入参数的时候,变量的值会改变。 值类型表示复制一个当前变量传给方法,在方法修改变量之后,最初的...
  • 之前听说说成员的基本类型是在堆分配空间,而如果是在函数,那么就是在栈的局部变量表分配空间,是这样吗?那么引用类型呢?和基本类型一样吗?
  • Java中引用类型和原始类型的区分

    千次阅读 2011-07-31 12:26:50
    Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。另外, Java还为每个原始类型提供了封装类(Wrapper)。如果需要一个整型变量,是使用基本的int 型呢,还是使用 Integer 类的一个对象呢?如果需要声明...
  • 一.Java中什么叫做引用类型变量? 引用:就是按内存地址查询  比如:String s = new String();这个其实是在栈内存里分配一块内存空间为s,在堆内存里 new了一个String类型的空间,在运行时是 栈内存里的...
  • 所谓的对象数组,就是指包含了一组相关的对象,但是在对象数组的使用一定要清楚一点:数组一定要先开辟空间,但是因为其是引用数据类型,所以数组里面的每一个对象都是null值,则在使用的时候数组的每一个对象...
  • How:Java中引用类型作为形参

    千次阅读 2012-04-15 20:06:00
    首先来看结论:  1.通过调用一个方法无法改变一个基本类型的变量值  2.通过调用一个方法可以改变一个引用类型所指向对象的成员变量的值 再来看结论的另一种表达: ...严格来说,在Java中所有的参数都是值传递
  • HashMap存在内存泄漏?Java引用为什么有一部分在堆?这到底是人性的扭曲还是道德的沦丧?欢迎观看此博客,了解java内存管理背后你不知道的故事。
  • 父类强转成子类会报异常,但是如果是向上转型后,再向下转型则不会报错,并且该对象能调用父类和子类全部的方法但只能调用在子类重写的父类方法; Son son =(Son) new Father()会报异常,但Father father =new Son...
  • JAVA 四中引用类型

    万次阅读 2019-07-09 12:22:12
    Java 最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 40,569
精华内容 16,227
关键字:

java中引用类型

java 订阅