java增量构建关于lambda表达式的问题

项目需要做java增量构建和部署。基本方案很简单。

构建:通过git diff拿到方法体变化的java类,绕过maven,直接使用同版本的javac编译出class文件。

部署:基于java-instrument技术,采用attach的方式对方法体变化的class进行retransform。

问题:  某些类在retransform的时候总是会报如下类似错误。有时是add a method, 有时是delete a method。我们都知道,“原生的jvm只支持方法体的修改,不支持增删方法,修改方法签名,修改类成员等”。在这里,我们对于源代码确实只修改了方法体,为什么会报出这样的错误呢?

 1.上图中的异常是原生方法抛出来的,我们查看jvm中对应的位置,可以看到,实际上jvm是支持增删私有静态和私有final方法的,其余的方法则会抛出异常。

难道我们的类编译过程中在字节码层面添加了新的方法?

2. 使用javap -private对比两个class文件的方法

  果不其然。原来javac在编译期会对方法体中lambda表达式做抽取,成为类的私有静态或者私有方法,且方法名后缀中会带有编号,编号是按照本次javac所遇见的lambda来计数的。

当抽取成为非静态私有方法,由于全量编译和增量编译的lambda计数一般是不一致的,所以jvm认为是增减了方法则抛异常。而当抽取成为私有静态方法时,jvm是允许的,怎么都不会有问题。这就

是为什么有些类可以,有些类不行的原因。

那么,好奇的是,匿名表达式抽取为静态还是非静态取决于什么呢?可以直接到javac中去找答案:

We conclude that the lambda must keep somehow a copy of all the variables it uses in the enclosing method. The current implementation of Java may add all the variables (in the enclosing method or class) the lambda read or write to the list of parameters the lambda get. It may also add only the variables from the enclosing method to the list of parameters because the rest can be reached in other ways. The lambda it private static method or private instance method inside the enclosing class. The decision of making it static method or instance method is decided by (can be changed in the future): If the lambda does not use any variable of the enclosing class, it may become a static method. Else it may become an instance method.

For example:

复制代码
public class A {

public static void main(String[] args) {
Consumer<Integer> f2 = (Integer x) -> System.out.print(x);
}
}
After complining:

javac A.java
javap -p A
Output:

Compiled from "A.java"
public class A {
public A();
public static void main(java.lang.String[]);
private static void lambda$main$0(java.lang.Integer);
}
Another example:

public class A {

int y=9;
public void f1() {
Consumer<Integer> f2 = (Integer x) -> System.out.print(y);
}
}
After complining:

javac A.java
javap -p A
Output:

Compiled from "A.java"
public class A {
int y;
public A();
public void f1();
private void lambda$f1$0(java.lang.Integer);
}
复制代码

可见,其取决于lambda中是否使用了类的成员变量。

上述问题导致我们的增量部署范围缩小很多,类中包含的某种形式匿名表达式就不支持热部署了。这是个令人难以接受的问题,我们将在下一篇文章里探索一些解决方案。

 

猜你喜欢

转载自www.cnblogs.com/luliAngel/p/10360649.html
今日推荐