• 志远
    2018-08-06
    老师您好,提个建议,您讲课过程中经常提到一些概念名词,您讲课总是预设了一个前提,就是假设我们已经知道那个概念,然而并不清楚。比如本文中被不断提到的内联,什么是内联呢?

    作者回复: 谢谢建议!方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用这么个过程。

    
     50
  • xiaguangme
    2018-08-06
    开发人员日常接触到的 Java 集成开发环境(IDE)便运用了这一功能:每当我们敲入点号时,IDE 便会根据点号前的内容,动态展示可以访问的字段或者方法。//这个应该是不完全正确的,大部分应该是靠语法树来实现的。

    作者回复: 谢谢指出!

    
     29
  • godtrue
    2018-08-07
    小结
    1:反射机制是Java语言的一个非常重要的特性,通过这个特性,我们能够动态的监控、调用、修改类的行为,许多的框架实现就用到了Java语言反射的机制

    2:使用反射挺好的,但它也是不完美的,复杂的操作往往更耗时间和精力,使用反射也是一样,性能低下是她所被人诟病的一个地方,那为什么方法的反射如此耗费性能呐?它的性能耗在那里呢?方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联。
    
     13
  • ext4
    2018-08-06
    雨迪您好,我有两个问题:

    一是我自己的测试结果和文章中有些出入。在我自己的mac+jdk10环境中,v3版本的代码和v2版本性能是差不多的,多次测试看v3还略好一些。从v2的GC log来看for循环的每一亿次iteration中间都会有GC发生,似乎说明这里的escape analysis并没有做到allocation on stack。您能想到这是什么原因么?另有个小建议就是文章中提到测试结果时,注明一下您的环境。

    另一个问题是在您v5版本的代码中,您故意用method1和method2两个对象霸占了2个ProfileType的位子,导致被测的反射操作性能很低。这是因为此处invoke方法的inline是压根儿就没有做呢?还是因为inline是依据target1或者target2来做的,而实际运行时发现类型不一致又触发了deoptimization呢?

    望解答,谢谢~
    展开

    作者回复: 多谢建议,我也是mac+jdk10。我这边裸跑v2是2.7x(因为每次要新建整数对象所以有GC),加大整数缓存后跑v2是1.8x(无GC)。你是否忘了加大整数缓存?

    第二个问题,研究得很深!Method.invoke一直会被内联,但是它里面的MethodAccesor.invoke则不一定。

    实际上,在C2编译之前循环代码已经运行过非常多次,也就是说MethodAccesor.invoke已经看到多次调用至target()的动态实现。在profile里会显示为有target1,有target2,但是profile不完整,即还有一大部分的调用者类型没有记录。

    这时候C2会选择不inline这个MethodAccesor.invoke调用,直接做虚调用。

    
     12
  • 李杰
    2018-08-06
    当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码...
    ———可以认为第16次反射调用时的耗时是最长的吗?

    作者回复: 动态生成发生在第15次(从0开始数的话),所以第15次比较耗时。

    
     10
  • 小明
    2018-08-17
    可以看看这篇博客 有详细的生成动态调用的解释
    http://rednaxelafx.iteye.com/blog/548536
    
     9
  • Kisho
    2018-08-06
    郑老师,你好,
           “动态实现无需经过Java到C++再到Java的切换”,这句话没太明白,能在解释下么?

    作者回复: 在v0版本中我贴了一段stacktrace,你可以看到中间有个native method,这就是C++代码,也就是它先调用至这个C++代码,在C++代码里面再调用至Java代码。

    
     6
  • 小鳄鱼
    2018-11-12
    不是同个对象,但equal。老师说了,返回的是目标方法的一份拷贝
    
     4
  • Stephen
    2018-11-03
    老师,有三个知识点不太明白:内联、逃逸分析以及inflation机制
     1
     4
  • Yoph
    2018-11-13
    MethodAccessor实例创建在ReflectionFactory中,如下代码:
    public class ReflectionFactory {
        private static boolean noInflation = false;
        private static int inflationThreshold = 15;
       
        public MethodAccessor newMethodAccessor(Method method) {
            checkInitted();
            if (noInflation) {
                return new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            } else {
                NativeMethodAccessorImpl acc =
                    new NativeMethodAccessorImpl(method);
                DelegatingMethodAccessorImpl res =
                    new DelegatingMethodAccessorImpl(acc);
                acc.setParent(res);
                return res;
            }
        }
    }

    实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。
    展开
    
     3
  • once
    2018-09-07
    请问老师 是不是本地方法的性能一般都不是很好呢

    作者回复: 需要经过JNI,所以性能很不好。

    不过即时编译器可能会将某些指定的本地方法调用给替换掉。这些特定的本地方法叫intrinsics,下周一会讲。

    
     3
  • Scott
    2018-08-15
    有两个问题:
    1.v3版本中,确定不逃逸的数组可以优化访问,这个是怎么做的?
    2.v5版本中,为啥逃逸分析会失效,明明都封闭在循环里的?

    作者回复: 1. 读数组被替换为之前写入数组的值。后面数组就只有写没有读了,因此可以优化掉。
    2. 只要没有完全内联,就会将看似不逃逸的对象通过参数传递出去。即时编译器不知道所调用的方法对该对象有没有副作用,所以会将其判定为逃逸。
    (如果你问的是不能分配到栈上,那我只能回答Java虚拟机从设计上不支持栈分配。它要不是堆分配,要不是虚拟分配+标量替换)

    
     3
  • 志远
    2018-08-06
    文章中“一亿次直接调用耗费的时间大约在 120ms。这和不调用的时间是一致的。”这句话是不是病句啊?不调用指的是什么?指的是直接调用吗?

    作者回复: 多谢指出!不调用就是字面意思,不做任何调用,也就是除了每一亿次调用的打印语句之外,循环就不包含其它东西了。

    
     3
  • godtrue
    2018-08-06
    老师请教个问题,如果手动修改某个Java字节码文件,如果JVM不重新加载此文件,有什么方式能让JVM识别并执行修改的内容呢?
    如果一定需要JVM加载后才能识别并执行,有什么好的手动触发的方法呢?

    作者回复: 你可以搜一下class redefinition的相关资料。我以前用cagent做过,Javaagent应该也可以。

    
     3
  • Stephen
    2018-11-03
    老师,有三个知识点不太明白,分别是:内联、逃逸分析以及inflation机制

    作者回复: 内联和逃逸分析后面有两篇会专门介绍,反射的inflation机制是当反射被频繁调用时,动态生成一个类来做直接调用的机制,可以加速反射调用

    
     2
  • 星文友
    2018-11-02
    给大家讲个笑话:
    我负责的项目中有大量动态生成的类,这些类实例的调用原本都是通过反射去完成,后来我觉得反射效率低,就为每个动态类的每个方法在动态生成一个代理类,这个代理类就是进行类型强转然后直接调用。后来在压测环境进行测试,发现并无卵用,早点开到这篇文章我就不用做这么多无用功了。特么的JVM已经有这个功能了啊

    作者回复: 这说明你和JVM架构师想一块去了 ;)

    
     2
  • life is short, e...
    2018-10-06
    总结一下
    反射有两种实现方式:

    本地方法调用(就是字节码中已经定义好的方法)

    动态生成字节码


    两者有什么区别?

    动态生成字节码(以下简称动态实现),生成字节码的过程很慢(类似于准备工作),但是执行效率高。

    本地方法调用,不用生成字节码,直接调用本地方法。所以准备工作几乎没有,很快。但是执行效率就差很多。


    JVM如何做决定选择哪种实现方式?

    通过反射执行的次数来决定,默认值是15。15次之前直接本地调用,之后动态实现。


    JVM为啥分两种实现方式?

    本地实现的调用流程复杂。而在执行多次的情况下,复杂意味着性能损耗,所以有一种适合多次执行的解决方案,就是动态生成字节码。

    展开
    
     2
  • vick
    2018-09-08
    请教一个问题,本地实现可以用java来替代c++的实现方式吗?这样就可以避过C++的额外开销?

    作者回复: 之所以叫本地实现,就是因为它用的C++代码。如果用Java来实现,就不会这么叫啦 :)

    JVM有用Java来替代的实现方式,也就是文中介绍的动态实现。它是根据反射调用的目标方法来动态生成字节码的。

    
     2
  • godtrue
    2018-08-06
    JVM加载了一个Java字节码文件,在不停止JVM的情况下能再次的加载同一个Java字节码文件吗?如果能是覆盖了原来的那个Java字节码文件还是怎么着了呢?
    在IDE中是可以直接修改Java源代码的,然后可以手动触发Java源代码的编译和重新加载,请问老师知道IDE是怎么实现的吗?
     1
     2
  • 巴西
    2019-11-28
    有点难啊
    
     1
我们在线,来聊聊吧