SourceCode Java SpringBoot Linux Spring Code

Graalvm打包报错:UnsupportedCharsetException解决方案

Posted on 2022-04-20,5 min read
  • 在使用Graalvm打包一个项目,在运行的时候出现了如下的报错:

    Exception in thread "main" java.lang.ExceptionInInitializerError
    	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:290)
    	at java.lang.Class.ensureInitialized(DynamicHub.java:467)
    	at okhttp3.OkHttpClient.<clinit>(OkHttpClient.java:127)
    	at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
    	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
    	at java.lang.Class.ensureInitialized(DynamicHub.java:467)
    	at okhttp3.OkHttpClient$Builder.<init>(OkHttpClient.java:475)
    	at io.fabric8.kubernetes.client.utils.HttpClientUtils.createHttpClient(HttpClientUtils.java:73)
    	at io.fabric8.kubernetes.client.utils.HttpClientUtils.createHttpClient(HttpClientUtils.java:64)
    	at io.fabric8.kubernetes.client.BaseClient.<init>(BaseClient.java:51)
    	at io.fabric8.kubernetes.client.BaseClient.<init>(BaseClient.java:43)
    	at io.fabric8.kubernetes.client.DefaultKubernetesClient.<init>(DefaultKubernetesClient.java:89)
    	at ch.frankel.kubernetes.extend.Sidecar.main(Sidecar.java:13)
    Caused by: java.nio.charset.UnsupportedCharsetException: UTF-32BE
    	at java.nio.charset.Charset.forName(Charset.java:531)
    	at okhttp3.internal.Util.<clinit>(Util.java:75)
    	at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:350)
    	at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:270)
    
  • 打包命令为:native-image -jar simple-rpc-server.jar -H:+ReportExceptionStackTraces

  • 从堆栈可以看出,有问题的代码位于:

    public final class Util {
    
      private static final ByteString UTF_8_BOM = ByteString.decodeHex("efbbbf");
      private static final ByteString UTF_16_BE_BOM = ByteString.decodeHex("feff");
      private static final ByteString UTF_16_LE_BOM = ByteString.decodeHex("fffe");
      private static final ByteString UTF_32_BE_BOM = ByteString.decodeHex("0000ffff");
      private static final ByteString UTF_32_LE_BOM = ByteString.decodeHex("ffff0000");
    
      public static final Charset UTF_8 = Charset.forName("UTF-8");
      public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
      private static final Charset UTF_16_BE = Charset.forName("UTF-16BE");
      private static final Charset UTF_16_LE = Charset.forName("UTF-16LE");
      private static final Charset UTF_32_BE = Charset.forName("UTF-32BE");
      private static final Charset UTF_32_LE = Charset.forName("UTF-32LE");
    
      public static Charset bomAwareCharset(BufferedSource source, Charset charset) throws IOException {
        if (source.rangeEquals(0, UTF_8_BOM)) {
          source.skip(UTF_8_BOM.size());
          return UTF_8;
        }
        if (source.rangeEquals(0, UTF_16_BE_BOM)) {
          source.skip(UTF_16_BE_BOM.size());
          return UTF_16_BE;
        }
        if (source.rangeEquals(0, UTF_16_LE_BOM)) {
          source.skip(UTF_16_LE_BOM.size());
          return UTF_16_LE;
        }
        if (source.rangeEquals(0, UTF_32_BE_BOM)) {
          source.skip(UTF_32_BE_BOM.size());
          return UTF_32_BE;
        }
        if (source.rangeEquals(0, UTF_32_LE_BOM)) {
          source.skip(UTF_32_LE_BOM.size());
          return UTF_32_LE;
        }
        return charset;
      }
    }
    
  • 打包后的可执行文件中,在加载类的时候,每个对应的Charset运行自己的Charset.forName(),在操作系统中,没有对应的字符集。于是报错了。

  • 此问题在Graalvm的github上也有提过Issues

解决方案

编译时添加所有字符集

  • 在打包命令中添加参数-H:+AddAllCharsets
  • 缺点:会导致打包后的文件更大一点。

修复库

  • 下载库的代码(如OkHttp)然后干掉这部分问题代码,自己打包构建,Maven引入。

使用substratevm

  • 注:关于substratevm这块没有找到官方文档,以下大部分内容是直接谷歌来的。

  • 参考项目

  • 项目中添加依赖:

    <dependency>
      <groupId>com.oracle.substratevm</groupId>
      <artifactId>svm</artifactId>
      <version>19.2.1</version>
      <scope>provided</scope>
    </dependency>
    
  • 编写替换的代码:

    @TargetClass(Util.class)                                  
    public final class okhttp3_internal_Util {
    
        @Alias                                                
        private static ByteString UTF_8_BOM;
        @Alias                                                
        private static ByteString UTF_16_BE_BOM;
        @Alias                                                
        private static ByteString UTF_16_LE_BOM;
        @Alias                                                
        public static Charset UTF_8;
        @Alias                                                
        private static Charset UTF_16_BE;
        @Alias                                                
        private static Charset UTF_16_LE;
    
        @Substitute                                           
        public static Charset bomAwareCharset(BufferedSource source, Charset charset) throws IOException {
            if (source.rangeEquals(0, UTF_8_BOM)) {
                source.skip(UTF_8_BOM.size());
                return UTF_8;
            }
            if (source.rangeEquals(0, UTF_16_BE_BOM)) {
                source.skip(UTF_16_BE_BOM.size());
                return UTF_16_BE;
            }
            if (source.rangeEquals(0, UTF_16_LE_BOM)) {
                source.skip(UTF_16_LE_BOM.size());
                return UTF_16_LE;
            }
            return charset;
        }
    }
    
    • 其中:
      • @TargetClass:被这个注解标注的类会在NativeImage编译期间替换方法执行的逻辑而无需改变原有代码
      • @Substitute:被这个注解标注的方法会被替换。
      • @Alias:被这个注解标注的变量会被替换。
  • 添加配置文件:

    [
      {
        "annotatedClass": "用于替换的类的全限定类名",
        "originalClass": "需要被替换的类的全限定类名",
        "methods": [
          {
            "annotatedName": "方法名",
            "substitute": true
          }
        ]
      }
    ]
    
  • 然后native-image时指定该文件:

    • -H:SubstitutionFiles=xxx.json

下一篇: Java内存占用超出Xmx原因→

loading...