lambda expressions cause arthas to be unable to redefine

As a person from PHP to Java, I found that alibaba's arthas is very easy to use. Through arthas redefine command , like PHP, the program behavior can be changed without re publishing (provided that the class structure and method signature are not changed).

However, if we use too much, we often find that we change a few lines of code, and sometimes even add a line of log, so we can't redefine. Tips

redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

It prompts us to add new methods. Let's see if they are new. View the defined methods through javap:

Old class:

New class:

After comparison, it is found that for the new class, that is, the locally compiled class, the method name corresponding to lambda is lambda$getAllCity lambda $getallcity $0..

In the old class, that is, the running class, the method name of the same lambda is lambda$getAllCity1. The last number of the method name has changed.

In this way, it should be the jdk version. The processing of different jdk versions may be inconsistent with that of lamdba.

By comparison, the jdk version compiled online is 1.8.0_66-b17, local 1.8.0_222-b10.

First, for the convenience of debugging, write a minimum recurrence case:

// Compile.java
// Compile LamdbaTest1.java and LamdbaTest2.java
import javax.tools.*;
import java.io.File;

public class Compile {
    public static void main(String[] args) {
        String path1 = "/path/to/LamdbaTest1.java";
        String path2 = "/path/to/LamdbaTest2.java";

        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector diagnostics = new DiagnosticCollector();
        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(diagnostics, null, null);

        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
                new File(path1),
                new File(path2)
        );
        JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, diagnostics, null, null,
                compilationUnits);
        boolean success = task.call();
        System.out.println(success);
    }
}

//LamdbaTest1.java
public class LamdbaTest1 {

    private void test(Runnable runnable) {
        runnable.run();
    }

    private void main() throws Throwable {
        test(() -> {
            System.out.println(11);
        });
    }
}

//LamdbaTest2.java
public class LamdbaTest2 {

    private void test(Runnable runnable) {
        runnable.run();
    }

    private void main() throws Throwable {
        test(() -> {
            System.out.println(22);
        });
    }
}

Use 1.8.0_ After running 222-b10 (new version jdk), it is found that the lambda method in LamdbaTest2 is:

private static void lambda$main$0();

For version 1.8.0_ After 66-b17 (old version jdk), the lambda method becomes:

private static void lambda$main$1();

If you try to compile several files at the same time, you can find that for the old version of javac, the number at the end is incremented globally, while the new version is counted again for each class.

After confirming the problem, the next step is to constantly interrupt and retry. Later, it was found that different versions of javac logic were indeed different.

First, check the jdk source code to see that the method names of lambda are:

For lambda $< methodname > $< lambdacount > code, see LambdaToMethod.java

The difference is that when dealing with a new class in the new version of javac, The previous lambdaCount will be saved and restored later , in the current class, start counting again directly from 0:

and The old version did not have this logic , directly use globally incremented counters:

This shows that the old version of the compiler is indeed lambda global number.

So, the question is, from which version did this behavior change?

After comparison, it is found that this change was jdk8u74-b02 introduced. The corresponding bug is https://bugs.openjdk.java.net/browse/JDK-8067422

Basically, the lambda in each class is numbered separately to ensure that the compilation order will not affect the method name of lambda.

Therefore, the solution is very simple. Just upgrade the jdk version of the compilation environment.

Coincidentally, I added a new jdk version 1.8.0 to the O & M two days ago in order to better adapt to the docker operating environment (generally speaking, to obtain the docker cpu quota in the container rather than the number of CPUs of the physical machine)_ 231-b11, so you only need to directly switch the jdk version of the compilation environment to 8u231!

This article was originally published in: https://robberphex.com/lambda-causes-arthas-cant-redefine/

Posted by AE117 on Mon, 22 Nov 2021 21:20:16 -0800