Java 通过反射访问类加载器字段

Java 通过反射访问类加载器字段,java,reflection,classloader,byte-buddy,Java,Reflection,Classloader,Byte Buddy,我们有一个自定义类加载器的应用程序,我需要访问给定类的类加载器字段。但是,无法通过反射访问此字段:-( java.lang.Class的JavaDoc很清楚: // This field is filtered from reflection access, i.e. getDeclaredField // will throw NoSuchFieldException 这就是调用getDeclaredField(“classLoader”) 这能以某种方式获得吗(我看到IntelliJ调试是

我们有一个自定义类加载器的应用程序,我需要访问给定类的类加载器字段。但是,无法通过反射访问此字段:-(

java.lang.Class
的JavaDoc很清楚:

// This field is filtered from reflection access, i.e. getDeclaredField
// will throw NoSuchFieldException
这就是调用
getDeclaredField(“classLoader”)
这能以某种方式获得吗(我看到IntelliJ调试是以某种方式实现的;如何实现的?)


也许是一些byteBuddy的诡计?

你解释了这个问题的原因很好:减少内存使用。如果你设法从类加载器中删除类,可能会出现其他问题。例如,
equals()
instanceof
的工作方式不同(如果有的话),对象的反序列化可以以不同的方式工作,等等

1) 我建议您检查内存消耗的真正原因是什么:是类装入实例本身还是这个类装入器装入的类之一?例如,一个类可以有一些消耗大量内存的静态字段

2)如果类加载器实例消耗大量内存,考虑使用弱引用或缓存占用内存的字段。


3)如果你想尝试一个“更好的方式”:考虑java代理,<代码> Trase[()/代码>或<代码> RealEdgCube < /C>。通过这种方式,您可以向类加载器加载的类添加所需的行为,并简化消除不需要的引用和释放一些内存的任务。

回答您的问题:您仍然可以使用Byte Buddy通过创建类的镜像来访问字段,该类的镜像将有一个更精确的类布局,以便您可以使用
Unsafe
访问和修改字段,方法是首先创建一个类的镜像,以隐藏字段以防反射:

Class<?> mirror = new ByteBuddy()
    .with(TypeValidation.DISABLED)
    .redefine(Class.class)
    .name("mirror.Class")
    .noNestMate()
    .make()
    .load(null)
    .getLoaded();

Class<?> unsafeType = Class.forName("sun.misc.Unsafe");
Field theUnsafe = unsafeType.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Object unsafe = theUnsafe.get(null);

long offset = (Long) unsafeType
    .getMethod("objectFieldOffset", Field.class)
    .invoke(unsafe, mirror.getDeclaredField("classLoader"));
ClassLoader loader = (ClassLoader) unsafeType
    .getMethod("getObject", Object.class, long.class)
    .invoke(unsafe, Foo.class, offset - 4);
Class mirror=new ByteBuddy()
.with(TypeValidation.DISABLED)
.重新定义(Class.Class)
.name(“mirror.Class”)
.noNestMate()
.make()
.load(空)
.getLoaded();
Class unsafeType=Class.forName(“sun.misc.Unsafe”);
字段theUnsafe=unsafeType.getDeclaredField(“theUnsafe”);
不安全。设置为可访问(true);
Object unsafe=theUnsafe.get(null);
长偏移量=(长)非安全类型
.getMethod(“objectFieldOffset”,Field.class)
.invoke(不安全,镜像.getDeclaredField(“类加载器”);
ClassLoader=(ClassLoader)unsafeType
.getMethod(“getObject”,Object.class,long.class)
.invoke(不安全,Foo.class,偏移量-4);
镜像的原始类具有smiliar字段布局,因此您可以保留该布局并根据需要访问字段。您可以用同样的方法使用
putObject
覆盖字段值


然而,我会推荐这种方法吗?绝对不是。这也将停止在任何未来版本的Java中工作。如果您需要一些额外的时间来开发一个合适的解决方案,这可能是一个可行的方法,但从长远来看,您应该重构您的代码,使其不必要地工作。

Java调试器看到这个字段,因为他们依赖于在本机API之上工作的:和

从技术上讲,可以使用JNI访问/修改
classLoader
字段 或不安全,但请不要这样做

毕竟,为什么您认为这个字段是从反射访问中过滤出来的?正是为了防止人们通过修改场地来射中自己的脚

关键的一点是,类永远不应该与其类装入器分离。热点JVM无法逐个卸载类;相反,当没有对
ClassLoader
对象的实时引用保留时,它卸载整个类加载器

ClassLoader
对象被垃圾收集时,可以回收元空间的相应部分,以及该
ClassLoader
加载的所有类的元数据


现在,如果将
classLoader
字段清空,会发生什么?如果没有更多对相应的
类加载器的引用,它就有资格进行垃圾收集(看起来正是您想要的)。但这可能会触发类卸载,这将杀死此加载程序类的所有元数据。但是,类(及其实例)在没有元数据的情况下完全被破坏。在此之后,类或其实例上的任何操作都可能会随机使JVM崩溃或使应用程序不稳定。

这里提供的答案比这里提供的答案简单得多,但可能不适用于所有JVM供应商:您可以通过调用原始本机方法跳过筛选:

    Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
    getDeclaredFields0.setAccessible(true);
    Field[] unfilteredFields = (Field[]) getDeclaredFields0.invoke(Class.class, false);
然后你可以迭代这个数组来找到你的字段

您还可以使用此技巧完全删除过滤,因为它存储在
jdk.internal.reflect.Reflection
类内部的映射中,但默认情况下字段也会被过滤

private static volatile Map<Class<?>,String[]> fieldFilterMap;
private static volatile Map<Class<?>,String[]> methodFilterMap;

static {
    Map<Class<?>,String[]> map = new HashMap<Class<?>,String[]>();
    map.put(Reflection.class,
        new String[] {"fieldFilterMap", "methodFilterMap"});
    map.put(System.class, new String[] {"security"});
    map.put(Class.class, new String[] {"classLoader"});
    fieldFilterMap = map;

    methodFilterMap = new HashMap<>();
}
private静态volatile映射,String[]>methodFilterMap;
静止的{
映射,字符串[]>();
map.put(Reflection.class,
新字符串[]{“fieldFilterMap”,“methodFilterMap”});
put(System.class,新字符串[]{“security”});
put(Class.Class,新字符串[]{“classLoader”});
fieldFilterMap=地图;
methodFilterMap=newHashMap();
}

不是对实际问题的回答,而是解决了问这个问题的根本原因。我们的症状(我为什么问这个)

  • 我们广泛使用自定义类加载(每分钟创建数十个“来来去去去”的
    类加载程序)

  • GC没有“足够快”收集这些类加载器,即使它们没有基于heapdump分析的直接引用。只有相当多的
    WeakReference
    s

  • Classloader
    s在很长一段时间内没有进行GCD(我们关闭了类加载器,这些类加载器在关闭后数小时仍然存在于堆中),这导致了元空间的急剧扩展(3G+)
银布