Method Handle与反射

如无特殊说明,本文所有代码均基于JDK1.8.0_221

Method Handle入门

反射我们都知道,为我们提供了运行时对类的成员方法访问的手段,极大地提高了Java语言的动态性,但是反射往往意味着效率低下,但是在JDK7以前为了利用反射带来的动态性,我们又不得不使用反射,随着JDK7中新加入的一组API,JDK为我们提供了全新的选择, 也就是Method Handle

什么是Method Handle?

这里引用JDK中的说明

A method handle is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values.

方法句柄是一个有类型的,可以直接执行的指向底层方法、构造器、field等的引用,可以简单理解为函数指针,它是一种更加底层的查找、调整和调用方法的机制。

如何使用Method Handle?

  • 首先我们需要一个Lookup,lookup是一个创建method handles的工厂,同样引用JDK的说明如下:

A lookup object is a factory for creating method handles,when the creation requires access checking. Method handles do not perform access checks when they are called, but rather when they are created.

Therefore, method handle access restrictions must be enforced when a method handle is created.

上面就其实以及提到了Method handle的不同之处,它的访问检查在创建时就完成了,而发射需要等到调用时,这个等两者对比的时候再说

根据方法修饰符的不同,采用不同的工厂

//访问public方法
MethodHandles.Lookup lookup1 = MethodHandles.publicLookup();
//访问private、protected方法
MethodHandles.Lookup lookup2 = MethodHandles.lookup();
  • 然后我们还需要创建Method Type,它用来描述被访问的方法的参数类型、返回值类型,引用MethodType类的注释如下

A method type represents the arguments and return type accepted and returned by a method handle, or the arguments and return type passed and expected by a method handle caller. Method types must be properly matched between a method handle and all its callers, and the JVM's operations enforce this matching at, specifically during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact} and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution of {@code invokedynamic} instructions.

可以看到,JVM强制要求声明的Method Type与实际调用方法的参数类型必须匹配。

通过Method Type的静态方法,我们可以非常简单的声明一个Method Type,传入方法的返回值类型和参数类型即可

//以String的length方法为例(void同理,void.class)
MethodType mt = MethodType.methodType(int.class);
  • 再者,通过lookup创建我们的MethodHandle
  1. 访问普通方法
//接上面的length方法
MethodHandle methodHandle = lookup1.findVirtual(String.class, "length", mt);
  1. 访问静态方法
//以valueOf方法为例
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
} MethodType mt2 = MethodType.methodType(String.class,Object.class);
MethodHandle valueOf = lookup1.findStatic(String.class, "valueOf", mt2);
  1. 访问构造函数
MethodType mt3= MethodType.methodType(void.class,String.class);
MethodHandle string = lookup1.findConstructor(String.class, mt3);
  1. 访问私有方法
//以checkBounds方法为例
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
} Method checkBounds = String.class.getDeclaredMethod("checkBounds", byte[].class, int.class, int.class);
checkBounds.setAccessible(true);
MethodHandle unreflect = lookup2.unreflect(checkBounds);
unreflect.invoke(new byte[]{},-1,-1);
  1. 访问公有成员

在JDK8中,我还没有找到访问私有成员的方法。

//访问一个自定义类的共有成员
MethodHandle value = lookup2.findGetter(A.class, "value", int.class);
int val = (int)value.invoke(new A(2));
System.out.println(val); static class A{
int value;
A(int value){
this.value=value;
}
}
  • 最后一步就是调用Method Handle了

按照对参数数目、参数类型的要求限制不同,分为三类invokeWithArguments(),invoke(),invokeExact()

  1. invokeWithArguments要求最低,它接收变长参数,允许参数拆装箱类型转换
  2. invoke要求第二,它接收固定的参数列表,允许参拆装箱,类型转换
  3. invokeExact要求最严格,它啥都不允许,参数类型不匹配就报错

示例如下:

//invokeWithArguments,接收变长数组
MethodType mt5 = MethodType.methodType(List.class, Object[].class);
MethodHandle asList = lookup1.findStatic(Arrays.class, "asList", mt5);
List<Integer> integers = (List<Integer>) asList.invokeWithArguments(1, 2);
System.out.println(integers); //invokeExact,直接报错
MethodType mt = MethodType.methodType(int.class, int.class, int.class);
MethodHandle sumMH = lookup1.findStatic(Integer.class, "sum", mt);
int sum = (int) sumMH.invokeExact(1, 1l);
System.out.println(sum); //Exception in thread "main" java.lang.invoke.WrongMethodTypeException: expected (int,int)int but found (int,long)int

Method Handle和反射性能对比

测试程序如下:

package com.hustdj.jdkStudy.methodHandle;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; public class MethodHandleVsReflect {
public static void main(String[] args) {
MethodHandleVsReflect methodHandleVsReflect = new MethodHandleVsReflect();
try {
methodHandleVsReflect.testDirect();
methodHandleVsReflect.testReflect();
methodHandleVsReflect.testMethodHandle();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} public void testDirect(){
A a = new A();
B b = new B();
//预热
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==0){
a.count(1);
}else{
b.count(1);
}
}
//统计性能
long startNano=System.nanoTime();
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==0){
a.count(1);
}else{
b.count(1);
}
}
System.out.format("计算结果为: a: %d b: %d",a.i,b.i);
double average = (System.nanoTime() - startNano) / 100_0000_0000.0;
System.out.println("直接调用平均耗时(ns):"+average);
} public void testReflect() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//反射
Method countA = A.class.getMethod("count", Integer.class);
Method countB = B.class.getMethod("count",Integer.class);
A a = new A();
B b = new B();
//预热
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==1){
countA.invoke(a,1);
}else{
countB.invoke(b,1);
}
}
//统计性能
long startNano=System.nanoTime();
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==1){
countA.invoke(a,1);
}else{
countB.invoke(b,1);
}
}
System.out.format("计算结果为: a: %d b: %d",a.i,b.i);
double average = (System.nanoTime() - startNano) / 100_0000_0000.0;
System.out.println("反射平均耗时(ns):"+average);
} public void testMethodHandle() throws Throwable {
A a = new A();
B b = new B();
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
MethodType mt = MethodType.methodType(void.class,Integer.class);
MethodHandle countA = publicLookup.findVirtual(A.class, "count", mt);
MethodHandle countB = publicLookup.findVirtual(B.class, "count", mt);
Integer int_1 = new Integer(1);
//预热
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==1){
countA.invoke(a,1);
}else{
countB.invoke(b,1);
}
}
//统计性能
long startNano=System.nanoTime();
for (long i = 0; i < 100_0000_0000L; i++) {
if ((i&1)==1){
countA.invoke(a,1);
}else{
countB.invoke(b,1);
}
}
System.out.format("计算结果为: a: %d b: %d",a.i,b.i);
double average = (System.nanoTime() - startNano) / 100_0000_0000.0;
System.out.println("methodHandle平均耗时(ns):"+average);
} public class A{
long i=0;
public void count(Integer a){
i++;
}
} public class B{
long i=0;
public void count(Integer a){
i++;
}
}
}

测试结果如下:

//1
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.20156184
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.05123386
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):7.09741082 //2
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.02183322
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.44056289
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):6.08221384 //3
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.22246659
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.41759047
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):5.90614517

通过给JVM添加参数

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

可以发现通过反射调用的invoke已经进行了方法内联

@ 147 java.lang.reflect.Method::invoke (62 bytes) inline (hot)

@ 15 sun.reflect.Reflection::quickCheckMemberAccess (10 bytes) inline (hot)

@ 1 sun.reflect.Reflection::getClassAccessFlags (0 bytes) (intrinsic)

@ 6 java.lang.reflect.Modifier::isPublic (12 bytes) inline (hot)

@ 56 sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes) inline (hot)

-> TypeProfile (18366/18366 counts) = sun/reflect/DelegatingMethodAccessorImpl

! @ 6 sun.reflect.GeneratedMethodAccessor2::invoke (66 bytes) inline (hot)

! @ 6 sun.reflect.GeneratedMethodAccessor1::invoke (66 bytes) inline (hot)

-> TypeProfile (5296/10593 counts) = sun/reflect/GeneratedMethodAccessor1

-> TypeProfile (5297/10593 counts) = sun/reflect/GeneratedMethodAccessor2

@ 40 com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect$A::count (11 bytes) inline (hot)

@ 168 java.lang.reflect.Method::invoke (62 bytes) inline (hot)

@ 15 sun.reflect.Reflection::quickCheckMemberAccess (10 bytes) inline (hot)

@ 1 sun.reflect.Reflection::getClassAccessFlags (0 bytes) (intrinsic)

@ 6 java.lang.reflect.Modifier::isPublic (12 bytes) inline (hot)

@ 56 sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes) inline (hot)

-> TypeProfile (18366/18366 counts) = sun/reflect/DelegatingMethodAccessorImpl

! @ 6 sun.reflect.GeneratedMethodAccessor2::invoke (66 bytes) inline (hot)

! @ 6 sun.reflect.GeneratedMethodAccessor1::invoke (66 bytes) inline (hot)

-> TypeProfile (5296/10593 counts) = sun/reflect/GeneratedMethodAccessor1

-> TypeProfile (5297/10593 counts) = sun/reflect/GeneratedMethodAccessor2

@ 40 com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect$B::count (11 bytes) inline (hot)

难道method handle的性能止步于此了嘛?

不,通过将method handle置为static final的变量,我们甚至可以达到直接调用的效率

static final MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
static final MethodType mt = MethodType.methodType(void.class,Integer.class);
private static final MethodHandle countA=getCountA();
private static final MethodHandle countB=getCountB();
private static MethodHandle getCountA(){
try {
return publicLookup.findVirtual(A.class, "count", mt);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
} private static MethodHandle getCountB(){
try {
return publicLookup.findVirtual(B.class, "count", mt);
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}

调用结果为:

//1
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):0.98582866
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.34687653
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):1.17300033
//2
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):0.97544322
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.0777855
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):0.95403012
//3
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.24535073
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.53959802
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):0.97747171

非常的amazing啊,我们简简单单只是从局部变量变成了静态变量,效率直逼直接调用了,为什么呢?

前后对比一下加了static final前的inline信息和不加static final的信息

  • 加入static final的log

com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect::testMethodHandle @ 33 (200 bytes)

@ 47 java.lang.invoke.LambdaForm$MH/1554547125::invokeExact_MT (21 bytes) force inline by annotation

...

@ 17 com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect$A::count (11 bytes) inline (hot)

...

@ 17 com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect$B::count (11 bytes) inline (hot)

我们发现出现了inline,而且这里的内联是直接内联到了最外层的testMethodHandle方法中,区别于method.invoke()的内联

  • 没有static final的log

com.hustdj.jdkStudy.methodHandle.MethodHandleVsReflect::testMethodHandle @ 126 (239 bytes)

@ 140 java.lang.invoke.LambdaForm$MH/1554547125::invokeExact_MT (21 bytes) force inline by annotation

@ 2 java.lang.invoke.Invokers::checkExactType (30 bytes) force inline by annotation

@ 11 java.lang.invoke.MethodHandle::type (5 bytes) accessor

@ 6 java.lang.invoke.Invokers::checkCustomized (20 bytes) force inline by annotation

@ 17 java.lang.invoke.MethodHandle::invokeBasic(LL)V (0 bytes) receiver not constant

@ 151 java.lang.invoke.LambdaForm$MH/1554547125::invokeExact_MT (21 bytes) force inline by annotation

@ 2 java.lang.invoke.Invokers::checkExactType (30 bytes) force inline by annotation

@ 11 java.lang.invoke.MethodHandle::type (5 bytes) accessor

@ 6 java.lang.invoke.Invokers::checkCustomized (20 bytes) force inline by annotation

@ 17 java.lang.invoke.MethodHandle::invokeBasic(LL)V (0 bytes) receiver not constant

并没有看到内联的出现,导致Method Handle的性能大涨的原因找到了,也就是方法内联。

至于为什么会出现这样的情况,这里参考了一下别人的博客https://shipilev.net/jvm/anatomy-quarks/17-trust-nonstatic-final-fields/

虽然他这里提到的是Nostatic final field,但是我们这里是static final field能够直接进行常量替换,不用考虑那么复杂,但是引用博客中的一句话

Constant folding through these final fields is the corner-stone for performance story for MethodHandle-s, VarHandle-s, Atomic*FieldUpdaters` and other high-performance implementations from the core library.

constant folding是MethodHandle-s, VarHandle-s, Atomic*FieldUpdaters`这些高性能实现的性能基石,那method handle可以,反射又行不行呢?测试一下

修改代码如下:

static final Method countAReflect=getReflectA();
static final Method countBReflect=getRelfectB();
private static Method getReflectA(){
try {
return A.class.getMethod("count", Integer.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
return null;
}
}
private static Method getRelfectB(){
try {
return B.class.getMethod("count",Integer.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
return null;
}
}

测试结果为:

//1
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.22609891
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.43359246
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):0.92919298
//2
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):1.00782253
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):3.60654
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):0.99133146

然而反射并不行!可能要深入JVM才能了解为何Method Handle能做到直接调用的性能吧。

初现端倪

照理说

  • method handle创建时就进行了类型检查,而method.invoke每次调用都需要进行检查
  • method invoke是用数组包装参数的,每次都需要创建一个新的数组
  • method handle在创建之后就是固定的,MH.invoke()自身都可以被内联,而Method.invoke()所有对方法的反射调用都需要经过它,它自身就很难被内联到调用方

但是事实来看method handle的性能很难让人满意大部分情况下都不如反射(除开static final这样的方式),这是为什么呢?

因为JDK8对反射进行了大量的优化,把代码放到JDK7中跑一下结果如下:

//1
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):0.97767317
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):14.81999815
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):10.21145029
//2
计算结果为: a: 10000000000 b: 10000000000直接调用平均耗时(ns):0.97137786
计算结果为: a: 10000000000 b: 10000000000反射平均耗时(ns):14.79272622
计算结果为: a: 10000000000 b: 10000000000methodHandle平均耗时(ns):9.30254589

那个熟悉的慢反射又回来了,在JDK1.7一下,Method Handle确实比反射要快上一些,但是还是比JDK8中慢

Method.invoke()和MethodHandle.invoke()同样是native,为什么反射能够被内联?

JDK的设计者对于Method.invoke()采取了两种策略,一种是native也就是C++的实现方式很难进行内联优化,另一种是在某个方法调用超过阈值后会利用字节码生成技术在内存中生成一个类(暂时没有找到将这个类保存下来的方法),包含要调用的方法,然后加载进虚拟机,这个时候就能内联优化了,而MethodHandle.invoke直接就是native调用,并没有上面的策略,自然也就无法内联,至于设置为static final之后为什么就可以内联了,这个。。。

参考链接

此外,这里再贴一个其他人做的method handle的性能测试

http://chriskirk.blogspot.com/2014/05/which-is-faster-in-java-reflection-or.html

https://www.iteye.com/blog/rednaxelafx-548536

题外话

将循环次数减低到10000,会发现一个奇怪的现象,直接调用居然最慢,感兴趣的可以自行测试一下,会出现made not entrant,JIT会进行反优化,附这个问题的另一个链接https://zhuanlan.zhihu.com/p/82118137

欢迎讨论!!

MethodHandleVS反射的更多相关文章

  1. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  2. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  6. .NET面试题系列[6] - 反射

    反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射

    此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...

  9. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

随机推荐

  1. Java蓝桥杯练习题——求小数n位后3个数

    求整数除法小数点后第n位开始的3位数 位数不足的补0,如0.125小数第3位后三位:0.12500→500 输入格式:a b n,空格分开,a是被除数,b是除数,n是小数后的位置 输出格式:3位数字, ...

  2. GraphicsLab 之 Atmospheric Scattering (一)

    作者:i_dovelemon 日期:2020-10-11 主题:Atmospheric Scattering, Volume Scattering, Rayleigh Scattering, Mie ...

  3. 网络篇:朋友面试之TCP/IP,回去等通知吧

    前言 最近和一同学聊天,他想换工作,然后去面了一家大厂.当时,他在简历上写着精通TCP/IP,本着对TCP协议稍有了解,面试官也不会深问的想法,就写了精通二字.没想到,大意了 关注公众号,一起交流,微 ...

  4. 没有wget Loading mirror speeds from cached hostfile

    问题描述 新装的系统,没有一些常用命令的rpm包.使用ifconfig,报错 Loading mirror speeds from cached hostfile解决 网上解决方案是换数据下载源,但是 ...

  5. 一周一个中间件-hbase

    前言 hbase是大数据的生态的一部分,是高可靠性.高性能.列存储.可伸缩.实时读写的数据库系统.介于nosql和RDBMS之间.主要存储非结构化和半结构化的松散数据. 海量数据存储 快速随机访问 大 ...

  6. Event Loop - 事件队列

    Event Loop 定义: event - 事件 loop - 循环,既然叫事件循环,那么循环的点在哪? 循环的是一个又一个的任务队列,这些任务队列由宏任务和微任务构成 两条原则 一次处理一个任务 ...

  7. Spring Boot 集成 MQTT

    本文代码有些许问题,处理方案见:解决 spring-integration-mqtt 频繁报 Lost connection 错误 一.添加配置 spring: mqtt: client: usern ...

  8. 排序--HeapSort 堆排序

    堆 排 序 堆排序.就是通过堆结构来排序.可以看之前写的http://www.cnblogs.com/robsann/p/7521812.html .关于堆的结构 堆排序先要使结构堆有序.所以要先使所 ...

  9. 第三十章、containers容器类部件QMdiArea多文档界面部件功能介绍及开发应用

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.引言 老猿在前期学习PyQt相关知识时,对每个组件的属性及方法都研 ...

  10. RedHat操作指令第3篇

    系统信息 arch 显示机器的处理器架构 uname -m 显示机器的处理器架构 uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI) ...