在 Java 中节省内存:使内存占用最小
本文通过演示四种来减少内存的占用的方法和一种计算方法来解释如何减少 Java 中的内存使用量。
本文将介绍下一个技巧和主题:
- 在包装器上使用原始字段(例如,Boolean -> boolean)
- 减少制作扁平结构的类数量(将类折叠成一个或更少的类)
- 尽可能使用窄数据类型(例如,
short
代替int
、long
代替Date
等) - 使用掩码将一种类型隐藏在另一种类型中(例如,short 中的许多布尔值)
引入要减少的类结构
让我们切入正题,
启动用户对象
首先,让我们创建 3 个对象并设置所有字段。在我们的示例中,我们将使用所有唯一对象,甚至对于布尔值,我们将使用新实例(通过使用new Boolean
)。在这种情况下,我们的计算将是最悲观的(从规模的角度来看),但完全正确:
现在让我们计算每个对象的大小:
,User
实例的总大小是 320 字节(包括UserSalary
和UserPayment
实例一起)。为了验证它,让我们使用 JOL-core 库并打印它们的大小:
打印出来的结果分别为 56 字节、136 字节和 320 字节。
现在让我们改进这个例子布尔值。
在前面的示例中,我将布尔值作为唯一对象启动。在这里,我们将以重用相同Boolean.true
和Boolean.false
引用的方式启动(这就是您经常初始化它们的方式)。有了这样的初始化,我们的例子就会更加真实。(这一步不是优化,只是解释内存计算如何工作的附加步骤。)
现在我们必须重新计算所有对象,因为我们基本上改变了布尔初始化:
打印结果符合预期:56 字节、136 字节和 208 字节(从 320 字节减少)。
第一次内存优化:用原语替换所有包装器
对象来进行第一次真正的优化。在这种情况下,我们不能使用null,我们所有的值都将默认初始化。
现在,让我们重新计算快照大小,考虑到所有原始值都没有额外的引用并保留在它们的容器对象中:
如您所见,所有包装器对象都增加了 16 个额外的字节。这样的转换将总大小几乎减少了 2 倍,从 208 字节减少到 112 字节。
第二次内存优化:在一个类中折叠数据
这种优化在大多数情况下是不可能的,或者至少使 OOP 结构的可读性和可维护性降低;但在一些内存不足的紧急情况下,你别无选择。因此,让我们将所有字段移到一个类上并查看改进:
现在只有一个用户对象,我们也失去了用于引用的额外内存。是的,代码更具可读性和可维护性,但我们将大小几乎提高了 2 倍。JOL-Core 库也确认现在的总大小是 64 字节!
第三次内存优化:使用窄类型
如果您检查我们使用的所有类型,您可能会提到其中一些覆盖的范围比我们需要的更大:例如,salary
是int
value,覆盖范围从 -2147483648 到 -2,147,483,647,但根据我们的业务要求,它不会超过 32,767 USD/月。因此,我们可以使用Short
数据类型来代替integer
. 同样,我们可以替换java.sql.Date 并只保留long
值(即未来和过去的数千年)。
因此,在我们的案例中,我们将进行以下更改:
现在总快照大小为 40 字节大小。我们可以进一步改进它吗?是的!
第四次内存优化:使用窄类型
在这一步,我们将在较大类型中“隐藏”较小的数据类型。整数值被 2^32 个值覆盖。布尔值被 2^1 覆盖。所以在一个整数内,我们可以“隐藏” 32 个布尔值。同样的事情也适用于这些例子:
- 1 个整数 (2^32) 内的 32 个布尔值 (2^1)
- 1 个整数 (2^32) 内的 4 个字节 (2^7)
- 1 个整数 (2^32) 内的 2 个短裤 (2^16)
- 一个长整数 (2^64) 内的 2 个整数 (2^32)
- 等等。
编写逻辑来隐藏布尔值
在我们的示例中,我们将把所有 9 个布尔值封装在一个 short 中。我们将使用按位运算(右移、左移等)。您可以在本文中找到更多示例。
从布尔到短的转换:
从boolean
to的转换short
包括以下 3 个步骤:
- 转换
boolean
为1 (true)
和0 (false)
。 N
使用左移运算符将该值向左移动(<<)
。- 使用
OR (|)
运算符合并所有值。
因此,使用定义的标志顺序如下:
并在一种方法中执行所有步骤:
现在有了这个功能,我们可以将值隐藏boolean
在一个单一的short
:
从短值中揭示隐藏的布尔值
为了识别哪个标志有什么值,我们需要做一个反向转换:
results
逐步向右移动值N
(取决于标志顺序)。- 进行比较以“削减”正确的数字。
- 将此数字与 比较
1
,如果为 1,=> flags
则值为真。
所有步骤如下所示:
现在使用所有这些函数,我们可以尝试隐藏下一个值:
我们的最终值为229, 或者可以用二进制格式表示为011100101 ,并表示下一个值如下:
现在有了这个结果值:
最后的计算是我们User
类的总大小 是32字节。
结论
在对我们的示例进行了四次转换之后,我们得到了下一个足迹改进:
经过所有的转换,我们将类的大小从 208 字节减少到 32 字节,几乎是 7 倍。我们的对象更难读取和维护,但内存消耗急剧下降。在节省 1000 万用户的情况下,我们只需要 320MB 而不是 2.1GB。关于如何减少内存使用仍有一些技巧,但我希望我展示的示例有用。在这里您可以找到所有使用的示例。谢谢阅读。