Ответ как обычно зависит от версии Java. Два поля, которые присутствовали во всех версиях – массив символов
char[] value и
int hash. В поле
hash кэшируется хэш-сумма при первом подсчете для соблюдения
контракта метода hashCode.
До Java 7 были еще поля
offset и
count – чтобы переиспользовать без пересоздания массивы из
билдеров и других строк. В современной джаве от этого отказались в угоду меньшего потребления памяти.
Изначально все строки хранились в кодировке UTF-16, каждый символ занимал по два байта и умещался в
char. Однако выяснилось, что статистически большинство строк содержит только ASCII-символы, которые вмещаются в один байт и составляют кодировку LATIN-1. То есть старший байт в большинстве
char остается нулевым, и строки наполовину состоят из пустоты. А
примерно четверть памяти приложений состоит из одних только строк.
В Java 6 была введена экспериментальная фича
Compressed Strings – способность строки хранить строки в LATIN-1 в массиве
byte вместо
char. С этой фичей был ряд проблем, и позднее ее убрали.
Снова сжатие строк
появилось в Java 9 – теперь оно называется
Compact Strings и включено по умолчанию. В классе String появилось поле
coder, которое переключает кодировки, и статический флаг
COMPACT_STRINGS, выключающий фичу вообще. Тип массива
value окончательно изменился с
char[] на
byte[].
Для полного обзора особенностей класса String стоит посмотреть доклады Алексея Шипилёва (
1,
2).