JAVA 隐藏类(Hidden Classes)

简介

来自JEP371
隐藏类,其他类的字节码不能直接使用的类。隐藏类适用于在运行时生成类并通过反射间接使用它们。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类进行卸载。

原因

近几年,产生很多基于jvm的动态语言,例如groovy,kotlin等,基本都是基于动态代理来实现动态语言的功能的,而像lambda表达式,也是在运行过程中传输一个字节码,而该字节码可以动态生成一个类并且实例化。

这样就会在程序执行的过程中产生很多的类,二基于传统关于定于标准java类的api上看(ClassLoader::defineClassLookup::defineClass)这些动态生成的类根本无法被识别出来。这样这些动态类型很容易被发现,并且声明周期会变得很长。

如果可以从标准API去定义一个可以隐藏且生命周期有限的类,那么肯定能够提高所有基于jvm的语言的实现效率,例如:

  • java.lang.reflect.Proxy可以定义隐藏类作为实现代理接口的代理类
  • java.lang.invoke.StringConcatFactory 可以生成隐藏类来保存常量连接方法
  • java.lang.invoke.LambdaMetaFactory 可以生成隐藏的nestmate类,以保存访问封闭变量的lambda主体
  • JavaScript引擎可以为从JavaScript程序转换的字节码生成隐藏类,因为当引擎不再使用这些类时,这些类将被卸载

创建一个隐藏类

  • 普通类是通过调用创建的ClassLoader::defineClass

  • 隐藏类是通过调用创建的java.lang.invoke.MethodHandles.Lookup#defineHiddenClass
    具体的用法可以看jdk15中的java.lang.invoke.InnerClassLambdaMetafactory#generateInnerClass

defineHiddenClass(byte[] bytes, boolean initialize, ClassOption... options)有三个参数

  • bytes 一个符合java虚拟机规范的字节码
  • initialize 如果为true,那么这个类会被初始化
  • options java类的类型详见java.lang.invoke.MethodHandles.Lookup.ClassOption

隐藏类的使用

Lookup::defineHiddenClass会返回一个Lookup对象,我们可以调用Lookup::lookupClass获取这个隐藏类的类型,隐藏类和普通的类一样使用,但是会有以下四点注意的地方:

  1. Class::getName 返回不是二进制名称的字符串

  2. Class::getCanonicalName返回值是null,表示示隐藏的类没有全限定名。(注意,Java语言中的匿名类对象该方法的返回值也是null。)

  3. 在隐藏类中声明的所有字段都是不可修改的,无论是使用Field::set还是其他修改字段的方法都会抛出IllegalAccessException(包括反射设置accessibleTrue时)

  4. 隐藏类对象不能被 instrumentation agents修改,也不能被JVM TI agents重新定义或转换。

调试中跟踪隐藏类

添加启动参数

-XX:+UnlockDiagnosticVMOptions
-XX:+ShowHiddenFrames

有三个api可以获取隐藏类的堆栈信息

Throwable::getStackTrace
Thread::getStackTrace
StackWalkerAPI

你可能感兴趣的:(JAVA 隐藏类(Hidden Classes))