在本文中,会带你如何使用最新的Java,让你最多可以节省25%的堆内存,这意味着更少的云服务费用。
您是否知道可以毫不费力地节省多达25%的堆内存和云服务器费用呢?好吧,那是真的。最近,最新的Java版本里中添加了许多令人兴奋的功能,在本文中,我将介绍其中的一项功能,因此请耐心等待。
众所周知,在任何的Java编写的项目中,字符串是应用程序中使用最多的对象。实际上,它几乎占据了Java应用程序堆大小的一半。
在深入探讨这一问题之前,让我回答您一个明显的问题,我知道您会问:Java中的String是如何产生的呢?
好吧,字符串不过是一个字符数组,至少过去是这样。如果从JDK 8打开String类,您将可以看到它。
但是,与C不同,在Java编程语言中,char数组不是String,并且String或char数组都不以' u0000'(NUL字符)结尾。尽管如此,Java中的String对象也是不可变的,这意味着String的内容永远不会改变,而char数组具有可变的元素。
在Java 8和Java 8之前的版本中,在String中使用char数组。一个char占用2个字节的内存。这意味着,要存储一个字符,您需要16位内存。例如,如果您编写“ Hello”,则需要一个数组对象,该对象将包含5个字符
字符串的总大小 = 数组对象本身的大小 + 5个字符的大小 + 数组的长度为整数
= 8个字节用于数组对象标头 + 5 * 2个字节 + 4个字节
= 8 + 10 + 4
= 22字节
但是,当今大多数西方本地人只需要8位字节数组即可对其进行编码。这就是Java 11引入新的紧凑型字符串的原因,该紧凑型字符串使用8位字节数组而不是char数组对字符串进行编码。除非他们明确需要16位字符。这些字符串被称为 紧凑字符串(CompactString)。
因此,Java 11中平均字符串的大小大约是Java 8中相同字符串的大小的一半。
平均而言,典型Java堆的50%可能被字符串对象消耗。这将因应用程序的不同而有所不同,但是平均而言,使用Java 11运行的此类程序的堆要求仅为使用Java 8运行的同一程序的75%。
这是巨大的节省。
-XX:+CompactStrings标志控制此功能
如果要禁用它,可以使用此标志 -XX:-CompactStrings。
参考JEP:http://openjdk.java.net/jeps/254
下面我们说说,CompactString的一些知识点。Compact String是作为JDK 9的一部分在JVM中引入的性能增强之一。直到JDK 8,无论何时我们创建一个String对象,然后在内部将其表示为char [],它由String对象的字符组成。
Compact String有什么需要?
- 直到JDK 8,Java都将String对象表示为char [],因为Java中的每个字符均为2个字节,因为Java内部使用UTF-16。
- 如果任何字符串包含英语单词,则该字符只能用一个字节表示,我们不需要每个字符2个字节。许多字符需要2个字节来表示它们,但是大多数字符仅需要1个字节,属于LATIN-1字符集。因此,存在改善内存消耗和性能的范围。
- Java 9引入了紧凑字符串的概念。紧凑字符串的主要目的是每当我们创建一个字符串对象,并且该对象内部的字符都可以使用1个字节表示时,这只不过是LATIN-1表示,那么内部Java将创建一个byte []。在其他情况下,如果任何字符需要多于1个字节来表示它,则每个字符使用2个字节(即UTF-16表示)进行存储。
- 这就是Java开发人员如何更改String的内部实现(即紧凑字符串)的方法,这将改善String的内存消耗和性能。
JDK8之前的String版本(或更低版本)
这里值得注意的是:在上述程序中,我们可以看到在Java 9之前,Java仅将String对象表示为char []。假设我们创建一个String对象,并且该对象包含可以使用1个字节表示的字符。代替将对象表示为byte [],它只会创建char [],这将消耗更多的内存。JDK开发人员分析说,大多数字符串只能使用Latin-1字符集表示。Latin-1字符可以存储在一个字节中,恰好是char大小的一半。这将提高String的性能。
JDK 9之后的String版本
注意:现在的问题是,如何区分LATIN-1和UTF-16表示形式?Java开发人员引入了一个最终的字节变量编码器,该编码器保留了有关字符表示的信息。编码器值的值可以是:
因此,就性能而言,新的String实现在Java 9中称为Compact String优于Java 9之前的String,因为与JDK 9堆中的String相比,Compact String使用的区域大约占一半。
我们举个栗子来说明这些区别:
这段代码如果在Java8或更早的版上运行,有以下的一些关键点:
- 在这里,我们创建了一个具有13个字符的String对象,并且该对象内部的字符可以使用1个字节表示,这不过是LATIN-1表示。
- 如果我们使用JDK 8或更早版本运行上述程序,则由于JDK 8默认使用UTF-16,因此内部字符串将表示为char []。
- 这里我们不需要char [],我们只能用1个字节表示每个字符。不是创建byte [],而是创建char [],并为每个字符在堆存储器中分配2个字节。这不过是堆内存的浪费。
如果在JDK9之后,则有以下的关键点:
- 从Java 9开始,将根据需要为String对象创建char []或byte []。正如我们所看到的,我们创建了具有13个字符的String对象s1和具有14个字符的对象s2。
- 对象s1内部存在的每个字符只能使用1个字节表示。因此,对于对象s1,将创建一个byte []。
- 现在对于s2,除了对象s1中存在的字符(即€)之外,还有一个附加字符。我们无法使用LATIN-1字符集来表示€字符。在这里,我们需要2个字节来表示€。这就是Java在这里将使用UTF-16表示s2内的字符的原因。
- 对于对象s2,将在内部创建char []。
- 这就是在内存消耗和性能方面,新的String实现(在Java 9中称为紧凑字符串)比Java 9之前的String更好。