《字符串链接你用+仍是用StringBuilder》续

前言

前面的一篇文章《字符串链接你用+仍是用StringBuilder》,有朋友找我反馈了一些问题,其中一位朋友说JDK10下生成的字节码跟文章中并不同,这里继续看下是什么状况。java

问题描述

以下图,按照《字符串链接你用+仍是用StringBuilder》的代码在 javap 后发现它并无建立 StringBuilder 类和一些相应的操做,与文章的描述的并不符合,使用的JDK版本为JDK10。mysql

image

image

问题缘由

JDK9及之后的编译器已经改为用动态指令执行字节码了,具体的调用实如今 java.lang.invoke.StringConcatFactory 类中,也就是说指令没有生成到class文件中保存起来,而是运行时生成。 具体实现以下,有六种策略,前五种仍是用StringBuilder实现。 sql

JDK9先后

对于下面简单的例子,JDK8及以前和JDK9及以后编译的字节码有什么差异数组

public class TestString2 {
	public static void main(String[] args) {
		String s = "www";
		for (int i = 0; i < 10; i++)
			s += i;
	}
}
复制代码

JDK8及以前,bash

public class com.seaboat.string.TestString2 {
  public com.seaboat.string.TestString2();
    Code:
       0: aload_0
       1: invokespecial #8 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #16 // String www
       2: astore_1
       3: iconst_0
       4: istore_2
       5: goto          30
       8: new           #18 // class java/lang/StringBuilder
      11: dup
      12: aload_1
      13: invokestatic  #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
      16: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      19: iload_2
      20: invokevirtual #29 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      23: invokevirtual #33 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      26: astore_1
      27: iinc          2, 1
      30: iload_2
      31: bipush        10
      33: if_icmplt     8
      36: return
}
复制代码

JDK9及以后,网络

public class com.seaboat.string.TestString2 {
  public com.seaboat.string.TestString2();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2 // String www
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: bipush        10
       8: if_icmpge     25
      11: aload_1
      12: iload_2
      13: invokedynamic #3, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
      18: astore_1
      19: iinc          2, 1
      22: goto          5
      25: return
}
复制代码

InvokeDynamic

能够看到JDK9以后生成的字节码是比较简洁的,只有一个 InvokeDynamic 指令,编译器会给该类字节码增长 invokedynamic 指令相关内容,包括方法句柄、引导方法、调用点、方法类型等等。它会调用 java.lang.invoke.StringConcatFactory 类中的makeConcatWithConstants方法,它有六种策略来处理字符串。以下代码所示,有默认的策略,也能够经过java.lang.invoke.stringConcat启动参数来修改策略。并发

private static final Strategy DEFAULT_STRATEGY = Strategy.MH_INLINE_SIZED_EXACT;

static {
        STRATEGY = DEFAULT_STRATEGY;

        Properties props = GetPropertyAction.privilegedGetProperties();
        final String strategy =
                props.getProperty("java.lang.invoke.stringConcat");
        CACHE_ENABLE = Boolean.parseBoolean(
                props.getProperty("java.lang.invoke.stringConcat.cache"));
        DEBUG = Boolean.parseBoolean(
                props.getProperty("java.lang.invoke.stringConcat.debug"));
        final String dumpPath =
                props.getProperty("java.lang.invoke.stringConcat.dumpClasses");

        STRATEGY = (strategy == null) ? DEFAULT_STRATEGY : Strategy.valueOf(strategy);
        CACHE = CACHE_ENABLE ? new ConcurrentHashMap<>() : null;
        DUMPER = (dumpPath == null) ? null : ProxyClassesDumper.getInstance(dumpPath);
    }
复制代码
private enum Strategy {
        BC_SB,
        BC_SB_SIZED,
        BC_SB_SIZED_EXACT,
        MH_SB_SIZED,
        MH_SB_SIZED_EXACT,
        MH_INLINE_SIZED_EXACT
    }
复制代码

有六种策略,前五种仍是用StringBuilder实现,而默认的策略MH_INLINE_SIZED_EXACT,这种策略下是直接使用字节数组来操做,而且字节数组长度预先计算好,能够减小字符串复制操做。实现的核心是经过 MethodHandle 来实现 runtime,具体实现逻辑在MethodHandleInlineCopyStrategy.generate方法中。app

private static MethodHandle generate(Lookup lookup, String className, MethodType mt, Recipe recipe) throws StringConcatException {
        try {
            switch (STRATEGY) {
                case BC_SB:
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.DEFAULT);
                case BC_SB_SIZED:
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED);
                case BC_SB_SIZED_EXACT:
                    return BytecodeStringBuilderStrategy.generate(lookup, className, mt, recipe, Mode.SIZED_EXACT);
                case MH_SB_SIZED:
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED);
                case MH_SB_SIZED_EXACT:
                    return MethodHandleStringBuilderStrategy.generate(mt, recipe, Mode.SIZED_EXACT);
                case MH_INLINE_SIZED_EXACT:
                    return MethodHandleInlineCopyStrategy.generate(mt, recipe);
                default:
                    throw new StringConcatException("Concatenation strategy " + STRATEGY + " is not implemented");
            }
        } catch (Error | StringConcatException e) {
            throw e;
        } catch (Throwable t) {
            throw new StringConcatException("Generator failed", t);
        }
    }
复制代码

-------------推荐阅读------------机器学习

个人开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)分布式

个人2017文章汇总——机器学习篇

个人2017文章汇总——Java及中间件

个人2017文章汇总——深度学习篇

个人2017文章汇总——JDK源码篇

个人2017文章汇总——天然语言处理篇

个人2017文章汇总——Java并发篇


跟我交流,向我提问:

公众号的菜单已分为“读书总结”、“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

为何写《Tomcat内核设计剖析》

欢迎关注:

相关文章
相关标签/搜索