在查看线上日志的时候发现时不时有以下日志打印出来:
[Loaded sun.reflect.GeneratedMethodAccessor167 from __JVM_DefineClass__]
截图如下:
开始感觉很奇怪为什么时不时会打印这些东西,一开始以为是jdk配置的问题,就去度娘查了一下。然后看到了这篇文章:
http://rednaxelafx.iteye.com/blog/548536
简单来说这是Sun的JDK中关于方法反射调用的一个实现细节,为了提高多次反射调用时的效率而在运行时生成专用的Java类实现调用逻辑。
因为该日志由反射产生,所以初步猜测这应该是项目中使用了AOP生成代理类时产生的log。下面梳理一下AOP代理的具体流程。
Spring有两种生成代理对象的方法,第一种是通过ProxyFactory,第二种是通过ProxyFactoryBean。第一种获取比较简单,但是需要手工的进行写代码,而第二种是通过Spring的IOC机制来控制Bean的生成。但是无论是ProxyFactory或者ProxyFactoryBean都是要通过createAopProxy().getProxy()来获取相应的代理对象。以下通过跟踪ProxyFactoryBean的代码来查找原因:
首先,找到ProxyFactoryBean的getObject方法
Spring Bean 默认scope="singleton" 即Bean是单例的,只有当在Bean中指明 scope="prototype" 时 Bean 才是多例形式。在 getObject() 方法中判断是要代理的类是单例还是多例的,然后调用对应的方法。
单例模式时调用getSingletonInstance()方法,在getSingletonInstance方法中引入了super中的方法,super是指ProxyCreatorSupport,这里ProxyCreatorSupport是ProxyFactoryBean和ProxyFactory的父类,已经做了很多工作,只需在ProxyFactoryBean的getObject()方法中通过父类的createAopProxy()取得相应的AopProxy。
跟踪createAopProxy方法,追踪到了ProxyCreatorSupport中,然后,借助了AopProxyFactory,此时得到的aopProxyFactory,在构造函数中已经定义为new DefaultAopProxyFactory() 进入DefaultAopProxyFactory中,找到createAopProxy方法,在这里判断是调用JDK动态或者CGlib动态中的一种。
在这里有个bug,在spring4.2.4之前的版本中ObjenesisStd 并没有被声明为static类型,如图2 所以在使用Cglib方式生成代理类时,每次调用createAopProxy时会执行以下代码:
return new ObjenesisCglibAopProxy(config);
此时会实例化一个新的 private final ObjenesisStd objenesis; 实例。
图1 spring-4.0.5.RELEASE
图2 spring-4.2.4.RELEASE
那么这个问题会有什么影响呢?
当通过ObjenesisCglibAopProxy去生成代理类的时候,会调用createProxyClassAndInstance()方法。
在这个方法内部会调用 this.objenesis.newInstance(enhancer.createClass()); newInstance方法继承自父类ObjenesisBase,该方法最终调用的是getInstantiatorOf()方法,这个方法里面有个cache,主要是保证一个代理增强类对象只有一个class字节码。
所以,当ObjenesisStd 为非静态常量时,每次new ObjenesisCglibAopProxy(config); 时cache都会失效,所以会去调用:
org.springframework.objenesis.strategy.SingleInstantiatorStrategy.newInstantiatorOf(Class)方法。
以下是后面的方法调用链:
1、java.lang.reflect.Constructor.newInstance(Object...)
2、java.lang.reflect.Constructor.acquireConstructorAccessor()
3、sun.reflect.ReflectionFactory.newConstructorAccessor(Constructor)
4、sun.reflect.MethodAccessorGenerator.generateConstructor(Class, Class[], Class[], int)
5、sun.reflect.MethodAccessorGenerator.generate(Class, String, Class[], Class, Class[], int, boolean, boolean, Class) 6、sun.reflect.MethodAccessorGenerator.generateName(boolean, boolean)
当执行到generateName()方法时,我们就能看到文章开始时打印出的那些的字符串了。
当需要被代理的要声明为非单例时 scope="prototype",由于代理类调用非常频繁时,会不断产生增强类字节码,会引发PermGen持续增长。当PermGen满了时就会引发FullGC。此时建议Spring升级到4.2.4以后版本。