2015-12-08 11 views
11

Java 8 scheint Klassen zu generieren, die Lambda-Ausdrücke darstellen. Zum Beispiel kann der Code:Transformieren von Lambdas in Java 8

Runnable r = app::doStuff; 

Manifests, grob, wie:

// $FF: synthetic class 
    final class App$$Lambda$1 implements Runnable { 
    private final App arg$1; 

    private App$$Lambda$1(App var1) { 
     this.arg$1 = var1; 
    } 

    private static Runnable get$Lambda(App var0) { 
     return new App$$Lambda$1(var0); 
    } 

    public void run() { 
     this.arg$1.doStuff(); 
    } 
    } 

Wie ich das verstehen, wird der Code zur Laufzeit generiert. Angenommen, man wollte Code in die run-Methode der obigen Klasse einfügen. Experimente ergeben bisher eine Mischung aus NoClassDefFound und VerifyError:

$ java -version 
java version "1.8.0_51" 
Java(TM) SE Runtime Environment (build 1.8.0_51-b16) 
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode) 

Dies ist auch vor dem Drücken einer beliebigen neuen Bytecode in die Klasse:

java.lang.NoClassDefFoundError: App$$Lambda$2 
    at App$$Lambda$2/1329552164.run(Unknown Source) 
    at App.main(App.java:9) 
Caused by: java.lang.ClassNotFoundException: App$$Lambda$2 
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424) 
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) 
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357) 
    ... 2 more 

Das gegen läuft.

Wird das erwartet? Riecht wie ein JDK-Fehler, aber ich bin glücklich, dass ich mich geirrt habe!

Hier ist ein Github repo illustrating the behavior

+0

Das gleiche Verhalten gilt für die neueste Version des JDK, '1.8.0_65' – thetwan

+1

Der Prüffehler zeigt an, dass Sie einen fehlerhaften Bytecode erstellt haben. Hast du versucht, den Code zu debuggen? In welcher Aktion tritt der Fehler auf? –

+0

Dies ist bei einem Retransform-Aufruf. Der ClassTransformation-Callback wird in diesem Fall nicht einmal aufgerufen! Um genau zu sein, geschieht dies auf 'Instrumentation # retransformClasses (...)' – thetwan

Antwort

10

Für mich ist dieses wie ein Fehler in der JVM scheint. Der Systemklassenlader versucht, die transformierte Klasse anhand ihres Namens zu lokalisieren. Allerdings Lambda-Ausdrücke werden über anonyme Klasse Laden geladen, in dem die folgende Bedingung:

clazz.getClassLoader() 
    .loadClass(clazz.getName().substring(0, clazz.getName().indexOf('/'))) 

eine ClassNotFoundException was in der NoClassDefError ergibt. Die Klasse wird nicht als echte Klasse betrachtet und solche anonymen Klassen werden beispielsweise nicht außerhalb einer Retransform an eine ClassFileTransformer übergeben.

Alles in allem fühlt sich die Instrumentierungs-API beim Umgang mit anonymen Klassen ein wenig buggy an. In ähnlicher Weise werden LambdaForm s an ClassFileTransformer s übergeben, aber mit allen Argumenten, aber die classFileBuffer auf null gesetzt, was den Vertrag der Transformatorklasse bricht.

Für Ihr Beispiel scheint das Problem zu sein, dass Sie null zurückgeben; Das Problem verschwindet, wenn die classFileBuffer zurückgegeben wird, was ein No-Op ist. Dies ist jedoch nicht das, was die ClassFileTransformer schon sagt, wo null Rückkehr ist der empfohlene Weg, dies zu tun:

ein wohlgeformte Klasse Dateipuffer (das Ergebnis der Transformation) oder null, wenn keine Transformation durchgeführt.

Für mich scheint dies ein Fehler in HotSpot. Sie sollten dieses Problem dem OpenJDK melden.

Alles in allem ist es perfekt möglich, anonym geladene Klassen zu instrumentieren, wie ich in meiner Code-Manipulationsbibliothek Byte Buddy demonstriere. Es erfordert einige unglückliche Verbesserungen im Vergleich zu normalen Instrumentierung aber die Laufzeit unterstützt es. Hier ist ein Beispiel, das erfolgreich läuft als Unit-Test in der Bibliothek:

Callable<String> lambda =() -> "foo"; 

Instrumentation instrumentation = ByteBuddyAgent.install(); 
ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.of(instrumentation) 
    .preregistered(lambda.getClass()); 
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.of(instrumentation, 
    lambda.getClass()); 

assertThat(lambda.call(), is("foo")); 

new ByteBuddy() 
    .redefine(lambda.getClass(), classFileLocator) 
    .method(named("call")) 
    .intercept(FixedValue.value("bar")) 
    .make() 
    .load(lambda.getClass().getClassLoader(), classReloadingStrategy); 

assertThat(lambda.call(), is("bar")); 
+0

Danke Rafael. Ich erreiche die Leute von Oracle und OpenJDK. Aktualisiert den Beitrag, sobald ich etwas Konkretes habe. – thetwan

4

Bug Vorlage von Leuten bei Oracle akzeptiert wurde, und wird als JDK-8145964 verfolgt.Dies ist nicht gerade eine Lösung, sondern scheint ein echtes Laufzeitproblem zu sein.