Десериализация больших строк без мусора в Java, проблема с огромными объектами

Я ищу способ десериализовать String из byte[] на Java с минимальным количеством мусора. Поскольку я создаю свой собственный сериализатор и десериализатор, у меня есть полная свобода реализации любого решения на стороне сервера (т. е. при сериализации данных) и на стороне клиента (т. е. при десериализации данных).

Мне удалось эффективно сериализовать String без каких-либо накладных расходов на мусор, перебирая символы String's (String.charAt(i)) и преобразовывая каждый char (16-битное значение) в 2x 8-битное значение. Существует хорошая дискуссия по этому поводу здесь. Альтернативой является использование Reflection для прямого доступа к String's базовому char[], но это выходит за рамки проблемы.

Однако мне кажется невозможным десериализовать byte[] без создания char[] дважды, что кажется, ну, странным.

Процедура:

  1. Создать char[]
  2. Повторите byte[] и заполните char[]
  3. Создать строку с помощью конструктора String(char[])

Из-за правил неизменяемости Java String конструктор копирует char[], создавая двойные накладные расходы GC. Я всегда могу использовать механизмы, чтобы обойти это (небезопасное выделение String + отражение для установки экземпляра char[]), но я просто хотел спросить, есть ли какие-либо последствия для этого, кроме того, что я нарушаю все соглашения о неизменности String's.

Конечно, самым мудрым ответом на это было бы: «Да ладно, прекрати это делать и доверься GC, оригинальный char[] будет чрезвычайно недолговечен, и G1 мгновенно избавится от него», что на самом деле имеет смысл, если char[] меньше 1/2 размера области G1. Если он больше, char[] будет непосредственно выделен как огромный объект (т.е. автоматически распространяется за пределы области G1). Такие объекты чрезвычайно трудно эффективно собирать в G1. Вот почему каждое распределение имеет значение.

Есть идеи, как решить проблему?

Большое спасибо.


person SergioTCG    schedule 21.01.2015    source источник
comment
Вы рассматривали возможность просто не работать со строками, а просто сериализовать необработанные байтовые данные и выполнять преобразования набора символов в подразделах, когда это абсолютно необходимо?   -  person the8472    schedule 21.01.2015
comment
У меня есть. Моя идея состояла в том, чтобы создать новый класс MutableString и реализовать над ним множество традиционно тяжелых операций с мусором (например, fastpath String split), а затем иметь метод toString(from, to), который создает экземпляр представления типа String. Я могу сделать это. Но это потребует полного рефакторинга нашего приложения и использования MutableStrings везде, где это возможно. Это хорошая идея, но я хотел сначала изучить альтернативы.   -  person SergioTCG    schedule 21.01.2015
comment
Знаете ли вы, что все эти вещи уже существуют? Есть CharBuffer и StringBuilder, оба являются своего рода изменяемым String (если вы не создали неизменяемое представление), есть методы для создания их облегченных подпоследовательностей, и все они реализуют CharSequence, interface, на котором пакет регулярных выражений, который фактически реализует операция split работает. И хотя выглядит так, содержимое символов постоянно копируется при преобразовании между Strings, CharBuffers и StringBuilders при просмотре исходного кода, HotSpot предлагает для них специальные оптимизации…   -  person Holger    schedule 21.01.2015


Ответы (3)


Такие объекты чрезвычайно трудно эффективно собирать в G1.

Возможно, это уже не так, но вам придется оценить это для своего собственного приложения. Ошибки JDK 8027959 и 8048179 представляет новые механизмы сбора огромных недолговечных объектов. В соответствии с флагами ошибок вам, возможно, придется работать с версиями jdk ≥8u40 и ≥8u60, чтобы воспользоваться их соответствующими преимуществами.

Экспериментальный вариант интереса:

-XX:+G1ReclaimDeadHumongousObjectsAtYoungGC

Отслеживание:

-XX:+G1TraceReclaimDeadHumongousObjectsAtYoungGC

Для получения дополнительных советов и вопросов, касающихся этих функций, я бы рекомендовал посетить hotspot-gc- использовать список рассылки.

person the8472    schedule 21.01.2015
comment
Спасибо, я посмотрю. - person SergioTCG; 21.01.2015

Я нашел решение, которое бесполезно, если у вас неуправляемая среда.

Класс java.lang.String имеет закрытый для пакета конструктор String(char[] value, boolean share).

Источник:

/*
* 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).

Решение (или хак, как бы вы это ни называли) состоит в том, чтобы переместить класс в пакет java.lang и получить доступ к конструктору, приватному пакету. Менеджеру безопасности это не сулит ничего хорошего, но это можно обойти.

person SergioTCG    schedule 22.01.2015
comment
вместо того, чтобы перемещать класс в пакет, вы, вероятно, могли бы просто получить доступ к конструктору через отражение, а затем создать дескриптор метода/лямбда для метода конструктора, чтобы избежать накладных расходов - person the8472; 23.01.2015

Нашел рабочее решение с простой "секретной" нативной библиотекой Java:

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