本文主要分三部分介绍 Java 中的值、指针与引用的概念。
第一部分从编程语言的三种参数传递方式入手,阐释“为什么 Java 中只有值传递”。
第二部分排除自动装箱和自动拆箱的干扰,理解 Integer 等封装类作为参数传值的情形。
第三部分通过简单的示例,展示强引用、软引用、弱引用和虚引用之间的区别。

一、参数传递方式

1.1 值传递

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。
从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。

public class IntegerTest01 {private static void changeInt(int value) {++value;}public static void main(String[] args) {int a = 1;changeInt(a);System.out.println("a = " + a);}
}

执行结果为a = 1

1.2 指针传递

Java 中没有指针,为了直观展示指针传递,这里使用了 C++ 的例子。
指针从本质上讲是一个变量,变量的值是另一个变量的地址。因此可以说指针传递属于值传递。

#include <iostream>
using namespace std;void fun(int *x) {// 声明指针*x += 5; // *x 是取得指针所指向的内存单元,即指针解引用// x += 5; 则对实参没有影响
}int main() {int y = 0;fun(&y);// 取地址cout<< "y =  "<< y <<endl;return 0;
}

执行结果为y = 5

Java 中的“指针”

《Head First Java》中关于 Java 参数传递的说明:

Java 中所传递的所有东西都是值,但此值是变量所携带的值。引用对象的变量所携带的是远程控制而不是对象本身,若你对方法传入参数,实际上传入的是远程控制的拷贝。

《深入理解 JVM 虚拟机》中关于 Sun HotSpot 虚拟机进行对象访问的方式的说明:

如果使用直接指针,那么 Java 堆对象的布局中就必须考虑如何放置访问对象类型数据的相关信息,而 reference 中存储的直接就是对象地址。

在 Java 中声明并初始化一个对象Object object = new Object(),在堆中存储对象实例数据,在栈中存储对象地址,这里的变量 object 相当于 C/C++ 中的指针。

因此,可以通过 Java 对象的引用,达到指针传递的效果。

public class IntegerTest02 {private static void changeInt(int[] value) {++value[0];}public static void main(String[] args) {int[] a = {1};changeInt(a);System.out.println("a[0] = " + a[0]);}
}

执行结果为a[0] = 2

1.3 引用传递

既然 Java 中没有引用传递,那么到底什么是引用传递呢,看下 C++ 中的例子。

#include <iostream>
using namespace std;void fun(int &x){// 声明一个别名x += 5; // 修改的是 x 引用的对象值 &x = y;
}int main()
{int y = 0;fun(y);cout<< "y =  "<< y <<endl;return 0;
}

执行结果y = 5

C++ 中的引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元

Java 中的引用

Java 中的引用是 reference 类型,类似于 C/C++ 中指针的概念,而跟 C/C++ 中引用的概念完全不同。

在 JDK 1.2 以前,Java 中的引用的定义:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。

在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

进一步的介绍见 Java 中的 Reference 类型

二、Integer 参数传递问题

回到开篇值传递的例子:

public class IntegerTest01 {private static void changeInt(int value) {++value;}public static void main(String[] args) {int a = 1;changeInt(a);System.out.println("a = " + a);}
}

如果把代码中的 int 类型换成 Integer 对象,结果会怎么样?

public class IntegerTest02 {private static void changeInteger(Integer value) {++value;}public static void main(String[] args) {Integer a = 1;changeInteger(a);System.out.println("a = " + a);}
}

首先需要排除自动装箱和自动拆箱的干扰。

2.1 自动装箱和自动拆箱

package com.sumkor.jdk7.integer02;public class IntegerTest {public static void main(String[] args) {Integer a = 1;int b = a;}
}

使用命令javap -c IntegerTest.class进行反编译:

Compiled from "IntegerTest.java"
public class com.sumkor.jdk7.integer02.IntegerTest {public com.sumkor.jdk7.integer02.IntegerTest();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: iconst_11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;4: astore_15: aload_16: invokevirtual #3                  // Method java/lang/Integer.intValue:()I9: istore_210: return
}

由此可知:
自动装箱实际调用的是Integer.valueOf
自动拆箱实际调用的是Integer.intValue

因此,排除自动装箱、自动拆箱,例子 IntegerTest02 等价于以下写法:

public class IntegerTest03 {private static void changeInteger(Integer value) {value = Integer.valueOf(value.intValue() + 1);}public static void main(String[] args) {Integer a = Integer.valueOf(1);changeInteger(a);}
}

查看 Integer 源码,可知valueOf()会将形参指向不同的 Integer 对象实例。

    /*** Returns an {@code Integer} instance representing the specified* {@code int} value.  If a new {@code Integer} instance is not* required, this method should generally be used in preference to* the constructor {@link #Integer(int)}, as this method is likely* to yield significantly better space and time performance by* caching frequently requested values.** This method will always cache values in the range -128 to 127,* inclusive, and may cache other values outside of this range.** @param  i an {@code int} value.* @return an {@code Integer} instance representing {@code i}.* @since  1.5*/public static Integer valueOf(int i) {assert IntegerCache.high >= 127;if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}/*** Cache to support the object identity semantics of autoboxing for values between* -128 and 127 (inclusive) as required by JLS.** The cache is initialized on first usage.  The size of the cache* may be controlled by the -XX:AutoBoxCacheMax=<size> option.* During VM initialization, java.lang.Integer.IntegerCache.high property* may be set and saved in the private system properties in the* sun.misc.VM class.*/private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");if (integerCacheHighPropValue != null) {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);}private IntegerCache() {}}

2.2 关于 IntegerCache

IntegerCache 在首次使用时被初始化,最小值为 -128,最大值默认为 127,也可以通过 VM 参数-XX:AutoBoxCacheMax=<size>设置最大值。

    @Testpublic void test01() {Integer a = 1;Integer b = 1;System.out.println(a == b);Integer aa = 128;Integer bb = 128;System.out.println(aa == bb);}

变量ab指向的是同一个IntegerCache.cache,因此比较结果为true.
变量aabb指向的是不同的 Integer 实例,因此比较结果为false.

三、Java 中的 Reference 类型

《深入理解 JVM 虚拟机》中对此的介绍为:

  • 强引用就是指在程序代码之中普遍存在的,类似Object object = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了 SoftReference 类来实现软引用。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了 WeakReference 类来实现弱引用。
  • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了 PhantomReference 类来实现虚引用。

Reference 类型的强度跟 JVM 垃圾回收有关,可惜书上没有给出实例,本文对此进行补充。

注意,以下例子中,使用 JDK 1.8,且均设置 JVM 参数为-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
即堆大小为 20 m,其中新生代大小为 10 m,按照 1:8 比例分配,Eden 区大小为 8 m。

3.1 强引用

/*** Created by Sumkor on 2018/9/10.*/
public class StrongReferenceTest {public static void main(String[] args) {byte[] allocation01 = new byte[1024 * 1024 * 9];byte[] allocation02 = new byte[1024 * 1024 * 9];}
}

执行结果如下,可知垃圾收集器宁愿抛出内存溢出异常,也不会回收正在使用中的强引用:

 [GC (Allocation Failure)  11197K->10032K(19456K), 0.0014301 secs][Full GC (Ergonomics)  10032K->9851K(19456K), 0.0072375 secs][GC (Allocation Failure)  9851K->9851K(19456K), 0.0004413 secs][Full GC (Allocation Failure)  9851K->9833K(19456K), 0.0093839 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat com.sumkor.reference.StrongReferenceTest.main(StrongReferenceTest.java:18)

3.2 软引用

@Test
public void test01() {byte[] allocation01 = new byte[1024 * 1024 * 8];SoftReference<byte[]> softReference = new SoftReference<byte[]>(allocation01);// 此时,对于这个byte数组对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量allocation01的强引用,所以这个数组对象是强可及对象。System.out.println("softReference.get() = " + softReference.get());allocation01 = null;// 结束变量allocation01对这个byte数组实例的强引用,此后该byte数组对象变成一个软可及对象,可以通过softReference进行访问System.out.println("softReference.get() = " + softReference.get());System.gc();System.out.println("softReference.get() = " + softReference.get());
}

执行结果如下,可见在触发 gc 时,内存空间充足,并不会回收软引用:

 softReference.get() = [B@5d6f64b1softReference.get() = [B@5d6f64b1[GC (System.gc())  14584K->9644K(19456K), 0.0040375 secs][Full GC (System.gc())  9644K->9508K(19456K), 0.0115994 secs]softReference.get() = [B@5d6f64b1

再来看内存不足的例子:

@Test
public void test02() {byte[] allocation01 = new byte[1024 * 1024 * 8];SoftReference<byte[]> softReference = new SoftReference<byte[]>(allocation01);// 此时,对于这个byte数组对象,有两个引用路径,一个是来自SoftReference对象的软引用,一个来自变量allocation01的强引用,所以这个数组对象是强可及对象。System.out.println("softReference.get() = " + softReference.get());allocation01 = null;// 结束变量allocation01对这个byte数组实例的强引用,此后该byte数组对象变成一个软可及对象,可以通过softReference进行访问System.out.println("softReference.get() = " + softReference.get());byte[] allocation02 = new byte[1024 * 1024 * 8];System.out.println("softReference.get() = " + softReference.get());
}

可见在触发 gc 时,内存空间不足,回收软引用:

 softReference.get() = [B@5d6f64b1softReference.get() = [B@5d6f64b1[GC (Allocation Failure)  14749K->9636K(19456K), 0.0056237 secs][GC (Allocation Failure)  9636K->9684K(19456K), 0.0014787 secs][Full GC (Allocation Failure)  9684K->9508K(19456K), 0.0128735 secs][GC (Allocation Failure)  9508K->9508K(19456K), 0.0006353 secs][Full GC (Allocation Failure)  9508K->1261K(19456K), 0.0107362 secs]softReference.get() = null

3.3 弱引用

package com.sumkor.reference;import java.lang.ref.WeakReference;/*** Created by Sumkor on 2018/9/10.*/
public class WeakReferenceTest {public static void main(String[] args) {byte[] allocation01 = new byte[1024 * 1024 * 8];WeakReference<byte[]> weakReference = new WeakReference<byte[]>(allocation01);System.out.println("weakReference.get() = " + weakReference.get());// [B@154ebaddallocation01 = null;System.out.println("weakReference.get() = " + weakReference.get());// [B@154ebaddSystem.gc();System.out.println("weakReference.get() = " + weakReference.get());// null}
}

执行结果如下,可见尽管内存空间充足,垃圾回收器工作时回收掉只被弱引用关联的对象:

 weakReference.get() = [B@14ae5a5weakReference.get() = [B@14ae5a5[GC (System.gc())  10177K->9008K(19456K), 0.0011390 secs][Full GC (System.gc())  9008K->643K(19456K), 0.0069800 secs]weakReference.get() = null

3.4 虚引用

package com.sumkor.reference;import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;/*** Created by Sumkor on 2018/9/10.*/
public class PhantomReferenceTest {public static void main(String[] args) throws InterruptedException {ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();byte[] allocation01 = new byte[1024 * 1024 * 8];PhantomReference<byte[]> phantom = new PhantomReference<>(allocation01, referenceQueue);allocation01 = null;Thread.currentThread().sleep(3000);System.gc();Thread.currentThread().sleep(3000);Reference<?> poll = referenceQueue.poll();System.out.println("poll = " + poll);// java.lang.ref.PhantomReference@5d6f64b1System.out.println("phantom.get() = " + phantom.get());}
}

执行结果如下,phantom.get()总是为 null,当 byte 数组对象被垃圾回收器回收时,垃圾收集器会把要回收的对象添加到引用队列ReferenceQueue,即得到一个“通知”:

 [GC (System.gc())  14742K->9608K(19456K), 0.0025841 secs][Full GC (System.gc())  9608K->9510K(19456K), 0.0117227 secs]poll = java.lang.ref.PhantomReference@5d6f64b1phantom.get() = null

Java 中的参数传递和引用类型相关推荐

  1. Java中的参数传递,到底是值传递还是引用传递?

    文章内容为转载,转载自公众号Hollis关于我要彻底给你讲清楚,Java就是值传递,不接受争辩的那种! 原文链接:我要彻底给你讲清楚,Java就是值传递,不接受争辩的那种! 知乎回答链接:Java 到 ...

  2. Java中的参数传递

    Java中的参数传递:分为值传递和引用传递 但本质上,Java中只有值传递.引用传递,其实可以理解为传的是类似指针的东西. 值传递就是把基本变量的值拷贝一份,传递这个拷贝.引用传递则是传递的引用的地址 ...

  3. java中date类型如何赋值_一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

  4. java什么是reference_如何理解java中的Reference和引用类型?正确的案例讲解

    java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟java垃圾 ...

  5. 一文读懂java中的Reference和引用类型

    简介 java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型.java为引用类型专门定义了一个类叫做Reference.Reference是跟jav ...

  6. java中引用类型_您真的了解Java中的4种引用类型吗?

    Java中提供了四个级别的引用:SoftReference,FinalReference,WeakReference和PhantomReference.在四种引用类型中,只有FinalReferenc ...

  7. Java中的参数传递 --Java

    1.基本类型传值,对象类型传地址 按值传递:当将一个参数传递给一个方法时,方法接收的是原始值的一个副本.因此,如果方法修改了该参数,仅改变副本,而原始值保持不变. 按引用传递:当将一个参数传递给一个方 ...

  8. 详解JVM内存管理与垃圾回收机制5 - Java中的4种引用类型

    在Java语言中,除了基础数据类型的变量以外,其他的都是引用类型,指向各种不同的对象.在前文我们也已经知道,Java中的引用可以是认为对指针的封装,这个指针中存储的值代表的是另外一块内存的起始地址(对 ...

  9. java中的参数传递(只有值传递没有引用传递)

    Java中只有传值调用(值传递),没有传址调用(址传递或者引用传递).所以在java方法中改变参数的值是不会改变原变量的值的,但为什么改变引用变量的属性值却可以呢?请看下面的解答. java中的数据类 ...

最新文章

  1. bzoj 2326: [HNOI2011]数学作业
  2. mfc将文件内容引入编辑框_南开大学黄津辉教授团队联合加拿大麦克马斯特大学Ravi教授团队发表微型MFC水质传感器实际废水检测适用性的研究...
  3. Arrays.copyOf()、Arrays.copyOfRange()与System.arraycopy()用法
  4. 阿里云OSS创建Access,并连接到cloudreve
  5. Perl语言学习总结
  6. Spring REST Controller,在GET方式下,有多个参数时的处理
  7. Tomcat 7源码学习笔记 -5 web app自动reload
  8. Cocos2dx-js 资源.jsc和.pkm反编译
  9. Ring3触发BSOD代码实现及内核逆向分析
  10. 马云请不动郭盛华的原因?原来背后还有更神秘的人物
  11. 我的头条面试经历分享,知乎上转疯了!
  12. ios objective-c将base64位编码字符串转换成图片
  13. 安卓app开机自启动代码
  14. Hyper-V 2016 系列教程30 机房温度远程监控方案
  15. 骁龙8gen2和骁龙8gen1plus区别
  16. eclipse点餐系统的框架
  17. 小红书koc和kol区别是什么?品牌方如何选择
  18. 用 Delphi 学设计模式(一) 之 简单工厂篇
  19. Rapid Object Detection using a Boosted Cascade of Simple Features 部分翻译
  20. linux中安装easy_install(setuptools)

热门文章

  1. 简单绑定要注意的问题_AX
  2. 2019.3.9笔试
  3. springmvc+mybatis多数据源配置,AOP注解动态切换数据源
  4. Linux 下编译安装OpenCV【转】
  5. 帧中继环境下ospf的使用(点到点模式)
  6. 如何区别***工具与病毒
  7. mac下显示隐藏文件的方法
  8. java(15)-策略模式(Strategy Pattern)
  9. 表单元素值获取方式js及java方式
  10. Android Map开发(MrMap源代码)