`

Java基本功之Reference详解

阅读更多

有这样一种说法,如今争锋于IT战场的两大势力,MS一族偏重于底层实现,Java一族偏重于系统架构。说法根据无从考证,但从两大势力各自的社区力量和图书市场已有佳作不难看出,此说法不虚。于是,事情的另一面让人忽略了。偏巧,我是一个喜欢探究底层实现的Java程序员,虽然我的喜好并非纯正咖啡,剑走偏锋却别是一番风味。
  
  Reference Java世界泰山北斗级大作《Thinking In Java》切入Java就提出“Everything is Object”。在Java这个充满Object的世界中,reference是一切谜题的根源,所有的故事都是从这里开始的。
  
  Reference是什么?
  
  如果你和我一样在进入Java世界之前曾经浪迹于C/C++世界,就一定不会对指针陌生。谈到指针,往日种种不堪回首的经历一下子涌上心头,这里不是抱怨的地方,让我们暂时忘记指针的痛苦,回忆一下最初接触指针的甜蜜吧!
  
  还记得你看过的教科书中,如何讲解指针吗?留在我印象中的一种说法是,指针就是地址,如同门牌号码一样,有了地址,你可以轻而易举找到一个人家,而不必费尽心力的大海捞针。C++登上历史舞台,reference也随之而来,容我问个小问题,指针和reference区别何在?我的答案来自于在C++世界享誉盛名的《More Effective C++》。
  
  没有null reference,reference必须有初值。
  
  使用reference要比使用指针效率高。因为reference不需要测试其有效性。指针可以重新赋值,而reference总是指向它最初获得的对象。
  
  设计选择:
  
  当你指向你需要指向的某个东西,而且绝不会改指向其它东西,或是当你实作一个运算符而其语法需要无法有指针达成,你就应该选择reference。其它任何时候,请采用指针。
  
  这和Java有什么关系?
  
  初学Java,鉴于reference的名称,我毫不犹豫的将它和C++中的reference等同起来。不过,我错了。在Java中,reference 可以随心所欲的赋值置空,对比一下上面列出的差异,就不难发现,Java的reference如果要与C/C++对应,它不过是一个穿着 reference外衣的指针而已。于是,所有关于C中关于指针的理解方式,可以照搬到Java中,简而言之,reference就是一个地址。我们可以把它想象成一个把手,抓住它,就抓住了我们想要操纵的数据。如同掌握C的关键在于掌握指针,探索Java的钥匙就是reference。
  
  一段小程序
  
  我知道,太多的文字总是令人犯困,那就来段代码吧!
  
  public class ReferenceTricks
  {
  public static void main(String[] args)
  {
  ReferenceTricks r = new ReferenceTricks();
  // reset integer
  r.i = 0;
  System.out.println
  ("Before changeInteger:" + r.i);
  changeInteger(r);
  System.out.println
  ("After changeInteger:" + r.i);
  
  // just for format
  System.out.println();
  
  // reset integer
  r.i = 0;
  System.out.println
  ("Before changeReference:" + r.i);
  changeReference(r);
  System.out.println
  ("After changeReference:" + r.i);
  }
  
  private static void
  changeReference(ReferenceTricks r)
  {
  r = new ReferenceTricks();
  r.i = 5;
  System.out.println
  ("In changeReference: " + r.i);
  }
  
  private static void
  changeInteger(ReferenceTricks r)
  {
  r.i = 5;
  System.out.println
  ("In changeInteger:" + r.i);
  }
  
  public int i;
  }
  
  我知道,把一个字段设成public是一种不好的编码习惯,这里只是为了说明问题。如果你有兴趣自己运行一下这个程序。你已经运行过了吗?结果如何?是否如你预期?下面是我在自己的机器上运行的结果:
  
  Before changeInteger:0
  In changeInteger:5
  After changeInteger:5
  
  Before changeReference:0
  In changeReference: 5
  After changeReference:0
  
  这里,我们关注的是两个change,changeReference和changeInteger。从输出的内容中,我们可以看出,两个方法在调用前和调用中完全一样,差异出现在调用后的结果。
  
  糊涂的讲解
  
  先让我们来分析一下changeInteger的行为。
  
  前面说过了,Java中的reference就是一个地址,它指向了一个内存空间,这个空间存放着一个对象的相关信息。这里我们暂时不去关心这个内存具体如何排布,只要知道,通过地址,我们可以找到r这个对象的i字段,然后我们给它赋成5。
  
  既然这个字段的内容得到了修改,从函数中返回之后,它自然就是改动后的结果了,所以调用之后,r对象的i字段依然是5。下图展示了changeInteger调用前后内存变化。
  
  让我们把目光转向changeReference。从代码上,我们可以看出,同changeInteger之间的差别仅仅在于多了这么一句:
  
  r = new ReferenceTricks();
  
  这条语句的作用是分配一块新的内存,然后将r指向它。执行完这条语句,r就不再是原来的r,但它依然是一个ReferenceTricks的对象,所以我们依然可以对这个r的i字段赋值。到此为止,一切都是那么自然。
  
  顺着这个思路继续下去的话,执行完changeReference,输出的r的i字段,那么应该是应该是新内存中的i,所以应该是5。至于那块被我们抛弃的内存,Java的GC功能自然会替我们善后的。
  
  事与愿违。
  
  实际的结果我们已经看到了,输出的是0。肯定哪个地方错了,究竟是哪个地方呢?
  
  参数传递的秘密
  
  知道方法参数如何传递吗?
  
  记得刚开始学编程那会儿,老师教导,所谓参数,有形式参数和实际参数之分,参数列表中写的那些东西都叫形式参数,在实际调用的时候,它们会被实际参数所替代。
  
  编译程序不可能知道每次调用的实际参数都是什么,于是写编译器的高手就出个办法,让实际参数按照一定顺序放到一个大家都可以找得到的地方,以此作为方法调用的一种约定。所谓“没有规矩,不成方圆”,有了这个规矩,大家协作起来就容易多了。这个公共数据区,现在编译器的选择通常是“栈”,而所谓的顺序就是形式参数声明的顺序。
  
  显然,程序运行的过程中,作为实际参数的变量可能遍布于内存的各个位置,而并不一定要老老实实的呆在栈里。为了守“规矩”,程序只好将变量复制一份到栈中,也就是通常所说的将参数压入栈中。
  
  我刚才说什么来着?将变量复制一份到栈中,没错,“复制”!
  
  这就是所谓的值传递。
  
  C语言的旷世经典《The C Programming Language》开篇的第一章中,谈到实际参数时说,“在C中,所有函数的实际参数都是传‘值’的”。
  
  马上会有人站出来,“错了,还有传地址,比如以指针传递就是传地址”。不错,传指针就是传地址。在把指针视为地址的时候,是否考虑过这样一个问题,它也是一个变量。前面的讨论中说过了,参数传递必须要把参数压入栈中,作为地址的指针也不例外。所以,必须把这个指针也复制一份。函数中对于指针操作实际上是对于这个指针副本的操作。
  
  Java的reference等于C的指针。所以,在Java的方法调用中,reference也要复制一份压入堆栈。在方法中对reference的操作就是对这个reference副本的操作。
  
  谜底揭晓
  
  好,让我们回到最初的问题上。在changeReference中对于reference的赋值实际上是对这个reference的副本进行赋值,而对于reference的本尊没有产生丝毫的影响。
  
  回到调用点,本尊醒来,它并不知道自己睡去的这段时间内发生过什么,所以只好当作什么都没发生过一般。就这样,副本消失了,在方法中对它的修改也就烟消云散了。
  
  也许你会问出这样的问题,“听了你的解释,我反而对changeInteger感到迷惑了,既然是对于副本的操作,为什么changeInteger可以运作正常?”这是很有趣的现象。
  
  好,那我就用前面的说法解释一下changeInteger的运作。所谓复制,其结果必然是副本完全等同于本尊。reference复制的结果必然是两个reference指向同一块内存空间。
  
  虽然在方法中对于副本的操作并不会影响到本尊,但对内存空间的修改确实实实在在的,回到调用点,虽然本尊依然不知道曾经发生过的一切,但它按照原来的方式访问内存的时候,取到的确是经过方法修改之后的内容。于是方法可以把自己的影响扩展到方法之外。
  
  多说几句
  
  这个问题起源于我对C/C++中同样问题的思考。同C/C++相比,在changeReference中对reference赋值可能并不会造成什么很严重的后果,而在C/C++中,这么做却会造成臭名昭著的“内存泄漏”,根本的原因在于Java拥有了可爱的GC功能。即便这样,我仍不推荐使用这种的手法,毕竟GC已经很忙了,我们怎么好意思再麻烦人家。
  
  在C/C++中,这个问题还可以继续引申。既然在函数中对于指针直接赋值行不通,那么如何在函数中修改指针呢?答案很简单,指针的指针,也就是把原来的指针看作一个普通的数据,把一个指向它的指针传到函数中就可以了。
  
  同样的问题到了Java中就没有那么美妙的解决方案了,因为Java中可没有reference的reference这样的语法。可能的变通就是将reference进行封装成类。至于值不值,公道自在人心。

 

http://www.iteye.com/topic/12961

 

 

  在 jdk 1.2 及其以后,引入了强引用、软引用、弱引用、虚引用这四个概念。网上很多关于这四个概念的解释,但大多是概念性的泛泛而谈,今天我结合着代码分析了一下,首先我们先来看定义与大概解释(引用类型在包 java.lang.ref 里)。
  1、强引用(StrongReference)
    强引用不会被GC回收,并且在java.lang.ref里也没有实际的对应类型。举个例子来说:
    Object obj = new Object();
    这里的obj引用便是一个强引用,不会被GC回收。
  2、软引用(SoftReference)
    软引用在JVM报告内存不足的时候才会被GC回收,否则不会回收,正是由于这种特性软引用在caching和pooling中用处广泛。软引用的用法:
1     Object obj = new Object(); 
2     WeakReference<Object> softRef = new WeakReference(obj); 
3     // 使用 softRef.get() 获取软引用所引用的对象 
4     Object objg = softRef.get();
  3、弱引用(WeakReference)
    当GC一但发现了弱引用对象,将会释放WeakReference所引用的对象。弱引用使用方法与软引用类似,但回收策略不同。
  4、虚引用(PhantomReference)
    当GC一但发现了虚引用对象,将会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference所指向的对象并没有被GC回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。虚引用的用法:
1 Object obj = new Object(); 
2 ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); 
3 PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue); 
4 // 调用phanRef.get()不管在什么情况下会一直返回null 
5 Object objg = phanRef.get(); 
6 // 如果obj被置为null,当GC发现了虚引用,GC会将phanRef插入进我们之前创建时传入的refQueue队列 
7 // 注意,此时phanRef所引用的obj对象,并没有被GC回收,在我们显式地调用refQueue.poll返回phanRef之后 
8 // 当GC第二次发现虚引用,而此时JVM将phanRef插入到refQueue会插入失败,此时GC才会对obj进行回收 
9 Reference<? extends Object> phanRefP = refQueue.poll();
看了简单的定义之后,我们结合着代码来测试一下,强引用就不用说了,软引用的描述也很清楚,关键是 “弱引用” 与 “虚引用”。
弱引用:
01 public static void main(String[] args) throws InterruptedException { 
02     Object obj = new Object(); 
03     ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); 
04     WeakReference<Object> weakRef = new WeakReference<Object>(obj, refQueue); 
05     System.out.println(weakRef.get()); 
06     System.out.println(refQueue.poll()); 
07     obj = null; 
08     System.gc(); 
09     System.out.println(weakRef.get()); 
10     System.out.println(refQueue.poll()); 
11 }
由于System.gc()是告诉JVM这是一个执行GC的好时机,但具体执不执行由JVM决定,因此当JVM决定执行GC,得到的结果便是(事实上这段代码一般都会执行GC):
  java.lang.Object@de6ced
  null
  null
  java.lang.ref.WeakReference@1fb8ee3
从执行结果得知,通过调用weakRef.get()我们得到了obj对象,由于没有执行GC,因此refQueue.poll()返回的null,当我们把obj = null;此时没有引用指向堆中的obj对象了,这里JVM执行了一次GC,我们通过weakRef.get()发现返回了null,而refQueue.poll()返回了WeakReference对象,因此JVM在对obj进行了回收之后,才将weakRef插入到refQueue队列中。
虚引用:
01 public static void main(String[] args) throws InterruptedException { 
02     Object obj = new Object(); 
03     ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>(); 
04     PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue); 
05     System.out.println(phanRef.get()); 
06     System.out.println(refQueue.poll()); 
07     obj = null; 
08     System.gc(); 
09     System.out.println(phanRef.get()); 
10     System.out.println(refQueue.poll()); 
11 }
同样,当JVM执行了GC,得到的结果便是:
  null
  null
  null
  java.lang.ref.PhantomReference@1fb8ee3
从执行结果得知,我们先前说的没有错,phanRef.get()不管在什么情况下,都会返回null,而当JVM执行GC发现虚引用之后,JVM并没有回收obj,而是将PhantomReference对象插入到对应的虚引用队列refQueue中,当调用refQueue.poll()返回PhantomReference对象时,poll方法会先把PhantomReference的持有队列queue(ReferenceQueue<? super T>)置为NULL,NULL对象继承自ReferenceQueue,将enqueue(Reference paramReference)方法覆盖为return false,而此时obj再次被GC发现时,JVM再将PhantomReference插入到NULL队列中便会插入失败返回false,此时GC便会回收obj。事实上通过这段代码我们也的却看不出来obj是否被回收,但通过 PhantomReference 的javadoc注释中有一句是这样写的:
Once the garbage collector decides that an object obj is phantom-reachable, it is being enqueued on the corresponding queue, but its referent is not cleared. That is, the reference queue of the phantom reference must explicitly be processed by some application code.
翻译一下(这句话很简单,我相信很多人应该也看得懂):
一旦GC决定一个“obj”是虚可达的,它(指PhantomReference)将会被入队到对应的队列,但是它的指代并没有被清除。也就是说,虚引用的引用队列一定要明确地被一些应用程序代码所处理。
弱引用与虚引用的用处
  软引用很明显可以用来制作caching和pooling,而弱引用与虚引用呢?其实用处也很大,首先我们来看看弱引用,举个例子:
1 class Registry { 
2     private Set registeredObjects = new HashSet(); 
3  
4     public void register(Object object) { 
5         registeredObjects.add( object ); 
6     } 
7 }
所有我添加进 registeredObjects 中的object永远不会被GC回收,因为这里有个强引用保存在registeredObjects里,另一方面如果我把代码改为如下:
1 class Registry { 
2      private Set registeredObjects = new HashSet(); 
3   
4      public void register(Object object) { 
5          registeredObjects.add( new WeakReference(object) ); 
6      } 
7  }
  现在如果GC想要回收registeredObjects中的object,便能够实现了,同样在使用HashMap如果想实现如上的效果,一种更好的实现是使用WeakHashMap。
而虚引用呢?我们先来看看javadoc的部分说明:
Phantom references are useful for implementing cleanup operations that are necessary before an object gets garbage-collected. They are sometimes more flexible than the finalize() method.
翻译一下:
虚引用在实现一个对象被回收之前必须做清理操作是很有用的。有时候,他们比finalize()方法更灵活。
很明显的,虚引用可以用来做对象被回收之前的清理工作。

 

转自:http://blog.sina.com.cn/s/blog_667ac0360102e9f3.html

分享到:
评论

相关推荐

    Java功底之Reference

    在Java编程语言中,`Reference`类是一个非常特殊且重要的概念,它超出了常规的引用类型,如`Object`或数组引用。`Reference`类及其子类主要用于处理对象的软引用、弱引用和虚引用,这些引用类型在内存管理,特别是...

    Java The Complete Reference 10th Edition

    Java The Complete Reference 10th Edition Java9 编程官方参考(第10版) 带书签 文字版

    Java The Complete Reference 9th

    《Java The Complete Reference 9th》作为Java编程学习的权威指南,覆盖了Java语言的各个方面,包括Java的基本概念、语法、面向对象的特性、核心API、高级主题(如反射、注解、泛型等)、并发编程以及Java的新特性。...

    The Java Reference Library

    Java in a Nutshell Java Language Reference Java AWT Reference Java Fundamental Classes Reference Exploring Java Combined Index Combined Search Web Version Credits

    A Java Reference: Assorted Java Reference Material

    Your author likes to read reference manuals (believe it or not)—at least if they are reasonably complete—on the grounds that this is the most efficient way to absorb a language quickly.

    Java The Complete Reference, 11th Edition 9781260440232 c.pdf

    Java The Complete Reference, 11th Edition 9781260440232 c.pdf Java The Complete Reference, 11th Edition 9781260440232 c.pdf Java The Complete Reference, 11th Edition 9781260440232 c.pdf

    Java The Complete Reference ,11th Edition.pdf

    Java: The Complete Reference, Eleventh Edition By 作者: Herbert Schildt ISBN-10 书号: 1260440230 ISBN-13 书号: 9781260440232 Edition 版本: 11 出版日期: 2018-12-12 pages 页数: (1955) The Definitive ...

    java的primitive和reference类.docx

    Java Primitive 与 Reference 类型详解 Java 作为一种面向对象的语言,数据类型可以分为两类:Primitive 类型和 Reference 类型。Primitive 类型包括数字和布尔类型,不被看做对象,而是被称为基本类型。Reference ...

    Java:The Complete Reference,J2SE 5 Edition

    中文译名是J2SE参考大全(第5版) 这份是自己制作的电子书,现在还没有制作完成,因为内容太多,关打字就打个半死.

    Java Reference Sheet

    Java reference sheet

    Java内存模型详解

    ### Java内存模型详解 #### 1. JMM简介 ##### i. 内存模型概述 Java内存模型(Java Memory Model, JMM)是Java虚拟机(JVM)的一部分,用于规定程序中的各种变量(包括实例字段、静态字段和数组元素等)在多个...

    java基础案例与开发详解

    数据类型分为两大类:基本类型(Primitive Types)和引用类型(Reference Types)。基本类型包括整型(如int)、浮点型(如float、double)、字符型(char)和布尔型(boolean)。引用类型则包括类(Class)、接口...

    neo4j-java-reference-3.3

    在Neo4j-java-reference-3.3这份参考指南中,涵盖了多个关于如何在Java环境中使用和扩展Neo4j数据库的高级主题。文档内容涉及到如何嵌入Neo4j到Java应用中、使用Neo4j的遍历框架、手动索引、事务管理、在线备份以及...

    Neo4j Java Reference 3.0

    ### Neo4j Java Reference 3.0:深入理解图数据库扩展与高级应用 #### 概述 《Neo4j Java Reference 3.0》是一本详细介绍如何使用Java语言来开发和扩展Neo4j图数据库的专业指南。本书不仅覆盖了Neo4j的核心功能,...

    java中ThreadLocal详解

    ### Java中ThreadLocal详解 #### 一、ThreadLocal概述 在Java多线程编程中,`ThreadLocal`是一个非常重要的工具类,它提供了一种在每个线程内部存储线程私有实例的方法。通常情况下,当多个线程共享某个变量时,...

    Java 中 Reference用法详解

    Java 中 Reference 用法详解 Java 中的 Reference 类型主要分为四种:强引用、软引用、弱引用和虚引用。这些引用类型在 Java 中的使用非常广泛,特别是在缓存、池化和垃圾回收机制中。 强引用(Strong Reference)...

    Java AWT Reference

    ### Java AWT(Abstract Window Toolkit)详解:掌握Java图形用户界面设计的核心技能 #### 引言:Java AWT——构建用户界面的基石 在Java的世界里,Abstract Window Toolkit(AWT)扮演着至关重要的角色,它为Java...

    Java:The Complete Reference,J2SE 5 Edition

    中文译名是J2SE参考大全(第5版) 这份是自己制作的电子书,现在还没有制作完成,因为内容太多,关打字就打个半死.

    Java之reference-JAVA程序员JAVA工程师面试必看.pdf,这是一份不错的文件

    在Java编程语言中,"reference"是一个至关重要的概念,它涉及到对象的引用和内存管理。在Java的世界里,"Everything is Object",这意味着所有的数据都以对象的形式存在,而reference则是连接这些对象与程序代码的...

Global site tag (gtag.js) - Google Analytics