String Kompak di Java 9

1. Ikhtisar

String di Java secara internal diwakili oleh char [] yang berisi karakter String . Dan, setiap karakter terdiri dari 2 byte karena Java secara internal menggunakan UTF-16.

Misalnya, jika sebuah String berisi sebuah kata dalam bahasa Inggris, 8 bit terdepan semuanya akan menjadi 0 untuk setiap karakter , karena karakter ASCII dapat direpresentasikan menggunakan satu byte.

Banyak karakter membutuhkan 16 bit untuk mewakili mereka tetapi secara statistik kebanyakan hanya membutuhkan 8 bit - representasi karakter LATIN-1. Jadi, ada ruang lingkup untuk meningkatkan konsumsi dan kinerja memori.

Yang juga penting adalah bahwa String biasanya menempati sebagian besar ruang heap JVM. Dan, karena cara mereka disimpan oleh JVM, dalam banyak kasus, sebuah String contoh dapat mengambil dua ruang itu benar-benar dibutuhkan .

Di artikel ini, kita akan membahas opsi Compressed String, yang diperkenalkan di JDK6 dan Compact String baru, yang baru-baru ini diperkenalkan dengan JDK9. Keduanya dirancang untuk mengoptimalkan konsumsi memori Strings di JMV.

2. String Terkompresi - Java 6

Rilis Kinerja JDK 6 update 21, memperkenalkan opsi VM baru:

-XX:+UseCompressedStrings

Ketika opsi ini diaktifkan, String disimpan sebagai byte [] , bukan char [] - dengan demikian, menghemat banyak memori. Namun, opsi ini akhirnya dihapus di JDK 7, terutama karena memiliki beberapa konsekuensi kinerja yang tidak diinginkan.

3. String Ringkas - Java 9

Java 9 telah mengusung konsep compact Strings ba ck.

Ini berarti bahwa setiap kali kita membuat String jika semua karakter String dapat direpresentasikan menggunakan representasi byte - LATIN-1, array byte akan digunakan secara internal, sehingga satu byte diberikan untuk satu karakter.

Dalam kasus lain, jika ada karakter yang membutuhkan lebih dari 8-bit untuk mewakilinya, semua karakter disimpan menggunakan dua byte untuk masing-masing - representasi UTF-16.

Jadi pada dasarnya, jika memungkinkan, itu hanya akan menggunakan satu byte untuk setiap karakter.

Sekarang, pertanyaannya adalah - bagaimana semua operasi String akan bekerja? Bagaimana cara membedakan representasi LATIN-1 dan UTF-16?

Nah, untuk mengatasi masalah ini, perubahan lain dilakukan pada implementasi internal String . Kami memiliki pembuat kode bidang akhir , yang menyimpan informasi ini.

3.1. Implementasi String di Java 9

Sampai saat ini, String disimpan sebagai char [] :

private final char[] value;

Mulai sekarang, itu akan menjadi byte []:

private final byte[] value;

Pembuat kode variabel :

private final byte coder;

Di mana pembuat kode bisa:

static final byte LATIN1 = 0; static final byte UTF16 = 1;

Sebagian besar operasi String sekarang memeriksa pembuat kode dan mengirimkan ke implementasi tertentu:

public int indexOf(int ch, int fromIndex) { return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex) : StringUTF16.indexOf(value, ch, fromIndex); } private boolean isLatin1() { return COMPACT_STRINGS && coder == LATIN1; } 

Dengan semua info yang dibutuhkan JVM siap dan tersedia, opsi VM CompactString diaktifkan secara default. Untuk menonaktifkannya, kita bisa menggunakan:

+XX:-CompactStrings

3.2. Cara Kerja pembuat kode

Dalam implementasi kelas String Java 9 , panjangnya dihitung sebagai:

public int length() { return value.length >> coder; }

Jika String hanya berisi LATIN-1, nilai codernya adalah 0 sehingga panjang String akan sama dengan panjang byte array.

Dalam kasus lain, jika String dalam representasi UTF-16, nilai pembuat kode adalah 1, dan karenanya panjangnya akan setengah dari ukuran array byte sebenarnya.

Perhatikan bahwa semua perubahan yang dibuat untuk Compact String, berada di implementasi internal kelas String dan sepenuhnya transparan untuk pengembang yang menggunakan String .

4. String Kompak vs. String Terkompresi

Dalam kasus JDK 6 Compressed Strings, masalah utama yang dihadapi adalah bahwa konstruktor String hanya menerima char [] sebagai argumen. Selain itu, banyak operasi String bergantung pada representasi char [] dan bukan array byte. Karena itu, banyak pembongkaran yang harus dilakukan, yang memengaruhi kinerja.

Sedangkan dalam kasus Compact String, mempertahankan field ekstra “coder” juga dapat meningkatkan overhead. Untuk mengurangi biaya pembuat kode dan penguraian byte ke karakter (dalam hal representasi UTF-16), beberapa metode diintrinsifikasi dan kode ASM yang dihasilkan oleh kompilator JIT juga telah ditingkatkan.

Perubahan ini menghasilkan beberapa hasil kontra-intuitif. LATIN-1 indexOf (String) memanggil metode intrinsik, sedangkan indexOf (char) tidak. Dalam kasus UTF-16, kedua metode ini memanggil metode intrinsik. Masalah ini hanya memengaruhi LATIN-1 String dan akan diperbaiki dalam rilis mendatang.

Dengan demikian, String Kompak lebih baik daripada String Terkompresi dalam hal kinerja.

Untuk mengetahui berapa banyak memori yang disimpan menggunakan Compact Strings, berbagai heap dump aplikasi Java dianalisis. Dan, meskipun hasilnya sangat bergantung pada aplikasi tertentu, peningkatan secara keseluruhan hampir selalu berarti.

4.1. Perbedaan Kinerja

Let's see a very simple example of the performance difference between enabling and disabling Compact Strings:

long startTime = System.currentTimeMillis(); List strings = IntStream.rangeClosed(1, 10_000_000) .mapToObj(Integer::toString) .collect(toList()); long totalTime = System.currentTimeMillis() - startTime; System.out.println( "Generated " + strings.size() + " strings in " + totalTime + " ms."); startTime = System.currentTimeMillis(); String appended = (String) strings.stream() .limit(100_000) .reduce("", (l, r) -> l.toString() + r.toString()); totalTime = System.currentTimeMillis() - startTime; System.out.println("Created string of length " + appended.length() + " in " + totalTime + " ms.");

Here, we are creating 10 million Strings and then appending them in a naive manner. When we run this code (Compact Strings are enabled by default), we get the output:

Generated 10000000 strings in 854 ms. Created string of length 488895 in 5130 ms.

Similarly, if we run it by disabling the Compact Strings using: -XX:-CompactStrings option, the output is:

Generated 10000000 strings in 936 ms. Created string of length 488895 in 9727 ms.

Clearly, this is a surface level test, and it can't be highly representative – it's only a snapshot of what the new option may do to improve performance in this particular scenario.

5. Conclusion

In this tutorial, we saw the attempts to optimize the performance and memory consumption on the JVM – by storing Strings in a memory efficient way.

Seperti biasa, seluruh kode tersedia di Github.