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

时间:2022-04-07 09:00:14

我们知道,动态代理(这里指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
2
3
4
5
6
7
8
9
10
//代码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;
  }
}

其引用关系如下图所示:

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

threadlocal中的引用关系

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

2.动态代理

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

?
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
//代码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」的代理类,并生成相应的对象。

?
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
//代码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<?>[]),代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//代码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?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//代码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行为:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//代码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,释放内存。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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行为和弱引用十分相似。

?
1
2
3
4
5
6
7
8
9
10
11
12
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中的引用和动态代理的实现详解的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。感谢朋友们对本站的支持。

原文链接:http://www.jianshu.com/p/77dfeccac85d