Java中的零垃圾大字符串反序列化,庞大的对象问题

Java中的零垃圾大字符串反序列化,庞大的对象问题,java,serialization,garbage-collection,java-8,g1gc,Java,Serialization,Garbage Collection,Java 8,G1gc,我正在寻找一种方法,在Java中从字节[]反序列化字符串,并尽可能少地产生垃圾。因为我正在创建自己的序列化程序和反序列化程序,所以我可以完全自由地在服务器端(即序列化数据时)和客户端(即反序列化数据时)实现任何解决方案 通过迭代字符串的字符(String.charAt(I)),并将每个char(16位值)转换为2x 8位值,我成功地序列化字符串,而不会产生任何垃圾开销。关于这一点有一场很好的辩论。另一种方法是使用反射直接访问字符串的底层char[],但这超出了问题的范围 然而,如果不创建两次ch

我正在寻找一种方法,在Java中从
字节[]
反序列化
字符串
,并尽可能少地产生垃圾。因为我正在创建自己的序列化程序和反序列化程序,所以我可以完全自由地在服务器端(即序列化数据时)和客户端(即反序列化数据时)实现任何解决方案

通过迭代
字符串的
字符(
String.charAt(I)
),并将每个
char
(16位值)转换为2x 8位值,我成功地序列化
字符串,而不会产生任何垃圾开销。关于这一点有一场很好的辩论。另一种方法是使用反射直接访问
字符串的
底层
char[]
,但这超出了问题的范围

然而,如果不创建两次
char[]
,我似乎不可能对
字节[]
进行反序列化,这看起来很奇怪

程序如下:

  • 创建
    char[]
  • 迭代字节[]
  • 并填写字符[]
  • 使用
    String(char[])
    constructor创建字符串
  • 由于Java的
    String
    不变性规则,构造函数复制char[],从而产生2倍的GC开销。我总是可以使用一些机制来规避这个问题(不安全的
    String
    allocation+Reflection来设置
    char[]
    实例),但我只是想问一下,除了打破
    String的
    不变性的每一个约定之外,是否还有其他后果

    当然,对此最明智的回应是“来吧,停止这样做,相信GC,原来的
    char[]
    将非常短暂,G1将立即摆脱它”,这实际上是有道理的,如果
    char[]
    小于G1区域大小的1/2
    。如果较大,char[]将直接分配为一个庞大的对象(即自动传播到G1区域之外)。这样的对象在G1中很难被有效地垃圾收集。这就是为什么每次分配都很重要

    关于如何解决这个问题有什么想法吗

    非常感谢

    这样的对象在G1中很难被有效地垃圾收集

    这可能不再是事实,但您必须为自己的应用程序评估它。JDK的缺陷,并引入了新的机制来收集庞大、短暂的对象。根据bug标志,您可能必须使用jdk版本运行≥8u40和≥8u60以获得各自的好处

    感兴趣的实验选项:

    -XX:+G1ReclaimDeadHumongousObjectsAtYoungGC
    
    追踪:

    -XX:+G1TraceReclaimDeadHumongousObjectsAtYoungGC
    

    有关这些功能的进一步建议和问题,我建议点击邮件列表。

    我找到了一个解决方案,如果您有一个非托管环境,它是无用的

    java.lang.String
    类有一个包私有构造函数
    String(char[]值,布尔共享)

    资料来源:

    /*
    * Package private constructor which shares value array for speed.
    * this constructor is always expected to be called with share==true.
    * a separate constructor is needed because we already have a public
    * String(char[]) constructor that makes a copy of the given char[].
    */
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
    
    这在Java中被广泛使用,例如在
    Integer.toString()
    Long.toString()
    String.concat(String)
    String.replace(char,char)
    String.valueOf(char)


    解决方案(或hack,不管你怎么称呼它)是将类移动到
    java.lang
    package并访问包私有构造函数。这对安全管理器来说不是个好兆头,但可以避免。

    找到了一个使用简单的“秘密”本机Java库的有效解决方案:

    String longString = StringUtils.repeat("bla", 1000000);
    char[] longArray = longString.toCharArray();
    String fastCopiedString = SharedSecrets.getJavaLangAccess().newStringUnsafe(longArray);
    

    你有没有考虑过不使用字符串,只是在绝对必要的时候序列化原始字节数据并在子部分上进行字符集转换?我有。我的想法是创建一个新类
    MutableString
    ,并在其上实现许多传统的垃圾重操作(例如,fastpath
    String
    split),然后有一个方法
    toString(from,to)
    ,它创建一个
    String
    类型的“视图”实例。我可以做到。但这需要完全重构我们的应用程序,并尽可能地使用
    MutableString
    s。这是一个好主意,但我想先探索替代方案。你知道所有这些东西都已经存在了吗?有
    CharBuffer
    StringBuilder
    ,它们都是一种可变的
    String
    (除非您创建了一个不可变的视图),有一些方法可以创建它们的轻量级子序列,它们都实现了
    CharSequence
    ,regex包所在的
    接口,它实际上实现了
    split
    操作。在查看源代码时,在
    String
    s、
    CharBuffer
    s和
    StringBuilder
    s之间进行转换时,字符内容似乎总是被复制的,HotSpot对它们进行了特殊的优化…谢谢,我将看一看,不必将类移动到包中,您可能只需通过反射访问construcor,然后为构造函数方法构建一个方法句柄/lambda,以避免调用开销