简介
来自JEP371
隐藏类,其他类的字节码不能直接使用的类。隐藏类适用于在运行时生成类并通过反射间接使用它们。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类进行卸载。
原因
近几年,产生很多基于jvm的动态语言,例如groovy,kotlin等,基本都是基于动态代理来实现动态语言的功能的,而像lambda表达式,也是在运行过程中传输一个字节码,而该字节码可以动态生成一个类并且实例化。
这样就会在程序执行的过程中产生很多的类,二基于传统关于定于标准java类的api上看(ClassLoader::defineClass
和Lookup::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
获取这个隐藏类的类型,隐藏类和普通的类一样使用,但是会有以下四点注意的地方:
Class::getName 返回不是二进制名称的字符串
Class::getCanonicalName
返回值是null,表示示隐藏的类没有全限定名。(注意,Java语言中的匿名类对象该方法的返回值也是null。)在隐藏类中声明的所有字段都是不可修改的,无论是使用
Field::set
还是其他修改字段的方法都会抛出IllegalAccessException
(包括反射设置accessible
为True
时)隐藏类对象不能被 instrumentation agents修改,也不能被
JVM TI agents
重新定义或转换。
调试中跟踪隐藏类
添加启动参数
-XX:+UnlockDiagnosticVMOptions
-XX:+ShowHiddenFrames
有三个api可以获取隐藏类的堆栈信息
Throwable::getStackTrace
Thread::getStackTrace
StackWalkerAPI