java的四种引用,强弱软虚,用到的场景 (r)

本文详细介绍了Java中的四种引用类型:强引用、软引用、弱引用和虚引用的特点及应用场景,包括如何利用这些引用类型来优化内存管理和实现内存敏感的高速缓存。


1、强引用(StrongReference)
   

      强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:                                   

1

Object o=new Object();   //  强引用

当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

1

o=null;     // 帮助垃圾收集器回收此对象

  显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

举例:

1

2

3

4

public void test(){

    Object o=new Object();

    // 省略其他操作

}

在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。

但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

1

2

3

4

5

6

7

8

private transient Object[] elementData;

public void clear() {

        modCount++;

        // Let gc do its work

        for (int i = 0; i < size; i++)

            elementData[i] = null;

        size = 0;

}

在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

 

2、软引用(SoftReference)

    

        如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。   

1

2

 String str=new String("abc");                                     // 强引用

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

  当内存不足时,等价于:

1

2

3

4

If(JVM.内存不足()) {

   str = null;  // 转换为软引用

   System.gc(); // 垃圾回收器进行回收

}

虚引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

这时候就可以使用软引用

1

2

3

4

5

6

7

8

Browser prev = new Browser();               // 获取页面进行浏览

SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用    

if(sr.get()!=null){ 

    rev = (Browser) sr.get();           // 还没有被回收器回收,直接获取

}else{

    prev = new Browser();               // 由于内存吃紧,所以对软引用的对象回收了

    sr = new SoftReference(prev);       // 重新构建

}

这样就很好的解决了实际的问题。

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

3、弱引用(WeakReference)

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

1

2

3

String str=new String("abc");    

WeakReference<String> abcWeakRef = new WeakReference<String>(str);

str=null;

当垃圾回收器进行扫描回收时等价于:

?

1

2

str = null;

System.gc();

 如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。   

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

1

String  abc = abcWeakRef.get();

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

当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。

这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

public class ReferenceTest {

 

    private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();

 

    public static void checkQueue() {

        Reference<? extends VeryBig> ref = null;

        while ((ref = rq.poll()) != null) {

            if (ref != null) {

                System.out.println("In queue: "   + ((VeryBigWeakReference) (ref)).id);

            }

        }

    }

 

    public static void main(String args[]) {

        int size = 3;

        LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();

        for (int i = 0; i < size; i++) {

            weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));

            System.out.println("Just created weak: " + weakList.getLast());

 

        }

 

        System.gc(); 

        try { // 下面休息几分钟,让上面的垃圾回收线程运行完成

            Thread.currentThread().sleep(6000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        checkQueue();

    }

}

 

class VeryBig {

    public String id;

    // 占用空间,让线程进行回收

    byte[] b = new byte[2 * 1024];

 

    public VeryBig(String id) {

        this.id = id;

    }

 

    protected void finalize() {

        System.out.println("Finalizing VeryBig " + id);

    }

}

 

class VeryBigWeakReference extends WeakReference<VeryBig> {

    public String id;

 

    public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {

        super(big, rq);

        this.id = big.id;

    }

 

    protected void finalize() {

        System.out.println("Finalizing VeryBigWeakReference " + id);

    }

}

最后的输出结果为:

1

2

3

4

5

6

7

8

9

Just created weak: com.javabase.reference.VeryBigWeakReference@1641c0

Just created weak: com.javabase.reference.VeryBigWeakReference@136ab79

Just created weak: com.javabase.reference.VeryBigWeakReference@33c1aa

Finalizing VeryBig Weak 2

Finalizing VeryBig Weak 1

Finalizing VeryBig Weak 0

In queue: Weak 1

In queue: Weak 2

In queue: Weak 0

4、虚引用(PhantomReference)

     “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

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

5、总结

 Java4种引用的级别由高到低依次为:

强引用  >  软引用  >  弱引用  >  虚引用

通过图来看一下他们之间在垃圾回收时的区别:

当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收,如果对这方面不是很了解,可以参考如下的文章:

传送门:Java内存管理  http://blog.youkuaiyun.com/mazhimazh/article/category/1907599

通过表格来说明一下,如下:


参考文献:

 

1、http://www.cnblogs.com/skywang12345/p/3154474.html

2、http://blog.youkuaiyun.com/lifetragedy?viewmode=contents

<think>我们正在为Java面试准备常见问题及答案总结。根据引用内容,我们可以整理出一些常见的Java面试问题。引用[2]中提供了一个包含40道Java基础面试题的列表,我们可以以此为基础进行扩展和整理。同时,引用[1]提到了一些重要的话题,如并发概念、JVM内部、32位与64位JVM的区别、单元测试及整洁代码等。引用[3]则提到了单例模式的应用场景和线程状态等问题。 因此,我们可以将问题分为几个大类:Java基础、面向对象、集合框架、并发、JVM、设计模式等。 下面我们将按照类别整理常见问题及答案。注意:由于答案需要详细,但这里我们只给出问题列表,并在每个问题后提供简要答案要点(实际准备时需详细回答)。同时,根据要求,我们使用中文,并且数学表达式使用$...$或$$...$$格式(虽然Java问题中数学表达式较少,但如果有则遵守规则)。 ### 一、Java基础 1. **八种基本数据类型的大小,以及他们的封装类** - 答案要点:byte(1)、short(2)、int(4)、long(8)、float(4)、double(8)、char(2)、boolean(未定);对应的封装类:Byte, Short, Integer, Long, Float, Double, Character, Boolean。 2. **引用数据类型** - 答案要点:类、接口、数组。 3. **Switch能否用string做参数** - 答案要点:Java 7开始支持。 4. **equals与==的区别** - 答案要点:==比较两个对象的地址(引用类型)或值(基本类型),equals是Object类的方法,默认比较地址,可重写比较内容。 5. **自动装箱,常量池** - 答案要点:自动装箱是基本类型和其包装类之间的自动转换;常量池(如字符串常量池、整型常量池)用于共享常用值。 6. **Object有哪些公用方法** - 答案要点:toString(), equals(), hashCode(), getClass(), clone(), finalize(), wait(), notify(), notifyAll()。 7. **Java四种引用强弱用到场景** - 答案要点:强引用(不会被回收)、引用(内存不足时回收)、弱引用(GC时回收)、引用(用于跟踪对象被回收的状态)。 8. **Hashcode的作用** - 答案要点:用于快速确定对象在哈希表中的位置。 9. **HashMap的hashcode的作用** - 答案要点:确定键值对在数组中的索引位置。 10. **为什么重载hashCode方法?** - 答案要点:为了保持和equals方法的一致性,即两个对象equals相等,则hashCode必须相等。 11. **ArrayList、LinkedList、Vector的区别** - 答案要点:ArrayList基于动态数组,随机访问快,插入删除慢;LinkedList基于双向链表,插入删除快,随机访问慢;Vector是线程安全的ArrayList。 12. **String、StringBuffer与StringBuilder的区别** - 答案要点:String不可变;StringBuffer和StringBuilder可变,StringBuffer线程安全(方法加synchronized),StringBuilder非线程安全。 13. **Map、Set、List、Queue、Stack的特点与用法** - 答案要点:Map键值对;Set无序不重复;List有序可重复;Queue队列;Stack栈(Vector子类)。 14. **HashMap和HashTable的区别** - 答案要点:HashMap非线程安全,允许null键值;HashTable线程安全(方法加synchronized),不允许null键值。 15. **JDK7与JDK8中HashMap的实现** - 答案要点:JDK7:数组+链表;JDK8:数组+链表/红黑树(当链表长度大于阈值时转换为红黑树)。 16. **HashMap和ConcurrentHashMap的区别,HashMap的底层源码** - 答案要点:ConcurrentHashMap线程安全(使用分段锁或CAS+synchronized),HashMap非线程安全。 17. **ConcurrentHashMap能完全替代HashTable吗** - 答案要点:可以,因为ConcurrentHashMap性能更好。 18. **为什么HashMap是线程不安全的** - 答案要点:多线程同时扩容可能导致死循环(JDK7)或数据覆盖(JDK8)。 19. **如何线程安全的使用HashMap** - 答案要点:使用Collections.synchronizedMap或ConcurrentHashMap。 20. **多并发情况下HashMap是否还会产生死循环** - 答案要点:JDK7中可能,JDK8已解决死循环问题,但仍有数据覆盖问题。 21. **TreeMap、HashMap、LinkedHashMap的区别** - 答案要点:TreeMap基于红黑树,有序;HashMap无序;LinkedHashMap保持插入顺序或访问顺序。 22. **Collection包结构,与Collections的区别** - 答案要点:Collection是集合接口,Collections是工具类。 23. **try-catch-finally,try里有return,finally还执行么** - 答案要点:finally会执行,如果finally中也有return,则覆盖try中的return。 24. **Exception与Error包结构,OOM你遇到过哪些情况,SOF你遇到过哪些情况** - 答案要点:Exception和Error都继承Throwable;OOM:堆溢出、方法区溢出等;SOF:栈溢出。 25. **Java(OOP)面向对象的三个特征与含义** - 答案要点:封装、继承、多态。 26. **Override和Overload的含义与区别** - 答案要点:Override重写(子类覆盖父类方法),Overload重载(同一类中方法名相同参数不同)。 27. **Interface与abstract类的区别** - 答案要点:接口多继承,不能有方法体(Java8可以有default方法);抽象类单继承,可以有抽象方法和具体方法。 28. **Static class 与 non static class的区别** - 答案要点:静态内部类不持有外部类引用,非静态内部类持有。 29. **java多态的实现原理** - 答案要点:基于继承和接口,通过方法表(方法表)实现。 30. **foreach与正常for循环效率对比** - 答案要点:对于数组,for循环效率略高;对于集合,foreach使用迭代器,效率与for循环接近。 31. **Java IO与NIO** - 答案要点:IO是同步阻塞,NIO是同步非阻塞(多路复用)。 32. **java反射的作用于原理** - 答案要点:在运行时获取类的信息并操作类,原理:通过Class对象。 33. **泛型常用特点解析** - 答案要点:类型安全,编译时检查,避免强制类型转换。 34. **XML的几种方式的原理与特点:DOM、SAX** - 答案要点:DOM将整个文档加载到内存形成树结构;SAX事件驱动,逐行解析。 35. **Java1.7与1.8,1.9,10 新特性** - 答案要点:Java7:try-with-resources;Java8:Lambda、Stream API;Java9:模块化;Java10:局部变量类型推断。 ### 二、设计模式 36. **设计模式:单例、工厂、适配器、责任链、观察者等** - 答案要点:单例模式(一个类只有一个实例)、工厂模式(创建对象)、适配器模式(接口转换)等。 37. **单例模式的应用场景** - 答案要点:如任务管理器、回收站、数据库连接池等(引用[3])。 ### 三、并发 38. **线程有哪几种状态?** - 答案要点:新建(New)、可运行(Runnable)、阻塞(Blocked)、等待(Waiting)、计时等待(Timed Waiting)、终止(Terminated)。 39. **并发概念** - 答案要点:包括线程安全、锁、同步等。 40. **JVM内部,32位JVM和64位JVM的区别** - 答案要点:32位JVM最大内存约4G,64位支持更大内存;64位对象引用占用空间更大。 ### 四、其他 41. **单元测试及整洁代码** - 答案要点:单元测试框架如JUnit;整洁代码原则如可读性、可维护性等。 42. **JNI的使用** - 答案要点:Java本地接口,用于调用本地方法(如C/C++)。 43. **AOP是什么** - 答案要点:面向切面编程,将横切关注点(如日志、事务)与业务逻辑分离。 44. **OOP是什么** - 答案要点:面向对象编程。 45. **AOP与OOP的区别** - 答案要点:OOP关注对象和类,AOP关注横切关注点。 以上问题覆盖了引用中提到的内容,并按照类别进行了整理。实际准备时,每个问题都需要详细回答,并可以结合代码示例。 §§相关问题§§ 1. Java中如何实现线程安全? 2. 请详细解释Java内存模型(JMM)? 3. 垃圾回收算法有哪些?各自的特点是什么? 4. 如何排查Java中的内存泄漏问题? 5. 请解释Java类加载机制?</think>以下是Java面试常见问题及答案总结,结合高频考点和实际面试需求整理,涵盖基础、集合、并发、JVM、设计模式等核心领域: --- ### **一、Java基础** 1. **八种基本数据类型及封装类** - 类型:`byte`(1字节)、`short`(2)、`int`(4)、`long`(8)、`float`(4)、`double`(8)、`char`(2)、`boolean`(未明确定义) - 封装类:`Byte`, `Short`, `Integer`, `Long`, `Float`, `Double`, `Character`, `Boolean` - **自动装箱/拆箱原理**:编译器自动转换基本类型与封装类(例:`Integer i = 10` → `Integer.valueOf(10)`) 2. **`equals()`与`==`的区别** - `==`:比较对象内存地址(引用类型)或值(基本类型) - `equals()`:默认比较地址,可重写(如`String`重写为比较内容) - **重写规范**:若重写`equals()`,必须重写`hashCode()`(确保相同对象哈希值相同) 3. **`String`、`StringBuilder`、`StringBuffer`区别** | 类 | 可变性 | 线程安全 | 性能 | |-------------|--------|----------|------------| | `String` | 不可变 | ✅ | 低(频繁拼接) | | `StringBuilder`| 可变 | ❌ | 高 | | `StringBuffer` | 可变 | ✅(synchronized)| 中 | 4. **`try-catch-finally`执行顺序** ```java try { System.out.println("try"); // 可能抛出异常 return 1; } catch (Exception e) { return 2; } finally { System.out.println("finally"); // 始终执行,若finally有return则覆盖try/catch的返回值 } ``` --- ### **二、面向对象** 1. **多态的实现原理** - **编译时**:方法重载(Overload,静态绑定) - **运行时**:方法重写(Override,动态绑定),基于JVM方法表(方法表) - 示例:父类引用指向子类对象 `Animal a = new Dog(); a.sound();` 2. **接口 vs 抽象类** | 特性 | 接口 (`interface`) | 抽象类 (`abstract class`) | |--------------|--------------------------|---------------------------| | 方法实现 | Java 8+支持`default`方法 | 可包含具体方法 | | 变量 | 默认`public static final`| 无限制 | | 继承 | 多继承(实现多个接口) | 单继承 | | 构造器 | ❌ | ✅ | 3. **四种引用类型** - **强引用**:`Object obj = new Object()`(GC不回收) - **引用**:`SoftReference`(内存不足时回收,适合缓存) - **弱引用**:`WeakReference`(GC即回收,如`WeakHashMap`) - **引用**:`PhantomReference`(对象回收跟踪,需与`ReferenceQueue`配合) --- ### **三、集合框架** 1. **`HashMap`原理(JDK8)** - **结构**:数组 + 链表/红黑树(链表长度≥8时转红黑树) - **哈希冲突**:拉链法解决 - **扩容**:负载因子0.75,扩容时重新哈希(2倍扩容) - **线程不安全场景**:多线程扩容导致循环链表(JDK7)或数据覆盖(JDK8)[^2] 2. **`ConcurrentHashMap`线程安全实现** - **JDK7**:分段锁(Segment) - **JDK8**:CAS + synchronized(锁单个桶) - **对比`HashTable`**:锁粒度更细,性能更高(完全可替代`HashTable`) 3. **`ArrayList` vs `LinkedList`** | 操作 | `ArrayList` | `LinkedList` | |--------------|-------------------|-------------------| | 随机访问 | $O(1)$(数组) | $O(n)$(链表遍历)| | 头部插入 | $O(n)$(数据搬移)| $O(1)$ | | 内存占用 | 低(连续内存) | 高(节点指针) | --- ### **四、并发编程** 1. **线程状态转换** ```mermaid graph LR A[新建 NEW] --> B[可运行 RUNNABLE] B --> C[阻塞 BLOCKED](等锁) B --> D[等待 WAITING](wait/join) B --> E[超时等待 TIMED_WAITING](sleep(n)) C & D & E --> B B --> F[终止 TERMINATED] ``` 2. **`synchronized`优化(锁升级)** - 无锁 → 偏向锁(单线程) → 轻量级锁(CAS) → 重量级锁(OS互斥量) - **锁粗化/锁消除**:JIT编译器优化减少锁开销 3. **`volatile`关键字** - 保证可见性(直接读写主内存) - 禁止指令重排序(内存屏障) - **不保证原子性**(需配合`AtomicInteger`等) --- ### **五、JVM** 1. **内存区域** - **线程私有**:程序计数器、拟机栈、本地方法栈 - **线程共享**:堆(对象实例)、方法区(类信息) - **直接内存**:NIO使用的堆外内存(避免JVM堆与Native堆数据拷贝) 2. **垃圾回收算法** - **标记-清除**:碎片问题 - **复制算法**:新生代(Eden-S0-S1区) - **标记-整理**:老年代 - **分代收集**:结合年轻代(Minor GC)和老年代(Full GC)策略 3. **类加载过程** 1. 加载 → 2. 验证 → 3. 准备 → 4. 解析 → 5. 初始化 - **双亲委派模型**:避免类重复加载(`Application → Extension → Bootstrap`类加载器) --- ### **六、设计模式** 1. **单例模式实现** ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 双重检查锁定 synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` - **应用场景**:数据库连接池、日志管理器[^3] 2. **工厂模式** - **简单工厂**:一个工厂类创建多种产品 - **工厂方法**:每个产品对应一个工厂 - **抽象工厂**:创建产品族(如不同风格的UI组件) --- ### **七、新特性** - **Java 8**:Lambda表达式、Stream API、`Optional` - **Java 11**:HTTP Client API、局部变量类型推断(`var`) - **Java 17**:密封类(`sealed class`)、模式匹配(`instanceof`自动转型) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值