如何获取java类里的私有方法

如何获取java类里的私有方法,第1张

异步数据转化为同步数据处理的两种情况:

第一种:定点定时将异步转化为同步

举例:在A方法中的某处需要调用B方法,并且在拿到B方法的返回值后,才可以继续完成A方法中剩下的功能,但是js的代码是从上至下的运行,会出现B方法还没有走完,A方法的代码已经开始往下走,会拿不到B方法的返回值,这个时候就需要异步获取(A调用B 有一定的位置和时间限制)

解决方法:用async和await

第二种:

举例:在A方法中需要B方法中的值才可以进行下去,但是A方法中不需要调用B方法,可以在代码刚开始就拿到B方法中的值,或者在B方法完成时再调用A方法(调用B方法没有特定的时间点限制)

反射机制,简单写了一个例子,不懂的可以看一下相关api public class OwerMethodParam {

public static void main(String[] args) {

new OwerMethodParam()test("bb");

}

public void test(String aa) {

Method[] methods = OwerMethodParamclassgetDeclaredMethods(); //取得这个类的所有方法

if (methods != null) {

for (int i = 0; i < methodslength; i++) {

Method method = methods[i];

if ("test"equals(methodgetName())) { //取得本方法,这个方法是test,所以就用test比较

Class<>[] paramsClass = methodgetParameterTypes(); //取得参数列表的所有类

if (paramsClass != null) {

for (Class<> class1 : paramsClass) {

Systemoutprintln(class1getName());

}

}

break;

}

}

在对某个服务进行迁移时,我们观察到数据如下。不同颜色代表不同的机器,可以发现很多机器在不同的时间段内都出现了 GC overhead 达到了100%的情况。意味着这段时间内,该机器不能对外提供服务。这是一个很危险的情况,而且并不是偶然。

原因分析

我们找到其中一台机器在GC overhead到达100%时的GC日志,如下:

从日志中可以发现,这两次GC都full GC。在G1中正常情况下是只有 young GC 和 mixed GC , full GC 在 G1 GC 无法满足内存分配需求时就会切换到 serial old GC 来收集整个堆内存。严格意义上来讲, full GC 并不属于G1,而是G1无法满足需求时使用的兜底策略。另外,我们还可以从时间戳和执行时间上发现,这两次GC是连续的,并且花费的时间也很长,这就是 GC Overhead 会升到100%的原因。

第一次GC的原因是 Metadata GC Threshold ,这表示是由MetaSpace空间不足引起的,而经过第一次GC,Metaspace空间并没有减少,于是引起了第二次GC,第二次GC会尝试清除软引用,但是MetaSpace空间依然没有减少。看到这里,第一反应就是MetaSpace有问题。在JDK 18中,为了更灵活的管理内存,永久代被移除,取而代之的是Metaspace。

MetaSpace 不再算是JVM的内存,所以我们在计算内存占用时需要用 metaspace+ jvm 的内存。配置永久代的相关参数 PermSize 以及 MaxPermSize 也不会在生效了。检查了启动参数之后,当前的metaspace只有512MB。当在大量使用反射,动态代理,动态生成JSP功能时,会导致Metaspace空间不足,导致无法正常回收。

解决方案

了解原因之后,解决起来就很简单,在启动参数中将 maxMetaSpaceSize 设置为1024MB。改动后 GC Overhead 如下图所示,100%的情况不再出现了。

Metaspace 到底放了什么

既然是Metaspace满了,那我们得看Metaspace里究竟放了什么,我们知道Metaspace里主要存的是类的原始数据,比如我们加载了一个类,那这个类的信息会在Metaspace里分配内存来存储它的一些数据结构,所以大部分情况下,Metaspace的使用量和加载的类个数是关系很大的。上文就是类的数量非常多导致的Metaspace空间不足。

另外还有一种情况Metaspace溢出,是有地方动态构建一个类加载器,同时不断加载一个类,我们遇到过一个案例,通过jmap命令,统计下 sunreflectDelegatingClassLoader 的个数居然达到了几万个

那基本可以锁定是反射类加载器导致Metaspace溢出的原因了,那究竟为什么会有这么多反射类加载器呢,反射类加载器又是什么,接下来先简单说下反射的原理

在java中,反射大部分是这么写,假设有个class A

获取Method

要调用首先要获取Method,而获取Method的逻辑是通过Class这个类来的,而关键的几个方法和属性如下:

在Class里有个关键的属性叫做 reflectionData ,这里主要存的是每次从jvm里获取到的一些类属性,比如方法,字段等,这是它在Class类里的定义

这个属性主要是 SoftReference 的,也就是在某些内存比较苛刻的情况下是可能被回收的,不过正常情况下可以通过 -XX:SoftRefLRUPolicyMSPerMB 这个参数来控制回收的时机,一旦时机到了,只要GC发生就会将其回收,那回收之后意味着再有需求的时候要重新创建一个这样的对象,同时也需要从JVM里重新拿一份数据,那这个数据结构关联的Method,Field字段等都是重新生成的对象。

后面的博文会讲 SoftReferenc 不断加载导致的麻烦。

getDeclaredMethod 方法从主要看 searchMethods(privateGetDeclaredMethods)privateGetDeclaredMethods 返回的方法列表里复制一个Method对象返回。如果 reflectionData 这个属性的 declaredMethods 非空,那 privateGetDeclaredMethods 就直接返回其就可以了,否则就从JVM里load,并赋值给 reflectionData 的字段。

searchMethods主要调用

getReflectionFactory()copyMethod(res) ->langReflectAccess()copyMethod(arg) ->ReflectAccesscopyMethod->methodcopy

由此可见, getDeclaredMethod 方法返回的Method对象其实都是一个新的对象,所以不宜调用的过多,如果调用频繁最好缓存起来。

Method调用

root属性其实上面已经说了,主要指向被 copyMethod 对象,也就是当前这个Method对象其实是根据root这个Method构建出来的,因此存在一个 root Method 派生出多个Method的情况。

methodAccessor 这个很关键了,其实 Methodinvoke 方法就是调用 methodAccessor 的 invoke 方法, methodAccessor 这个属性如果root本身已经有了,那就直接用 root 的 methodAccessor 赋值过来,否则的话就创建一个。

MethodAccessor的实现

MethodAccessor 本身就是一个 interface , 有三个实现。

DelegatingMethodAccessorImpl

NativeMethodAccessorImpl

GeneratedMethodAccessorXXX

其中 DelegatingMethodAccessorImpl 是最终注入给 Method 的 methodAccessor 的,也就是某个Method的所有的invoke方法都会调用到这个 DelegatingMethodAccessorImplinvoke 。 它是代理类,真正的实现可以是下面的两种

Default 实现是 NativeMethodAccessorImpl ,而 GeneratedMethodAccessorXXX 是为每个需要反射调用的 Method 动态生成的类,后的XXX是一个数字,不断递增的

并且所有的方法反射都是先走 NativeMethodAccessorImpl ,默认调用 ReflectionFactoryinflationThreshold 次之后,才生成一个 GeneratedMethodAccessorXXX 类,生成好之后就会走这个生成的类的invoke方法了

那如何从 NativeMethodAccessorImpl 过度到 GeneratedMethodAccessorXXX 呢,来看看 NativeMethodAccessorImpl 的 invoke 方法

ReflectionFactoryinflationThreshold() default 是15次。我们可以通过 -DsunreflectinflationThreshold 来指定,我们还可以通过 -DsunreflectnoInflation=true 来直接绕过上面的15次 NativeMethodAccessorImpl 调用,和

-DsunreflectinflationThreshold=0 的效果一样的

而 GeneratedMethodAccessorXXX 都是通过 new MethodAccessorGenerator()generateMethod 来生成的,一旦创建好之后就设置到 DelegatingMethodAccessorImpl 里去了,这样下次 Methodinvoke 就会调到这个新创建的 MethodAccessor 里了。

动态创建出来的类由 DelegatingClassLoader 来加载。请看 ClassDefiner , new DelegatingClassLoader 来加载新类。

每次new类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载。

NativeMethodAccessorImplinvoke 其实都是不加锁的,如果并发很高的时候,可能同时有很多线程进入到创建 GeneratedMethodAccessorXXX 类的逻辑里,假如有1000个线程都进入到创建 GeneratedMethodAccessorXXX 的逻辑里,那意味着多创建了999个无用的类,这些类会一直占着内存,直到能回收MetaSpace的GC发生才会回收

sd-jdijar 来 dumpsd-jdijar 里自带的 sunjvmhotspottoolsjcoreClassDump 可以把类的class内容dump到文件里。

ClassDump里可以设置两个 System properties :

sunjvmhotspottoolsjcorefilter Filter的类名

sunjvmhotspottoolsjcoreoutputDir 输出的目录

首先写一个filter类

然后编译成class文件

要使用这个首先需要把 sa-jdijar 加到java的 classpath 里。

进入 刚刚写的filter类的class文件的目录下。执行

把MyFilter改为你自己的类名,pid为目标 java进程的pid(可以使用jps查看)。然后就会在<folder>产生相应的class文件。

这样我们就可以将所有的 GeneratedMethodAccessor 给dump下来了,这个时候我们再通过 javap -verbose GeneratedMethodAccessor0 随便看一个类的字节码

看36行就可以知道到你那个方法导致的Method 反射过多。

以上就是关于如何获取java类里的私有方法全部的内容,包括:如何获取java类里的私有方法、在JAVA中,怎么利用反射获取一个方法、java如何解析webservice的method等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9308653.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-27
下一篇 2023-04-27

发表评论

登录后才能评论

评论列表(0条)

保存