Java中的引用和动态代理的实现详解

我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的。但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础?

如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>>,这个类用到了弱引用来构建。

在JDK的3个特殊引用中,弱引用是使用范围最广的,它的特性也最清晰,相对而言,其他两种逻辑稍显晦涩,源码中的注释也语焉不详。本文将简单介绍几种引用的行为特征,然后分析一下弱引用的一些实际应用场景,其中包含了动态代理中的实现。本文将包含以下内容:

JDK中的引用类型

不同引用类型对GC行为的影响

引用类型的实现

ThreadLocal对弱引用的使用

动态代理对弱引用的实现

虚引用如何导致内存泄漏

JDK中「引用(Reference)」的类型

Java的所有运行逻辑都是基于引用的,其形态类似于不可变的指针,所以在Java中会有一些很绕的概念,比如说,Java中函数的传参是值传递,但这里所说的值,其实是引用的值,所以你可以通过一个函数的参数来修改其对象的值。另一方面,Java中还存在一些基本数据类型,它们没有引用,传递的是真实的值,所以不能在函数内部修改参数的值。

关于引用,Java中有这样几种:

1.强引用

所有对象的引用默认为强引用,普通代码中,赋值语句之间传递的都是强引用,如果一个对象可以被某个线程(活着的,下同)通过强引用访问到,则称之为强可达的(StronglyReachable)。

强可达的对象不会被GC回收。

2.软引用(SoftReference<T>)

当一个对象不是强可达的,但可以被某个线程通过软引用访问到,则称之为软可达的(SoftlyReachable)。

软可达的对象的引用只有在内存不足时会被清除,使之可以被GC回收。在这一点上,JVM是不保证具体什么时候清除软引用,但可以保证在OOM之前会清除软可达的对象。同时,JVM也不保证软可达的对象的回收顺序,但OracleJDK的文档中提到,最近创建和最近使用的软可达对象往往会最后被回收,与LRU类似。

关于软可达的对象何时被回收,可以参考Oracle的文档。

3.弱引用(WeakReference<T>)

当一个对象不是强可达的,也不是软可达的,但可以被某个线程通过弱引用访问到,则称之为弱可达的(WeaklyReachable)。

弱引用是除了强引用之外,使用最广泛的引用类型,它的特性也更简单,当一个对象是弱可达时,JVM就会清除这个对象上的弱引用,随后对这个对象进行回收动作。

4.虚引用(PhantomReference<T>)

当一个对象,通过以上几种可达性分析都不可达,且已经finalized,但有虚引用指向它,则它是虚可达的(PhantomReachable)。

虚引用是行为最诡异,使用方法最难的引用,后边会讲到。

虚引用不会影响GC,它的get()方法永远返回null,唯一的用处就是在GCfinalize一个对象之后,它会被放到指定的队列中去。关于这个队列会在下边说明。

不同GC行为背后的原理

JVMGC的可达性分析

JVM的GC通过可达性分析来判断一个对象是否可被回收,其基本思路就是以GCRoots为起点遍历链路上所有对象,当一个对象和GCRoots之间没有任何的直接或间接引用相连时,就称之为不可达对象,则证明此对象是不可用的。

而进一步,Java中又定义了如上所述的4种不同的可达性,用来实现更精细的GC策略。

Finalaze和ReferenceQueue

对于普通的强引用对象,如果其变成不可达之后,通常GC会进行Finalize(Finalize主要目的是让用户可以自定义释放资源的过程,通常是释放本地方法中使用的资源),然后将它的对象销毁回收,但对于本文中讨论的3种引用,还有可能在这个过程中做一些别的事情:

GC根据约定的规则来决定是否清除这些引用

这方面上一节已经讲过了,每个引用类型都有约定的处理规则。

如果它们注册了引用队列,在Finalize对象后,将引用的对象放入队列。

主要用来使开发者可以得到对象被销毁的通知,当然,如虚引用这样的,其引用不会自动被清除,所以它可以阻止其所引用的对象被回收。

引用(java.lang.ref.Reference<T>)对象的状态

这里所说的「引用对象」指的是由类java.lang.ref.Reference<T>生产的对象,这个对象上保持了「需要特殊处理的」对「目标对象」的引用。

引用对象有4种状态,根据它与其注册的队列的关系,分为以下4种:

Active

引用对象的初始状态,表示GC要按照特殊的逻辑来处理这个对象,大致方法就是按照上一节提到的。

Pending

如果一个引用对象,其注册了队列,在入队之前,会进入这个状态。

Enqueued

一个引用对象入队后,进入这个状态。

Inactive

一个引用对象出队后,或者没有注册队列,其队列是一个特殊的对象java.lang.ref.ReferenceQueue.NULL,表示这个对象已经没有用了。

几种引用的实际应用

日常开发工作中,用到除强引用之外的引用的可能性很小,只有在处理一些性能敏感的逻辑时,才需要考虑使用这些特殊的引用,下面就举几个相关的实际例子,分析其使用场景。

软引用

弱引用的使用比较简单,如Guava中的LocalCache中就是用了SoftReference来做缓存。

弱引用

弱引用是使用的比较多的,从上文的描述可知:对于一个「目标对象A」,如果还有强引用指向它,那么从一个弱引用就可以访问到A,一旦没有强引用指向它,那么就可以认为,从这个弱引用就访问不到A了(实际情况可能会有偏差)。

根据这个特点,JDK中注释说到,弱引用通常用来做映射表(canonicalizingmapping),总结下来映射表有这样2个特点:

如果表中的Key(或者Value)还存在强引用,则可以通过Key访问到Value,反之则访问不到

换句话说,只要有原始的Key,就能访问到Value。

映射表本身不会影响其中Key或者Value的GC

在JDK中有很多个地方使用了它的这个特点,下面是2个具有代表性的实例。

1.ThreadLocal

ThreadLocal的原理比较简单,线程中保持了一个以ThreadLocal为Key的ThreadLocal.ThreadLocalMap对象threadLocals,其中的Entry如代码1中所示:

//代码1
static class Entry extends WeakReference<ThreadLocal<?>> {
  /** The value associated with this ThreadLocal. */
  Object value;
  //其保持了对作为Key的ThreadLocal对象的弱引用
  Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
  }
}

其引用关系如下图所示:

ThreadLocal中的引用关系

从上图可以看出,当引用2被清除之后(ThreadLocal对象不再使用),如果引用4为强引用,则不论引用1是否还存在,只要Thread对象还没死,则对象1和对象2永远不会被释放。

2.动态代理

动态代理是Java世界一个十分重要的特性,对于需要做AOP的业务逻辑十分重要。JDK本身提供了基于反射的动态代理机制,其原理大致是要通过预先定义的接口(interface)来动态的生成代理类,并将之代理到InvocationHandler的实例上去。JDK的动态代理使用起来很简单,如下代码2中所示:

//代码2
package me.lk;

import java.lang.reflect.*;

public class TestProxy {
  /**
   * 两个预定义的需要被代理的接口
   */
  public static interface ProxiedInterface {

    void proxiedMethod();
  }
  public static interface ProxiedInterface2 {

    void proxiedMethod2();
  }

  /**
   * 真正的处理逻辑
   */
  public static class InvoHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("in proxy:" + method.getName());
      //其他逻辑
      System.out.println("in proxy end");
      return null;
    }

  }
  public static void main(String[] args) {
    InvoHandler ih = new InvoHandler();
    ProxiedInterface proxy = (ProxiedInterface) Proxy.newProxyInstance(TestProxy.class.getClassLoader(), new Class[]{ProxiedInterface.class, ProxiedInterface2.class}, ih);
    proxy.proxiedMethod();
    ProxiedInterface2 p = (ProxiedInterface2) proxy;
    p.proxiedMethod2();
  }

}

动态代理的实现原理

关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理。

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

参阅:Java设计模式之代理模式原理及实现代码分享

其实现原理其实也很简单,就是在方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)中动态生成一个「实现了interfaces中所有接口」并「继承于Proxy」的代理类,并生成相应的对象。

//代码3
public static Object newProxyInstance(ClassLoader loader,
                     Class<?>[] interfaces,
                     InvocationHandler h)
    throws IllegalArgumentException
  {
	Objects.requireNonNull(h);
	final Class<?>[] intfs = interfaces.clone();
	//验证真实调用者的权限
	final SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
		checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
	}
	//查询或生成代理类
	Class<?> cl = getProxyClass0(loader, intfs);
	//验证调用者对代理类的权限,并生成对象
	。。。省略代码
}
private static Class<?> getProxyClass0(ClassLoader loader,
                    Class<?>... interfaces) {
	if (interfaces.length > 65535) {
		throw new IllegalArgumentException("interface limit exceeded");
	}
	// 通过缓存获取代理类
	return proxyClassCache.get(loader, interfaces);
}

生成动态类的逻辑在方法java.lang.reflect.Proxy.ProxyClassFactory.apply(ClassLoader, Class<?>[]),代码如下:

//代码4
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

  Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
  //验证接口,验证接口是否重复,验证loader对接口的可见性

  //生成包名和修饰符

  //生成类
  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
  try {
    return defineClass0(loader, proxyName,
              proxyClassFile, 0, proxyClassFile.length);
  } catch (ClassFormatError e) {
    /*
     * 生成失败
     */
    throw new IllegalArgumentException(e.toString());
  }
}

动态代理中的缓存策略

为了更高效的使用动态代理,Proxy类中采用了缓存策略(代码3中的proxyClassCache )来缓存动态生成的代理类,由于这个缓存对象是静态的,也就是说一旦Proxy类被加载,proxyClassCache 很可能永远不会被GC回收,然而它必须要保持对其中的ClassLoader和Class的引用,如果这里使用强引用,则它们也随着proxyClassCache 永远不会被GC回收。

不再使用的类和类加载器如果无法被GC,其内存泄漏的风险很大。所以WeakCache中设计为,「传入的类加载器」和「生成的代理类」为弱引用。

类和类加载器是相互引用的,而类加载器的内存泄漏可能会带来很严重的问题,有兴趣可以去看这篇文章:Reloading Java Classes 201: How do ClassLoader leaks happen?

//代码5
/**
 * a cache of proxy classes
 */
//ClassLoader  用来加载预定义接口(interface)和生成代理类的类加载器
//Class<?>[]   预定义接口(interface)
//Class<?>    生成的代理类
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
  proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

/**
 * CacheKey containing a weakly referenced {@code key}. It registers
 * itself with the {@code refQueue} so that it can be used to expunge
 * the entry when the {@link WeakReference} is cleared.
 */
private static final class CacheKey<K> extends WeakReference<K> 

/**
 * A {@link Value} that weakly references the referent.
 */
private static final class CacheValue<V>
  extends WeakReference<V> implements Value<V>

从代码5中可以看出,WeakCache对象中保持了对ClassLoader(包装为CacheKey)和代理类(包装为CacheValue)的弱引用,所以当此类加载器和代理类不再被强引用时,它们就会被回收。

存在的问题

然而,WeakCache的实现是有问题的,在java.lang.reflect.WeakCache.reverseMap和java.lang.reflect.WeakCache.valueFactory中的状态在极限情况下可能会出现不同步,导致一个代理类被调用java.lang.reflect.Proxy.isProxyClass(Class<?>)的返回值不正确。具体可以参考RaceConditioninjava.lang.reflect.WeakCache。

不过这个问题在JDK9中已经不存在了。

关于虚引用的GC行为

在上一节,并没有列出虚引用的使用场景,因为它的使用场景十分单一。PhantomReference设计的目的就是可以在对象被回收之前收到通知(通过注册的队列),所以它没有不含注册队列的构造器(只有publicPhantomReference(Treferent,ReferenceQueue<?superT>q),但你仍可以传null进去),但这种场景在JDK里并没有出现,也很少有开发者使用它。

从PhantomReference类的源代码可知,你永远无法通过它获取到它引用的那个对象(其get()方法永远返回null),但是它又可以阻止其引用的目标对象被GC回收。从上文可知,通常一个不可达(强不可达、软不可达、弱不可达)的对象会被finalize,然后被回收。但如果它在被回收前,GC发现它仍然是虚可达,那么它就不会回收这块内存,而这块内存又不能被访问到,那么这块内存就泄漏了。

想要虚引用的「目标对象」被回收,必须让「引用对象」本身不可达,或者显式地清除虚引用。所以如果使用不当,很可能会造成内存泄漏,这也是它使用范围不广的原因之一。

代码6演示了这3种引用分别的GC行为:

//代码6
private static List<PhantomReference<Object>> phantomRefs = new ArrayList<>();
private static List<WeakReference<Object>> weaks = new ArrayList<>();
private static List<SoftReference<Object>> softs = new ArrayList<>();

public static void testPhantomRefLeakOOM() {
  while(true) {
    //生成一个占用10M的内存的对象
    Byte[] bytes = new Byte[1024 * 1024 * 10];
    //使用软引用存储
//  softs.add(new SoftReference<Object>(bytes));
    //使用虚引用存储
    PhantomReference<Object> pf = new PhantomReference<Object>(bytes, null);
    //使用弱引用存储
//  weaks.add((new WeakReference<Object>(bytes)));
    phantomRefs.add(pf);
    //显式清除引用
//  pf.clear();
    //建议GC
    System.gc();
  }
}

以上代码展示了4种影响GC的行为,分别是:

1. 使用软引用的GC行为

GC日志如下,可以看到,当系统内存不够的时候(OOM之前),软引用会被清除,引发GC,释放内存。

2017-07-03T12:36:22.995+0800: [Full GC (System.gc()) [PSYoungGen: 40971K->40960K(76288K)] [ParOldGen: 492061K->492061K(506880K)] 533033K->533022K(583168K), [Metaspace: 2727K->2727K(1056768K)], 0.0610620 secs] [Times: user=0.23 sys=0.00, real=0.06 secs] 

2017-07-03T12:36:24.391+0800: [Full GC (System.gc()) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 1065502K->1065502K(1087488K)] 1106462K->1106462K(1163776K), [Metaspace: 

2017-07-03T12:36:32.291+0800: [Full GC (System.gc()) [PSYoungGen: 40962K->40962K(76288K)] [ParOldGen: 2581022K->2581022K(2621952K)] 2621985K->2621985K(2698240K), [Metaspace: 2727K->2727K(1056768K)], 0.3106258 secs] [Times: user=2.31 sys=0.00, real=0.31 secs]
2017-07-03T12:36:32.610+0800: [GC (System.gc()) [PSYoungGen: 40962K->128K(76288K)] 2662945K->2663070K(2739712K), 0.6298054 secs] [Times: user=4.63 sys=0.00, real=0.63 secs]
2017-07-03T12:36:33.240+0800: [Full GC (System.gc()) [PSYoungGen: 128K->0K(76288K)] [ParOldGen: 2662942K->2662945K(2663424K)] 2663070K->2662945K(2739712K), [Metaspace: 2727K->2727K(1056768K)], 0.2898513 secs] [Times: user=2.25 sys=0.00, real=0.29 secs] 

2017-07-03T12:36:34.096+0800: [Full GC (System.gc()) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744865K->2744865K(2746368K)] 2785825K->2785825K(2822656K), [Metaspace: 2727K->2727K(1056768K)], 0.3282086 secs] [Times: user=2.47 sys=0.00, real=0.33 secs]
2017-07-03T12:36:34.425+0800: [Full GC (Ergonomics) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744865K->2744865K(2777088K)] 2785825K->2785825K(2853376K), [Metaspace: 2727K->2727K(1056768K)], 0.3061587 secs] [Times: user=2.32 sys=0.00, real=0.31 secs] 

2017-07-03T12:36:34.731+0800: [Full GC (Allocation Failure) [PSYoungGen: 40960K->0K(76288K)] [ParOldGen: 2744865K->531K(225280K)] 2785825K->531K(301568K), [Metaspace: 2727K->2727K(1056768K)], 0.1559132 secs] [Times: user=0.02 sys=0.14, real=0.16 secs]
2017-07-03T12:36:34.890+0800: [GC (System.gc()) [PSYoungGen: 40960K->32K(76288K)] 41491K->82483K(301568K), 0.0304114 secs] [Times: user=0.14 sys=0.00, real=0.03 secs]
2017-07-03T12:36:34.920+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82451K->41491K(225280K)] 82483K->41491K(301568K), [Metaspace: 2727K->2727K(1056768K)], 0.0179676 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
2017-07-03T12:36:34.941+0800: [GC (System.gc()) [PSYoungGen: 41649K->32K(76288K)] 83140K->123443K(301568K), 0.0323917 secs] [Times: user=0.11 sys=0.00, real=0.03 secs]
2017-07-03T12:36:34.973+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 123411K->82451K(225280K)] 123443K->82451K(301568K), [Metaspace: 2727K->2727K(1056768K)], 0.0424672 secs] [Times: user=0.20 sys=0.00, real=0.04 secs]
2017-07-03T12:36:35.414+0800: [Full GC (System.gc()) [PSYoungGen: 41011K->40960K(76288K)] [ParOldGen: 287252K->287252K(308224K)] 328264K->328212K(384512K), [Metaspace: 2727K->2727K(1056768K)], 0.0520262 secs] [Times: user=0.33 sys=0.00, real=0.05 secs] 

2017-07-03T12:36:48.569+0800: [Full GC (Ergonomics) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744854K->2744854K(2777088K)] 2785815K->2785815K(2853376K), [Metaspace: 2727K->2727K(1056768K)], 0.3476025 secs] [Times: user=2.45 sys=0.02, real=0.35 secs]
2017-07-03T12:36:48.916+0800: [Full GC (Allocation Failure) [PSYoungGen: 40960K->0K(76288K)] [ParOldGen: 2744854K->534K(444928K)] 2785815K->534K(521216K), [Metaspace: 2727K->2727K(1056768K)], 0.1644360 secs] [Times: user=0.02 sys=0.16, real=0.17 secs]
2017-07-03T12:36:49.084+0800: [GC (System.gc()) [PSYoungGen: 40960K->32K(76288K)] 41494K->82486K(521216K), 0.0444057 secs] [Times: user=0.22 sys=0.00, real=0.04 secs]
2017-07-03T12:36:49.128+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82454K->41494K(444928K)] 82486K->41494K(521216K), [Metaspace: 2727K->2727K(1056768K)], 0.0288512 secs] [Times: user=0.11 sys=0.00, real=0.03 secs]

2. 使用弱引用

GC日志如下,从中可以看到,弱引用所引用的目标对象,时时刻刻都在被GC。

2017-07-03T12:32:55.214+0800: [GC (System.gc()) [PSYoungGen: 43581K->728K(76288K)] 43581K->41696K(251392K), 0.0354037 secs] [Times: user=0.20 sys=0.00, real=0.04 secs]
2017-07-03T12:32:55.252+0800: [Full GC (System.gc()) [PSYoungGen: 728K->0K(76288K)] [ParOldGen: 40968K->41502K(175104K)] 41696K->41502K(251392K), [Metaspace: 2726K->2726K(1056768K)], 0.0258447 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 

2017-07-03T12:32:55.533+0800: [Full GC (System.gc()) [PSYoungGen: 41309K->40960K(76288K)] [ParOldGen: 164381K->164381K(175104K)] 205690K->205341K(251392K), [Metaspace: 2726K->2726K(1056768K)], 0.0389489 secs] [Times: user=0.25 sys=0.00, real=0.04 secs] 

2017-07-03T12:32:57.413+0800: [Full GC (System.gc()) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 1024541K->1024541K(1046016K)] 1065502K->1065502K(1122304K), [Metaspace: 2726K->2726K(1056768K)], 0.1263574 secs] [Times: user=0.94 sys=0.00, real=0.13 secs] 

2017-07-03T12:33:05.364+0800: [Full GC (System.gc()) [PSYoungGen: 40962K->40962K(76288K)] [ParOldGen: 2581022K->2581022K(2621952K)] 2621984K->2621984K(2698240K), [Metaspace: 2726K->2726K(1056768K)], 0.2474419 secs] [Times: user=1.69 sys=0.00, real=0.25 secs] 

2017-07-03T12:33:07.447+0800: [Full GC (Ergonomics) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744864K->2744864K(2777088K)] 2785824K->2785824K(2853376K), [Metaspace: 2726K->2726K(1056768K)], 0.2825105 secs] [Times: user=1.79 sys=0.00, real=0.28 secs]
2017-07-03T12:33:07.729+0800: [Full GC (Allocation Failure) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744864K->2744851K(2777088K)] 2785824K->2785812K(2853376K), [Metaspace: 2726K->2726K(1056768K)], 0.8902204 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at me.lk.TestReference.testPhantomRefLeakOOM(TestReference.java:109)
  at me.lk.TestReference.main(TestReference.java:50)
 [Times: user=3.79 sys=0.00, real=0.89 secs]
Heap
 PSYoungGen   total 76288K, used 43025K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000)
 eden space 65536K, 65% used [0x000000076b400000,0x000000076de04408,0x000000076f400000)
 from space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000)
 to  space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000)
 ParOldGen    total 2777088K, used 2744851K [0x00000006c1c00000, 0x000000076b400000, 0x000000076b400000)
 object space 2777088K, 98% used [0x00000006c1c00000,0x0000000769484fb8,0x000000076b400000)
 Metaspace    used 2757K, capacity 4490K, committed 4864K, reserved 1056768K
 class space  used 310K, capacity 386K, committed 512K, reserved 1048576K

3. 使用虚引用,不显式清除

GC日志如下,可以看到,不显式清除的虚引用会阻止GC回收内存,最终导致OOM。

2017-07-03T12:32:55.214+0800: [GC (System.gc()) [PSYoungGen: 43581K->728K(76288K)] 43581K->41696K(251392K), 0.0354037 secs] [Times: user=0.20 sys=0.00, real=0.04 secs]
2017-07-03T12:32:55.252+0800: [Full GC (System.gc()) [PSYoungGen: 728K->0K(76288K)] [ParOldGen: 40968K->41502K(175104K)] 41696K->41502K(251392K), [Metaspace: 2726K->2726K(1056768K)], 0.0258447 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 

2017-07-03T12:32:55.533+0800: [Full GC (System.gc()) [PSYoungGen: 41309K->40960K(76288K)] [ParOldGen: 164381K->164381K(175104K)] 205690K->205341K(251392K), [Metaspace: 2726K->2726K(1056768K)], 0.0389489 secs] [Times: user=0.25 sys=0.00, real=0.04 secs] 

2017-07-03T12:32:57.413+0800: [Full GC (System.gc()) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 1024541K->1024541K(1046016K)] 1065502K->1065502K(1122304K), [Metaspace: 2726K->2726K(1056768K)], 0.1263574 secs] [Times: user=0.94 sys=0.00, real=0.13 secs] 

2017-07-03T12:33:05.364+0800: [Full GC (System.gc()) [PSYoungGen: 40962K->40962K(76288K)] [ParOldGen: 2581022K->2581022K(2621952K)] 2621984K->2621984K(2698240K), [Metaspace: 2726K->2726K(1056768K)], 0.2474419 secs] [Times: user=1.69 sys=0.00, real=0.25 secs] 

2017-07-03T12:33:07.447+0800: [Full GC (Ergonomics) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744864K->2744864K(2777088K)] 2785824K->2785824K(2853376K), [Metaspace: 2726K->2726K(1056768K)], 0.2825105 secs] [Times: user=1.79 sys=0.00, real=0.28 secs]
2017-07-03T12:33:07.729+0800: [Full GC (Allocation Failure) [PSYoungGen: 40960K->40960K(76288K)] [ParOldGen: 2744864K->2744851K(2777088K)] 2785824K->2785812K(2853376K), [Metaspace: 2726K->2726K(1056768K)], 0.8902204 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at me.lk.TestReference.testPhantomRefLeakOOM(TestReference.java:109)
  at me.lk.TestReference.main(TestReference.java:50)
 [Times: user=3.79 sys=0.00, real=0.89 secs]
Heap
 PSYoungGen   total 76288K, used 43025K [0x000000076b400000, 0x0000000770900000, 0x00000007c0000000)
 eden space 65536K, 65% used [0x000000076b400000,0x000000076de04408,0x000000076f400000)
 from space 10752K, 0% used [0x000000076f400000,0x000000076f400000,0x000000076fe80000)
 to  space 10752K, 0% used [0x000000076fe80000,0x000000076fe80000,0x0000000770900000)
 ParOldGen    total 2777088K, used 2744851K [0x00000006c1c00000, 0x000000076b400000, 0x000000076b400000)
 object space 2777088K, 98% used [0x00000006c1c00000,0x0000000769484fb8,0x000000076b400000)
 Metaspace    used 2757K, capacity 4490K, committed 4864K, reserved 1056768K
 class space  used 310K, capacity 386K, committed 512K, reserved 1048576K

4. 使用虚引用,显式清除

显式清除的虚引用,不会影响GC,其GC行为和弱引用十分相似。

2017-07-03T12:45:14.774+0800: [GC (System.gc()) [PSYoungGen: 43581K->696K(76288K)] 43581K->41664K(251392K), 0.0458469 secs] [Times: user=0.17 sys=0.00, real=0.05 secs]
2017-07-03T12:45:14.820+0800: [Full GC (System.gc()) [PSYoungGen: 696K->0K(76288K)] [ParOldGen: 40968K->41502K(175104K)] 41664K->41502K(251392K), [Metaspace: 2726K->2726K(1056768K)], 0.0198788 secs] [Times: user=0.08 sys=0.00, real=0.02 secs]
2017-07-03T12:45:14.842+0800: [GC (System.gc()) [PSYoungGen: 42231K->32K(76288K)] 83734K->82495K(251392K), 0.0367363 secs] [Times: user=0.22 sys=0.00, real=0.04 secs]
2017-07-03T12:45:14.879+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82463K->41501K(175104K)] 82495K->41501K(251392K), [Metaspace: 2727K->2727K(1056768K)], 0.0198085 secs] [Times: user=0.08 sys=0.00, real=0.02 secs]
2017-07-03T12:45:14.901+0800: [GC (System.gc()) [PSYoungGen: 41786K->32K(76288K)] 83287K->82493K(251392K), 0.0327529 secs] [Times: user=0.19 sys=0.00, real=0.03 secs]
2017-07-03T12:45:14.934+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82461K->41501K(175104K)] 82493K->41501K(251392K), [Metaspace: 2727K->2727K(1056768K)], 0.0283782 secs] [Times: user=0.17 sys=0.00, real=0.03 secs]
2017-07-03T12:45:14.964+0800: [GC (System.gc()) [PSYoungGen: 41497K->32K(76288K)] 82998K->82493K(251392K), 0.0336216 secs] [Times: user=0.20 sys=0.00, real=0.03 secs]
2017-07-03T12:45:14.998+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82461K->41501K(175104K)] 82493K->41501K(251392K), [Metaspace: 2727K->2727K(1056768K)], 0.0211702 secs] [Times: user=0.13 sys=0.00, real=0.02 secs]
2017-07-03T12:45:15.021+0800: [GC (System.gc()) [PSYoungGen: 41309K->32K(76288K)] 82810K->82493K(251392K), 0.0445368 secs] [Times: user=0.30 sys=0.00, real=0.05 secs]
2017-07-03T12:45:15.066+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82461K->41501K(175104K)] 82493K->41501K(251392K), [Metaspace: 2727K->2727K(1056768K)], 0.0219968 secs] [Times: user=0.11 sys=0.00, real=0.02 secs]
2017-07-03T12:45:15.090+0800: [GC (System.gc()) [PSYoungGen: 41186K->32K(76288K)] 82688K->82493K(251392K), 0.0436528 secs] [Times: user=0.36 sys=0.00, real=0.04 secs]
2017-07-03T12:45:15.133+0800: [Full GC (System.gc()) [PSYoungGen: 32K->0K(76288K)] [ParOldGen: 82461K->41501K(175104K)] 82493K->41501K(251392K), [Metaspace: 2727K->2727K(1056768K)], 0.0219814 secs] [Times: user=0.11 sys=0.00, real=0.02 secs]

总结

以上就是本文关于Java中的引用和动态代理的实现详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续啊参阅本站:

浅谈Java注解和动态代理

Java使用代理进行网络连接方法示例

Java设计模式之代理模式原理及实现代码分享

如有不足之处,欢迎留言指出。感谢朋友们对本站的支持。

(0)

相关推荐

  • java 1.8 动态代理源码深度分析

    JDK8动态代理源码分析 动态代理的基本使用就不详细介绍了: 例子: class proxyed implements pro{ @Override public void text() { System.err.println("本方法"); } } interface pro { void text(); } public class JavaProxy implements InvocationHandler { private Object source; public Jav

  • Java中反射动态代理接口的详解及实例

    Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

  • Java动态代理分析及理解

    Java动态代理分析及理解 代理设计模式 定义:为其他对象提供一种代理以控制对这个对象的访问. 动态代理使用 java动态代理机制以巧妙的方式实现了代理模式的设计理念. 代理模式示例代码 public interface Subject { public void doSomething(); } public class RealSubject implements Subject { public void doSomething() { System.out.println( "call

  • Java动态代理机制详解_动力节点Java学院整理

    class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象: class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的.具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列.或者是Java虚拟机规范. 下面通过一段代

  • Java JDK 动态代理的使用方法示例

    本文主要和大家分享介绍了关于Java JDK 动态代理使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 前言 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. Spring AOP的实现对于接口来说就是使用的JDK的动态代理来实现的,而对于类的代理使用CGLIB来实现. JDK的动态代理,就是在程序运行的过程中,根据被代理的接口来动态生成代理类的class文

  • 浅谈Java注解和动态代理

    本文主要介绍Java中与注解和动态代理有关的部分知识,接下来我们看看具体内容. Annotation(注解) 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行. 1. 三个基本的Annotation: Override:限定重写父类方法, 该注解只能用于方法 Deprecated:用于表示某个程序元素(类, 方法等)已过时 SuppressWarnings:抑制编译器警告. 2.自定义Annotati

  • Java中的引用和动态代理的实现详解

    我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的.但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础? 如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>

  • Java动态代理的示例详解

    目录 定义 分类 案例 需求 方案一:jdk动态代理 方案二:cglib动态代理 分析 总结 定义 动态代理指的是,代理类和目标类的关系在程序运行的时候确定的,客户通过代理类来调用目标对象的方法,是在程序运行时根据需要动态的创建目标类的代理对象. 分类 jdk动态代理 cglib动态代理 案例 需求 苹果公司通过苹果代理商来卖手机 方案一:jdk动态代理 定义抽象接口 /** * 售卖手机的接口(代理模式--抽象角色) * @author:liyajie * @createTime:2022/2

  • Spring JDK动态代理实现过程详解

    这篇文章主要介绍了Spring JDK动态代理实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1. 创建项目 在 MyEclipse 中创建一个名称为 springDemo03 的 Web 项目,将 Spring 支持和依赖的 JAR 包复制到 Web 项目的 WEB-INF/lib 目录中,并发布到类路径下. 2. 创建接口 CustomerDao 在项目的 src 目录下创建一个名为 com.mengma.dao 的包,在该包下

  • 基于Java中最常用的集合类框架之HashMap(详解)

    一.HashMap的概述 HashMap可以说是Java中最常用的集合类框架之一,是Java语言中非常典型的数据结构. HashMap是基于哈希表的Map接口实现的,此实现提供所有可选的映射操作.存储的是对的映射,允许多个null值和一个null键.但此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 除了HashMap是非同步以及允许使用null外,HashMap 类与 Hashtable大致相同. 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性

  • Java中由substring方法引发的内存泄漏详解

    内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出. 内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样. 由substring方法引发的内存泄漏 substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全

  • java中Servlet监听器的工作原理及示例详解

    监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行. 监听器原理 监听原理 1.存在事件源 2.提供监听器 3.为事件源注册监听器 4.操作事件源,产生事件对象,将事件对象传递给监听器,并且执行监听器相应监听方法 监听器典型案例:监听window窗口的事件监听器 例如:swing开发首先制造Frame**窗体**,窗体本身也是一个显示空间,对窗体提供监听器,监听窗体方法调用或者属性改变:

  • Java中关于二叉树的概念以及搜索二叉树详解

    目录 一.二叉树的概念 为什么要使用二叉树? 树是什么? 树的相关术语! 根节点 路径 父节点 子节点 叶节点 子树 访问 层(深度) 关键字 满二叉树 完全二叉树 二叉树的五大性质 二.搜索二叉树 插入 删除 hello, everyone. Long time no see. 本期文章,我们主要讲解一下二叉树的相关概念,顺便也把搜索二叉树(也叫二叉排序树)讲一下.我们直接进入正题吧!GitHub源码链接 一.二叉树的概念 为什么要使用二叉树? 为什么要用到树呢?因为它通常结合了另外两种数据结

  • java 中mongodb的各种操作查询的实例详解

    java 中mongodb的各种操作查询的实例详解 一. 常用查询: 1. 查询一条数据:(多用于保存时判断db中是否已有当前数据,这里 is  精确匹配,模糊匹配 使用regex...) public PageUrl getByUrl(String url) { return findOne(new Query(Criteria.where("url").is(url)),PageUrl.class); } 2. 查询多条数据:linkUrl.id 属于分级查询 public Lis

  • java中 Set与Map排序输出到Writer详解及实例

     java中 Set与Map排序输出到Writer详解及实例 一般来说java.util.Set,java.util.Map输出的内容的顺序并不是按key的顺序排列的,但是java.util.TreeMap,java.util.TreeSet的实现却可以让Map/Set中元素内容以key的顺序排序,所以利用这个特性,可以将Map/Set转为TreeMap,TreeSet然后实现排序输出. 以下是实现的代码片段: /** * 对{@link Map}中元素以key排序后,每行以{key}={val

  • java 中基本算法之希尔排序的实例详解

    java 中基本算法之希尔排序的实例详解 希尔排序(Shell Sort)是插入排序的一种.也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法.该方法因DL.Shell于1959年提出而得名. 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止. 基本思想:算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差

随机推荐