“Invokedynamic工厂创建单例的lambda吗?”

huangapple go评论106阅读模式
英文:

Invokedynamic factory creates lambdas as a singleton?

问题

例如,我有两个 lambda 表达式:

  1. Runnable exec1 = () -> {
  2. System.out.print("Hi from lambda");
  3. };
  4. Runnable exec2 = () -> {
  5. System.out.print("Hi from lambda");
  6. };

Invokedynamic 操作符将使用特殊工厂创建它:

  1. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

但是我在字节码读取方面遇到了一些问题。这是否意味着在这种情况下该工厂会缓存 lambda 创建(exec2 将重用实例)?

  1. // access flags 0x9
  2. public static main([Ljava/lang/String;)V
  3. L0
  4. LINENUMBER 6 L0
  5. INVOKEDYNAMIC run()Ljava/lang/Runnable; [
  6. // handle kind 0x6 : INVOKESTATIC
  7. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  8. // arguments:
  9. ()V,
  10. // handle kind 0x6 : INVOKESTATIC
  11. test/Main.lambda$main$0()V,
  12. ()V
  13. ]
  14. ASTORE 1
  15. L1
  16. LINENUMBER 10 L1
  17. INVOKEDYNAMIC run()Ljava/lang/Runnable; [
  18. // handle kind 0x6 : INVOKESTATIC
  19. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  20. // arguments:
  21. ()V,
  22. // handle kind 0x6 : INVOKESTATIC
  23. test/Main.lambda$main$1()V,
  24. ()V
  25. ]
  26. ASTORE 2
  27. L2
  28. LINENUMBER 13 L2
  29. RETURN
  30. L3
  31. LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
  32. LOCALVARIABLE exec Ljava/lang/Runnable; L1 L3 1
  33. LOCALVARIABLE exec2 Ljava/lang/Runnable; L2 L3 2
  34. MAXSTACK = 1
  35. MAXLOCALS = 3
  36. // access flags 0x100A
  37. private static synthetic lambda$main$1()V
  38. L0
  39. LINENUMBER 11 L0
  40. GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
  41. LDC "Hi from lambda"
  42. INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
  43. L1
  44. LINENUMBER 12 L1
  45. RETURN
  46. MAXSTACK = 2
  47. MAXLOCALS = 0
  48. // access flags 0x100A
  49. private static synthetic lambda$main$0()V
  50. L0
  51. LINENUMBER 7 L0
  52. GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
  53. LDC "Hi from lambda"
  54. INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
  55. L1
  56. LINENUMBER 8 L1
  57. RETURN
  58. MAXSTACK = 2
  59. MAXLOCALS = 0
  60. }
英文:

For example, I have two lambdas:

  1. Runnable exec1 = () -> {
  2. System.out.print("Hi from lambda");
  3. };
  4. Runnable exec2 = () -> {
  5. System.out.print("Hi from lambda");
  6. };

Invokedynamic operator will create it with special factory

  1. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

But I have some problems with bytecode reading. Does it mean, that in this case this factory will cache lambda creation (and exec2 will reuse instance)?

  1. // access flags 0x9
  2. public static main([Ljava/lang/String;)V
  3. L0
  4. LINENUMBER 6 L0
  5. INVOKEDYNAMIC run()Ljava/lang/Runnable; [
  6. // handle kind 0x6 : INVOKESTATIC
  7. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  8. // arguments:
  9. ()V,
  10. // handle kind 0x6 : INVOKESTATIC
  11. test/Main.lambda$main$0()V,
  12. ()V
  13. ]
  14. ASTORE 1
  15. L1
  16. LINENUMBER 10 L1
  17. INVOKEDYNAMIC run()Ljava/lang/Runnable; [
  18. // handle kind 0x6 : INVOKESTATIC
  19. java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  20. // arguments:
  21. ()V,
  22. // handle kind 0x6 : INVOKESTATIC
  23. test/Main.lambda$main$1()V,
  24. ()V
  25. ]
  26. ASTORE 2
  27. L2
  28. LINENUMBER 13 L2
  29. RETURN
  30. L3
  31. LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
  32. LOCALVARIABLE exec Ljava/lang/Runnable; L1 L3 1
  33. LOCALVARIABLE exec2 Ljava/lang/Runnable; L2 L3 2
  34. MAXSTACK = 1
  35. MAXLOCALS = 3
  36. // access flags 0x100A
  37. private static synthetic lambda$main$1()V
  38. L0
  39. LINENUMBER 11 L0
  40. GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
  41. LDC "Hi from lambda"
  42. INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
  43. L1
  44. LINENUMBER 12 L1
  45. RETURN
  46. MAXSTACK = 2
  47. MAXLOCALS = 0
  48. // access flags 0x100A
  49. private static synthetic lambda$main$0()V
  50. L0
  51. LINENUMBER 7 L0
  52. GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
  53. LDC "Hi from lambda"
  54. INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
  55. L1
  56. LINENUMBER 8 L1
  57. RETURN
  58. MAXSTACK = 2
  59. MAXLOCALS = 0
  60. }

答案1

得分: 2

你需要首先理解什么是 调用点(call-site),在我看来,要能够理解缓存(caching)发生的位置。exec1exec2 都将创建 Runnable 接口的两个单独实例;它们都将被缓存在调用点上。也许这个小片段会有所帮助:

  1. public static void main(String[] args) {
  2. useStatelessLambda1();
  3. useStatelessLambda1();
  4. useStatelessLambda2();
  5. useStatelessLambda2();
  6. }
  7. static void useStatelessLambda1() {
  8. Runnable exec1 = () -> {
  9. System.out.print("Hi from lambda");
  10. };
  11. System.out.print(exec1.hashCode() + " ");
  12. exec1.run();
  13. System.out.println("\n");
  14. }
  15. static void useStatelessLambda2() {
  16. Runnable exec2 = () -> {
  17. System.out.print("Hi from lambda");
  18. };
  19. System.out.print(exec2.hashCode() + " ");
  20. exec2.run();
  21. System.out.println("\n");
  22. }

运行这段代码会显示:

  1. 1878246837 Hi from lambda
  2. 1878246837 Hi from lambda
  3. 1995265320 Hi from lambda
  4. 1995265320 Hi from lambda
  5. 分离的实例,但都缓存于调用点。
  6. 无论如何,查看字节码不会告诉你关于这个的任何信息。你可以查看 `invokedynamic` 将使用的引导方法:`LambdaMetafactory::metafactory`,并理解它的作用。
英文:

You need to understand what a call-site is, first, imo; to be able to understand where caching happens. Both exec1 and exec2 will create two separate instances of a Runnable interface; both will be cached on the call-site. May be this little snippet will help:

  1. public static void main(String[] args) {
  2. useStatelessLambda1();
  3. useStatelessLambda1();
  4. useStatelessLambda2();
  5. useStatelessLambda2();
  6. }
  7. static void useStatelessLambda1() {
  8. Runnable exec1 = () -> {
  9. System.out.print("Hi from lambda");
  10. };
  11. System.out.print(exec1.hashCode() + " ");
  12. exec1.run();
  13. System.out.println("\n");
  14. }
  15. static void useStatelessLambda2() {
  16. Runnable exec2 = () -> {
  17. System.out.print("Hi from lambda");
  18. };
  19. System.out.print(exec2.hashCode() + " ");
  20. exec2.run();
  21. System.out.println("\n");
  22. }

Running this reveals:

  1. 1878246837 Hi from lambda
  2. 1878246837 Hi from lambda
  3. 1995265320 Hi from lambda
  4. 1995265320 Hi from lambda

separate instances, but both cached on the call-site.

Either way, looking at the byte-code will not tell you anything about that. what you could look at is the bootstrap method that invokedynamic will use : LambdaMetafactory::metafactory and understand what that will do.

huangapple
  • 本文由 发表于 2020年7月26日 03:21:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/63092578.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定