约定:本文所讲的内容适用于oracle公司的发布的1.8版本的jdk(hotspot虚拟机),文中例子请在相应的jdk版本下测试。

目录

1.什么是引用

2.引用的类型

3.值传递与引用传递

4.基于强弱区分引用

4.1强引用

4.2软引用

4.3弱引用

4.4虚引用


1.什么是引用

我们知道Java是一门纯面向对象的语言,我们在使用Java语言编程时,到处都在使用对象(<<Thinking in Java>>一书中提到"Everything is object"),程序在运行时,对象是在堆内存(heap)中存储的,那么我们如何来访问对象呢?在C/C++中是通过指针,而是Java中是通过引用,引用指向了对象在内存(heap)中的地址。

无论是Java语言还是JVM都支持8种基本类型,而这8种基本类型并不是对象,但是有相应的8种包装器类型,所以对这8种基本类型的访问并不是通过引用,对8种基本类型对应的包装器类访问才是基于引用。那么为什么设计这8种基本类型呢?这个主要是基于内存和性能的考量,譬如从内存上来看对于32位的JVM来说一个Integer数据占用4个字节(对象头) + 4个字节(类型指针) +4个字节(实例数据) + 4个字节(对齐填充)=16个字节,而一个int只占用4个字节。从性能上来看当JVM执行方法时,对于基本类型的数据直接从局部变量表中获取即可,而对象类型需要根据reference定位到heap,所以JVM的JIT(即时编译器)在会根据逃逸分析的标量替换的优化。

引用本身也是占用内存空间的,一个引用值在32位JVM上占用32位(4个字节),在64位JVM上占用64位(8个字节),但通过指针压缩后占用4个字节。

2.引用的类型

根据Java语言规范,引用类型有:类类型(class types),接口类型(interface types),数组类型(array types),类型变量类型(type variables)(实际上泛型类型,泛型擦除后运行时的实际类型可能是类类型,接口类型或者数组类型)。根据JVM规范,引用类型有:类类型(class types),接口类型(interface types),数组类型(array types)。

这些引用类型的值分别指向动态创建的类实例、数组实例和实现了某个接口的类实例或者数组实例。

import org.junit.Test;import java.io.Serializable;public class ReferenceTypeTest {@Testpublic void test() {//类类型Object obj = new Object();//数组类型Object[] objArr = new Object[4];// 接口类型Serializable serializable = new Serializable() {};}
}

所有引用类型有一个默认值:null,当一个引用不指向任何对象的时候,它的值就用null来表示。一个为null的引用,起初并不具备任何实际的运行期类型,但是它可转换为任意的引用类型。但是null又不属于任何类型。

import org.junit.Assert;
import org.junit.Test;import java.io.Serializable;public class ReferenceNullValueTest {@Testpublic void test() {//没有指向任何对象Object obj = null;// null可强制转换成任何类型String s = (String) obj;Assert.assertNull(s);Integer i = (Integer) obj;Assert.assertNull(i);// 但是null不属于任何类型Assert.assertFalse(null instanceof Object);Assert.assertFalse(null instanceof String);Assert.assertFalse(null instanceof Integer);}
}

3.值传递与引用传递

Java语言规范与JVM规范中并没有对值传递与引用传递有描述,值传递与引用传递都是在方法调用时的叫法,以方便理解,或者从某种程度上来说都是值传递,因为引用传递也是传递的引用值(内存地址)的copy。

理解方法执行时栈帧的局部变量表内存数据结构对这两个概念会有深入的理解。下边以这个例子来说明值传递与引用传递。通过“javap -verbose ReferenceTransmitTest.class”可以获取字节码。例子中尽可能添加了更多的注释,以方便理解,请运行单元测试并且理解注释。

这里稍微解释下局部变量表和操作数据栈的概念,方便对注释的理解。每个方法在执行时都会生成一个栈帧,而局部变量表和操作数据栈就是栈帧的一部分,局部变量表可理解一个数组,这个数组的大小在编译后已经决定了,局部变量表依次存储了this(如果是实例方法)、方法入参、方法内部使用的局部变量,而操作数栈是存储运行字节码指令需要的操作数及操作结果。

import org.junit.Assert;
import org.junit.Test;/*** 测试值传递与引用传递*/
public class ReferenceTransmitTest {private class Foo {public int a;}/*** 值传递。*/@Testpublic void testIntTransmit() {// int a = 100 反编译后对应如下指令:// 0: bipush        100      //将100压入操作数栈// 2: istore_1               //将操作数栈中的数据存入该方法栈桢的局部变量表下标为1的变量(即变量a)中int a = 100;System.out.println("before testIntTransmit() a = " + a);// 参考addOne方法的相应注释addOne(a);// 反编译后对应指令如下:// 48: iload_1              //将局部变量表中下标为1的变量(即变量a)压入操作数栈// 49: invokevirtual #7     // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;// 52: invokevirtual #8     // Method java/lang/StringBuilder.toString:()Ljava/lang/String;// 55: invokevirtual #9     // Method java/io/PrintStream.println:(Ljava/lang/String;)VSystem.out.println("after testIntTransmit() a = " + a);Assert.assertEquals(100, a);// 分析:// testIntTransmit()与addOne()方法有各自的方法栈桢// 调用addOne()方法传递的入参a的值为100,且该值copy到addOne()的方法栈桢中// 变量a值在testIntTransmit()与addOne()的方法栈桢中互不影响}/*** 引用传递。但是由于Integer不可变的特性,测试结果同值传递。*/@Testpublic void testIntegerTransmit() {// Integer a = 100 反编译后对应如下指令:// 0: bipush        100      // 将100压入操作数栈// 2: invokestatic  #15      // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;// 5: astore_1               // 将操作数栈中的数据存入该方法栈桢的局部变量表下标为1的变量中// 分析:这里做了一次装箱操作,变量a为指向堆内存的引用。也就是说该方法栈桢的局部变量表下标为1存储的是一个引用值Integer a = 100;System.out.println("before testIntTransmit() a = " + a);// 参考addOne方法的相应注释addOne(a);// 51: aload_1                           //将局部变量表中下标为1的变量(即变量a)压入操作数栈// 52: invokevirtual #16                 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;// 55: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;// 58: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)VSystem.out.println("after testIntTransmit() a = " + a);Assert.assertEquals(100, a.intValue());// 分析:// 由于Integer对象的不可变性(没有提供任何可以修改内部属性的途径),// 在调用addOne方法时,不会也不可能改变a指向内存中的对象实例的值}/*** 引用传递。但是由于String不可变的特性,测试结果同值传递。*/@Testpublic void testStringTransmit() {// String a = "Hello" 反编译后对应的指令。// 0: ldc           #19    // String Hello  从常量池中加载常量数据到操作数据栈中// 2: astore_1             // 将操作数据栈中的数据存储该方法栈桢的局部变量表下标为1中// 此处可以看出"Hello"这个值存在于运行时常量池中中String a = "Hello";System.out.println("before testStringTransmit() a = " + a);// 参考append()方法的注释append(a);System.out.println("after testStringTransmit() a = " + a);Assert.assertEquals("Hello", a);// 分析:// 由于String对象的不可变性(没有提供任何可以修改内部属性的途径),// 在调用append方法时,不会也不可能改变a指向内存中的对象实例的值}/*** 引用传递。*/@Testpublic void testObjectTransmit() {// Foo foo = new Foo() 反编译后对应的指令如下:// 0: new           #24    // class ReferenceTransmitTest$Foo  申请一块Foo实例对象内存,此时所有字段默认为0,并将其引用值压入栈顶// 3: dup                  // 复制栈顶数据值并将复制值压入栈顶,供后续JVM调用构造方法用// 4: aload_0// 5: aconst_null// 6: invokespecial #25    // Method ReferenceTransmitTest$Foo."<init>":(LReferenceTransmitTest;LReferenceTransmitTest$1;)V 包括:初始化值、构造语句块、构造方法// 9: astore_1             // 将操作数据栈中的数据存储到该方法栈桢的局部变量表下标为1中Foo foo = new Foo();System.out.println("before testObjectTransmit() foo.a = " + foo.a);// 参考addOne方法的注释addOne(foo);System.out.println("after testObjectTransmit() foo.a = " + foo.a);Assert.assertEquals(1, foo.a);//分析:// testObjectTransmit()与addOne()方法访问及修改同一对象内存的值,所以在testObjectTransmit()中会看到addOne()方法对属性a值的修改}public void addOne(int a) {//JVM调用该方法时,会将参数a放在该方法栈桢的局部变量表下标为1的位置System.out.println("before addOne() a = " + a);//++a 反编译后得到如下指令,它的意思是将该方法栈桢的局部变量表下标为1的值加1,然后写回局部变量表下标为1中//25: iinc          1, 1++a;System.out.println("after addOne() a = " + a);}public void addOne(Integer a) {//JVM调用该方法时,会将参数a放在该方法栈桢的局部变量表下标为1的位置System.out.println("before addOne() a = " + a);// ++a 反编译后得到如下指令// 25: aload_1             //将该方法栈桢的局部变量表下标为1的值加载到操作数栈是// 26: invokevirtual #18   // Method java/lang/Integer.intValue:()I  //这里实际上做了个拆箱操作,获取a的int值并且压入操作数据栈// 29: iconst_1            // 将常量1压入操作数据栈// 30: iadd                // 将操作数栈中的两个操作数相加,把结果压入操作数栈// 31: invokestatic  #15   // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  //对操作数栈中的数据做了一次装箱操作,并且把返回结果(是一个引用地址)压入操作数栈// 34: astore_1            // 将操作数栈中的值存储到该方法栈桢的局部变量表下标为1中++a;// 上述指令等同于如下代码// int b = a.intValue();// int b = b + 1;// a = Integer.valueOf(b);System.out.println("after addOne() a = " + a);//分析:// 由于Integer的不可变性(没有提供任何可以修改内部属性的途径),// 编译器通过拆箱之后获取int值,然后对int值做了加1的操作,// 最后通过装箱,生成了一个新的Integer对象,同时重新修改引用类型a的值指向新的Integer对象的内存地址。}public void append(String a) {//JVM调用该方法时,会将参数a放在该方法栈桢的局部变量表下标为1的位置System.out.println("before append() a = " + a);// a += " World!" 反编译后得到如下指令// 25: new           #3    // class java/lang/StringBuilder// 28: dup// 29: invokespecial #4    // Method java/lang/StringBuilder."<init>":()V// 32: aload_1// 33: invokevirtual #6    // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;// 36: ldc           #33   // String  World!// 38: invokevirtual #6    // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;// 41: invokevirtual #8    // Method java/lang/StringBuilder.toString:()Ljava/lang/String;// 44: astore_1a += " World!";// 上述指令等同于如下代码:// StringBuilder sb = new StringBuilder();// sb.append(a);// sb.append(" World");// a = sb.toString();System.out.println("after append() a = " + a);//分析:// 由于String的不可变性(没有提供任何可以修改内部属性的途径),// 编译器对String的连接操作,通过生成StringBuilder来完成的// 最后通过StringBuilder的toString()方法生成了一个新的字符串对象,同时重新修改引用类型a的值指向新的String对象的内存地址。。}public void addOne(Foo foo) {//JVM调用该方法时,会将参数foo放在该方法栈桢的局部变量表下标为1的位置System.out.println("before addOne() foo.a = " + foo.a);// foo.a++ 反编译后对应的指令如下:// 28: aload_1              // 将该方法栈桢的局部变量表下标为1(即foo)的值加载到操作数据栈// 29: dup                  // 复制栈顶数据值并将复制值压入栈顶// 30: getfield      #27    // Field ReferenceTransmitTest$Foo.a:I  获取对象属性a的值并且压入操作数栈// 33: iconst_1             // 将常量1压入操作数栈// 34: iadd                 // 将操作数栈中数据相加然后把结果压入操作数栈// 35: putfield      #27    // Field ReferenceTransmitTest$Foo.a:I  将操作数栈的值写入对象foo的属性a中foo.a++;System.out.println("after addOne() foo.a = " + foo.a);}
}

上述单元测试的结论:

a.基本数据类型参数传值为值传递,对传入调用方法的入参变量修改不会影响原始值。

b.包装器类型及String类型参数值值为引用传递,但由于包装器类与String类的不变性,编译器对其进行了特殊处理,并不会改变原始值,所以也可以理解为值传递。

c.引用传递,对传入调用方法的入参变量和原始变量指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。

4.基于强弱区分引用

我们平时开发大部分都在用强引用,如:Object obj = new Object()。在JDK1.2之前,只有强引用,对于强引用,即使发生OOM,垃圾回收器也是不会回收它的,但是对于某些场景当内存不足时,希望垃圾回收器能清理这些对象,譬如缓存,连接池等,对这些对象就显得无能为力了。所在以JDK1.2及以后,在API层面及JVM根据引用的强弱程度将引用分为:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),这四种引用强度依次逐渐减弱。

那么垃圾回收器是如何根据强弱程度对对象进行回收的呢?垃圾收集器实际上会选出一些对象作为root,若对象与这些root对象之间有路径存在,那么这些对象是可达的,根据对象引用的引用强弱程度,依次可分为强可达(Strong Reachable),软可达(Soft Reachable),弱可达(Weak  Reachable),虚可达(Phantom  Reachable),其中软引用、弱引用、虚引用在一定条件下,都是可以被回收的。不同的可达性状态影响着垃圾回收器的回收策略,这个就是目前JVM采用的可达性分析算法。

基于强弱的引用比较
  可达性 垃圾回收 如何使用 使用场景
强引用 强可达 即使OOM,GC也不会回收强引用对象 使用例子:Object obj = new Object();  一般开发都是在使用强引用
软引用 软可达 GC会在OOM前回收软引用对象 基于SoftReference和ReferenceQueue 多用于框架中,如缓存、连接池
弱引用 弱可达 GC发生时会回收弱引用对象 基于WeakReference和ReferenceQueue 多用于框架中,如缓存、连接池
虚引用 虚可达 随时可能会被回收 基于PhantomReference和ReferenceQueue 跟踪对象的回收

4.1强引用

       强引用就是程序中一使用的引用类型。

强引用具备如下特点:

a.强引用可以直接访问目标对象

b.强引用所指向的对象在任何时候都不会被系统回收,虚拟机宁愿抛出OOM异常,也不会回收强引用所指向的对象

c.强引用可能导致内存泄露

import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class StrongReferenceTest {/*** JVM参数:* -Xms10m -Xmx10m  -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError*/@Test(expected = OutOfMemoryError.class)public void testStrongReference() {int size = 1000000;//用list保持强引用,即使发生OOM,垃圾回收器也不会回收list中的对象List<Integer> list = new ArrayList(size);for (int i = 0; i < size; i++) {list.add(i);}}
}

4.2软引用

软引用是比强引用弱一些的引用类型。一个对象只持有软引用,那么当堆空间不足时,就会回收该对象。软引用使用java.lang.ref.SoftReference类实现。使用场景:缓存

import org.junit.Assert;
import org.junit.Test;import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;public class SoftReferenceTest {private class Student {private int id;private String name;public Student(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Student{" + "id=" + id + ", name='" + name + '\'' + '}';}}/*** JVM参数:* -Xms10m -Xmx10m  -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError*/@Testpublic void testSoftReferenceWithoutQueue() {int softRefSize = 100000;List<SoftReference> list = new ArrayList<SoftReference>(softRefSize);for (int i = 0; i < softRefSize; i++) {// 注意此处创建的Student对象除了SoftReference没有其它强引用// 若此处Student还被其它GC root对象关联,那么它仍然是强引用对象list.add(new SoftReference<Student>(new Student(i, "张三")));}// 可用内存充足,没有发生GCAssert.assertEquals(0, getGcObjectSize(list));// 强制执行gc,大部分情况下垃圾回收器都会立即执行,若没有执行,请在gc后sleep一段时间System.gc();// 可用内存充足,垃圾回收器不会回收软引用对象Assert.assertEquals(0, getGcObjectSize(list));// 创建一个大对象,让heap内存紧张,发生full gc,清除软引用byte[] bArr = new byte[1024 * 1024];System.out.println("getGcObjectSize(list) = " + getGcObjectSize(list));//被垃圾回收器回收的软引用对象的数量>0Assert.assertTrue(getGcObjectSize(list) > 0);}/*** JVM参数:* -Xms10m -Xmx10m  -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError*/@Testpublic void testSoftReferenceWithQueue() throws InterruptedException {// 定义一个软引用的队列final ReferenceQueue<Student> softQueue = new ReferenceQueue<Student>();class StudentSoftReference extends SoftReference<Student> {private int id;public StudentSoftReference(Student referent, ReferenceQueue<? super Student> q) {super(referent, q);id = referent.id;}}// 启动一个线程,实时追踪对象回收的情况Thread t = new Thread(new Runnable() {public void run() {while (true) {try {StudentSoftReference sr = (StudentSoftReference) softQueue.remove();System.out.println("Student id " + sr.id + " has been deleted by GC!");} catch (InterruptedException e) {e.printStackTrace();}}}});t.setDaemon(true); //设置为守护线程t.start();int softRefSize = 10;List<SoftReference> list = new ArrayList<SoftReference>(softRefSize);for (int i = 0; i < softRefSize; i++) {// 若垃圾回收器准备回收一个对象时,如果发现它还有弱引用,就会在回收对象后,将这个弱引用加入引用队列,// 以通知应用程序的回收情况list.add(new StudentSoftReference(new Student(i, "张三"), softQueue));}// 可用内存充足,没有发生GCAssert.assertEquals(0, getGcObjectSize(list));// 强制执行gc,大部分情况下垃圾回收器都会立即执行,若没有执行,请在gc后sleep一段时间System.gc();// 可用内存充足,垃圾回收器不会回收软引用对象Assert.assertEquals(0, getGcObjectSize(list));// 创建一个大对象,让内存资源紧张,发生full gc,清除软引用byte[] bArr = new byte[1024 * 1010 * 6];System.out.println("getGcObjectSize(list) = " + getGcObjectSize(list));Assert.assertTrue(getGcObjectSize(list) > 0);// 避免主线程退出时,守护线程还没结束Thread.sleep(2000);}/*** 获取被垃圾回收器清除的对象数量** @param list* @return*/private int getGcObjectSize(List<SoftReference> list) {int gcStuSize = 0;for (SoftReference sr : list) {if (sr.get() == null) {gcStuSize++;}}return gcStuSize;}
}

4.3弱引用

弱引用比软引用弱一些,在系统GC时,只要发现弱引用,总将将对象进行回收。弱引用使用java.lang.ref.WeakReference类实现。使用场景:缓存

import org.junit.Assert;
import org.junit.Test;import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;public class WeakReferenceTest {private class Student {private int id;private String name;public Student(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "Student{" + "id=" + id + ", name='" + name + '\'' + '}';}}/*** JVM参数:* -Xms10m -Xmx10m  -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError*/@Testpublic void testWeakReferenceWithoutQueue() {int weakRefSize = 1000;List<WeakReference> list = new ArrayList<WeakReference>(weakRefSize);for (int i = 0; i < weakRefSize; i++) {// 注意此处创建的Student对象除了WeakReference没有其它强引用// 若此处Student还没其它GC root对象关联,那么它仍然是强引用对象list.add(new WeakReference<Student>(new Student(i, "张三")));}// 可用内存充足,没有发生GCAssert.assertEquals(0, getGcObjectSize(list));// 强制执行gc,大部分情况下垃圾回收器都会立即执行,若没有执行,请在gc后sleep一段时间System.gc();// 不管内存情况是否充足,软引用对象都会被垃圾回收器回收Assert.assertEquals(weakRefSize, getGcObjectSize(list));}// 基于队列的测试同SoftReferenceTest#testPhantomReferenceWithQueue/*** 获取被垃圾回收器清除的对象数量** @param list* @return*/private int getGcObjectSize(List<WeakReference> list) {int gcStuSize = 0;for (WeakReference sr : list) {if (sr.get() == null) {gcStuSize++;}}return gcStuSize;}
}

4.4虚引用

虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。软引用使用java.lang.ref.PhantomReference类实现。使用场景:对象回收跟踪。

import org.junit.Assert;
import org.junit.Test;import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;public class PhantomReferenceTest {private class Student {private int id;private String name;public Student(int id, String name) {this.id = id;this.name = name;}// 可以解注下这段代码,看下打印日志。下一篇源码剖析解释下这个。//@Override//protected void finalize() {//    System.out.println("Student id " + id + " has been finalize()");//}@Overridepublic String toString() {return "Student{" + "id=" + id + ", name='" + name + '\'' + '}';}}/*** JVM参数:* -XX:+PrintHeapAtGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError*/@Testpublic void testPhantomReferenceWithQueue() {final ReferenceQueue<Student> phantomQueue = new ReferenceQueue<Student>();class StudentPhantomReference extends PhantomReference<Student> {private int id;public StudentPhantomReference(Student referent, ReferenceQueue<? super Student> q) {super(referent, q);id = referent.id;}}// 启动一个线程,实时追踪对象回收的情况Thread t = new Thread(new Runnable() {public void run() {while (true) {try {StudentPhantomReference sr = (StudentPhantomReference) phantomQueue.remove();System.out.println("Student id " + sr.id + " has been deleted");} catch (InterruptedException e) {e.printStackTrace();}}}});t.setDaemon(true);//设置为守护线程t.start();StudentPhantomReference phantomReference = new StudentPhantomReference(new Student(1, "张三"), phantomQueue);// 通过get()方法获取到的Student对象为nullAssert.assertNull(phantomReference.get());// 强制执行gc,大部分情况下垃圾回收器都会立即执行,若没有执行,请在gc后sleep一段时间System.gc();//无论内存是否充足,Student对象都被回收,观察日志打印}}

5.总结

Java中的类型分两类,一是基本类型,另一类就是引用类型,引用类型的值就是指向对象内存的内存地址。引用类型主要分为三类:类类型,接口类型,数组类型。还有一种特殊的引用类型值null,它既可以转换为任意类型,但又不属于任意类型。在Java中传递分为值传递和引用传递,值传递不会改变原始值,但引用传递会,可通过跟踪执行字节码过程的本地变量表和操作数栈来理解这个概念。垃圾回收器在回收对象时,会根据引用对象的可达性进行进行不同的回收,其中强引用对应强可达,软引用对应软可达,弱引用对应弱可达,虚引用对应虚可达。Java提供了一套API来支持软引用,弱引用和虚引用。

Java的API和JVM是如何支持软引用、弱引用、虚引用以及Finalizer的,我们下一节继续分析。


下一篇详细剖析java.lang.ref包下类的源码

深入理解Java引用(一)相关推荐

  1. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  2. 理解Java中的弱引用(Weak Reference)

    理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限, ...

  3. Java多态-如何理解父类引用指向子类对象

    java多态,如何理解父类引用指向子类对象 要理解多态性,首先要知道什么是"向上转型". 我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类.我可以通过   C ...

  4. java弱引用怎么手动释放,十分钟理解Java中的弱引用,十分钟java引用

    十分钟理解Java中的弱引用,十分钟java引用 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,帮助大家理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限 ...

  5. java虚引用作用_深入理解Java中的引用(二)——强软弱虚引用

    深入理解Java中的引用(二)--强软弱虚引用 在上一篇文章中介绍了Java的Reference类,本篇文章介绍他的四个子类:强引用.软引用.弱引用.虚引用. 强引用(StrongReference) ...

  6. [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)...

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  7. java的弱引用_理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  8. java byreference_深入理解Java中的引用(一)——Reference

    深入理解Java中的引用(一)--Reference 本系列文章首先会介绍Reference类,为之后介绍的强引用.软引用.弱引用和虚引用打下基础. 最后会介绍虚引用在DirectBuffer回收中的 ...

  9. java 父类引用子类对象_java多态,如何理解父类引用指向子类对象

    java多态,如何理解父类引用指向子类对象 要理解多态性,首先要知道什么是"向上转型". 我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类.我可以通过   C ...

最新文章

  1. win8开发中需要用到一些系统版本之类的统计信息,总结如下。
  2. mysql timestamp _mysql之TIMESTAMP(时间戳)用法详解
  3. 传统红色纹样图案背景|中式海报必备素材
  4. 玩了一年多电子商务,接触各种品类产品
  5. 谷歌Linux基金会等联合推出开源软件签名服务 sigstore,提振软件供应链安全
  6. android浮动按钮_Android浮动操作按钮示例教程
  7. PM_我们是怎么做Code Review的
  8. u盘安装linux6.5教程,u盘安装CentOS6.5
  9. python模拟鼠标拖动滑块_Python中selenium的作用链模拟滑块运动,python,ActionChains,移动...
  10. Homegrown【翻译】
  11. 中国大陆五级行政区划数据爬虫
  12. 淼淼刷力扣(PTA特别版2)
  13. SCVMM 2012 R2---安装Hyper-V Server 2012 R2主机服务器
  14. 目标检测经典论文——YOLOv1论文翻译(纯中文版):YOLO:统一的实时目标检测
  15. MPI聚合通信之MPI_Bcast函数
  16. 小白css基础学习记之精灵图
  17. html5地图编辑器,Tiled地图编辑器 Tiled Map Editor 的使用(一)基础功能+地形功能...
  18. python青少年编程比赛_第十一届蓝桥杯大赛青少年创意编程组比赛细则
  19. 智能化工厂的几大特征
  20. 算法工程师的工程修养:Linux 服务器性能故障分析

热门文章

  1. 计算机毕业设计JAVA企业人事管理系统mybatis+源码+调试部署+系统+数据库+lw
  2. 拼多多找同款接口,拼多多相似图片,拼多多图片搜索接口
  3. 批处理echo、echo off、echo on、@、@echo off详细讲解
  4. 企业系统软件分类简介
  5. 软件性能测试理论手札(二)
  6. 广州大学计算机网络期末考试答案,广州大学2015广州大学计算机网络试卷__A卷和答案...
  7. 【Office】Word 删除最后一页空白页
  8. ISME Commun:RNA-seq助力发现菌群收敛新机制
  9. 辐射避难所ol服务器维护,辐射避难所Online出现网络或设备异常怎么解决 解决方案一览...
  10. 使用防火玻璃块创建安全,时尚的窗户和墙壁