1. Overview
1.概述
In this tutorial, we show the pros and cons of using Java primitive types and their wrapped counterparts.
在本教程中,我们展示了使用Java原始类型及其封装后的对应类型的优点和缺点。
2. Java Type System
2.JAVA类型系统
Java has a two-fold type system consisting of primitives such as int, boolean and reference types such as Integer, Boolean. Every primitive type corresponds to a reference type.
Java有一个双重类型系统,由基元(如int, boolean)和引用类型(如Integer, Boolean)组成。每个基元类型都对应着一个引用类型。
Every object contains a single value of the corresponding primitive type. The wrapper classes are immutable (so that their state can’t change once the object is constructed) and are final (so that we can’t inherit from them).
每个对象都包含一个相应原始类型的单个值。封装类是不可变的(因此一旦对象被构造出来,它们的状态就不会改变),并且是最终的(因此我们不能从它们那里继承)。
Under the hood, Java performs a conversion between the primitive and reference types if an actual type is different from the declared one:
在引擎盖下,如果实际类型与声明类型不同,Java会在原始类型和引用类型之间进行转换。
Integer j = 1; // autoboxing
int i = new Integer(1); // unboxing
The process of converting a primitive type to a reference one is called autoboxing, the opposite process is called unboxing.
将原始类型转换为引用类型的过程被称为自动装箱,相反的过程被称为解箱。
3. Pros and Cons
3.优点和缺点
The decision what object is to be used is based on what application performance we try to achieve, how much available memory we have, the amount of available memory and what default values we should handle.
决定使用什么对象是基于我们试图实现什么应用性能,我们有多少可用的内存,可用的内存数量和我们应该处理的默认值。
If we face none of those, we may ignore these considerations though it’s worth knowing them.
如果我们没有面临这些问题,我们可能会忽略这些考虑,尽管值得了解它们。
3.1. Single Item Memory Footprint
3.1.单个项目的内存足迹
Just for the reference, the primitive type variables have the following impact on the memory:
仅供参考,原始类型变量对内存有如下影响。
- boolean – 1 bit
- byte – 8 bits
- short, char – 16 bits
- int, float – 32 bits
- long, double – 64 bits
In practice, these values can vary depending on the Virtual Machine implementation. In Oracle’s VM, the boolean type, for example, is mapped to int values 0 and 1, so it takes 32 bits, as described here: Primitive Types and Values.
在实践中,这些值会因虚拟机的实现而不同。例如,在Oracle的虚拟机中,布尔类型被映射为int值0和1,所以它需要32位,如这里所述。原始类型和值。
Variables of these types live in the stack and hence are accessed fast. For the details, we recommend our tutorial on the Java memory model.
这些类型的变量生活在堆栈中,因此被快速访问。有关细节,我们推荐我们的关于Java内存模型的教程>。
The reference types are objects, they live on the heap and are relatively slow to access. They have a certain overhead concerning their primitive counterparts.
引用类型是对象,它们生活在堆上,访问起来相对缓慢。它们有一定的开销,与它们的原始对应物相关。
The concrete values of the overhead are in general JVM-specific. Here, we present results for a 64-bit virtual machine with these parameters:
一般来说,开销的具体数值是针对JVM的。在这里,我们提出了一个64位虚拟机的结果,这些参数。
java 10.0.1 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
To get an object’s internal structure, we may use the Java Object Layout tool (see our another tutorial on how to get the size of an object).
为了获得对象的内部结构,我们可以使用Java Object Layout工具(参见我们另一个教程,了解如何获得对象的大小)。
It turns out that a single instance of a reference type on this JVM occupies 128 bits except for Long and Double which occupy 192 bits:
事实证明,除了Long和Double占用192位之外,这个JVM上的一个引用类型实例占用了128位。
- Boolean – 128 bits
- Byte – 128 bits
- Short, Character – 128 bits
- Integer, Float – 128 bits
- Long, Double – 192 bits
We can see that a single variable of Boolean type occupies as much space as 128 primitive ones, while one Integer variable occupies as much space as four int ones.
我们可以看到,一个Boolean类型的变量占用的空间与128个原始变量一样多,而一个Integer变量占用的空间与四个int变量一样多。
3.2. Memory Footprint for Arrays
3.2.阵列的内存足迹
The situation becomes more interesting if we compare how much memory occupy arrays of the types under consideration.
如果我们比较所考虑的类型的数组占用多少内存,情况会变得更有趣。
When we create arrays with the various number of elements for every type, we obtain a plot:
当我们为每一种类型的元素创建不同数量的数组时,我们会得到一个情节。
that demonstrates that the types are grouped into four families with respect to how the memory m(s) depends on the number of elements s of the array:
这表明,根据内存m(s)对数组元素数s的依赖程度,这些类型被分为四个系列。
- long, double: m(s) = 128 + 64 s
- short, char: m(s) = 128 + 64 [s/4]
- byte, boolean: m(s) = 128 + 64 [s/8]
- the rest: m(s) = 128 + 64 [s/2]
where the square brackets denote the standard ceiling function.
其中方括号表示标准上限函数。
Surprisingly, arrays of the primitive types long and double consume more memory than their wrapper classes Long and Double.
令人惊讶的是,原始类型long和double的数组比它们的封装类Long和Double消耗更多内存。
We can see either that single-element arrays of primitive types are almost always more expensive (except for long and double) than the corresponding reference type.
我们也可以看到,原始类型的单元素数组几乎总是比相应的引用类型更昂贵(除了long和double)。
3.3. Performance
3.3.业绩
The performance of a Java code is quite a subtle issue, it depends very much on the hardware on which the code runs, on the compiler that might perform certain optimizations, on the state of the virtual machine, on the activity of other processes in the operating system.
Java代码的性能是一个相当微妙的问题,它在很大程度上取决于代码运行的硬件,取决于可能进行某些优化的编译器,取决于虚拟机的状态,取决于操作系统中其他进程的活动。
As we have already mentioned, the primitive types live in the stack while the reference types live in the heap. This is a dominant factor that determines how fast the objects get be accessed.
正如我们已经提到的,原始类型生活在堆栈中,而引用类型生活在堆中。这是一个决定对象访问速度的主导因素。
To demonstrate how much the operations for primitive types are faster than those for wrapper classes, let’s create a five million element array in which all elements are equal except for the last one; then we perform a lookup for that element:
为了证明原始类型的操作比包装类的操作快多少,让我们创建一个500万个元素的数组,其中除了最后一个元素,其他元素都是相等的;然后我们对这个元素进行查找。
while (!pivot.equals(elements[index])) {
index++;
}
and compare the performance of this operation for the case when the array contains variables of the primitive types and for the case when it contains objects of the reference types.
并比较该操作在数组包含原始类型的变量和包含引用类型的对象的情况下的性能。
We use the well-known JMH benchmarking tool (see our tutorial on how to use it), and the results of the lookup operation can be summarized in this chart:
我们使用著名的JMH基准测试工具(参见我们的教程,了解如何使用该工具),查询操作的结果可以总结为这个图表。
Even for such a simple operation, we can see that it’s required more time to perform the operation for wrapper classes.
即使是这样一个简单的操作,我们也可以看到,对封装类进行操作需要更多的时间。
In case of more complicated operations like summation, multiplication or division, the difference in speed might skyrocket.
如果是更复杂的操作,如求和、乘法或除法,速度上的差异可能会急剧上升。
3.4. Default Values
3.4.默认值
Default values of the primitive types are 0 (in the corresponding representation, i.e. 0, 0.0d etc) for numeric types, false for the boolean type, \u0000 for the char type. For the wrapper classes, the default value is null.
原始类型的默认值是:数字类型为0(在相应的表示中,即0,0.0d等),布尔类型为false,char类型为\u0000。对于封装类,默认值是null。
It means that the primitive types may acquire values only from their domains, while the reference types might acquire a value (null) that in some sense doesn’t belong to their domains.
这意味着原始类型可能只从它们的域中获取值,而引用类型可能获取在某种意义上不属于它们的域的值(null)。
Though it isn’t considered a good practice to leave variables uninitialized, sometimes we might assign a value after its creation.
虽然让变量不被初始化并不是一个好的做法,但有时我们可能会在变量创建后分配一个值。
In such a situation, when a primitive type variable has a value that is equal to its type default one, we should find out whether the variable has been really initialized.
在这种情况下,当一个原始类型的变量有一个等于其类型默认的值时,我们应该找出变量是否真的被初始化了。
There’s no such a problem with a wrapper class variables since the null value is quite an evident indication that the variable hasn’t been initialized.
在封装类变量中没有这样的问题,因为null值很明显地表明了该变量还没有被初始化。
4. Usage
4.使用情况
As we’ve seen, the primitive types are much faster and require much less memory. Therefore, we might want to prefer using them.
正如我们所看到的,原始类型要快得多,需要的内存也少得多。因此,我们可能希望优先使用它们。
On the other hand, current Java language specification doesn’t allow usage of primitive types in the parametrized types (generics), in the Java collections or the Reflection API.
另一方面,当前的Java语言规范不允许在参数化类型(泛型)、Java集合或反射API中使用原始类型。
When our application needs collections with a big number of elements, we should consider using arrays with as more “economical” type as possible, as it’s illustrated on the plot above.
当我们的应用程序需要大量元素的集合时,我们应该考虑使用尽可能 “经济 “的数组类型,正如上面的图所示。
5. Conclusion
5.总结
It this tutorial, we illustrated that the objects in Java are slower and have a bigger memory impact than their primitive analogs.
在本教程中,我们说明了Java中的对象比它们的原始类似物更慢,对内存的影响更大。
As always, code snippets can be found in our repository on GitHub.
一如既往,代码片段可以在我们的GitHub上的存储库中找到。