创建和使用临时堆
create and use a temporary heap
我在java开发一款游戏,基本上每1/60秒更新一次。在这样的更新过程中,大量数据被创建、传递然后未被引用,我们称这些数据为 DTO。据我了解,JVM 和 OS 将此数据存储在程序的堆中。堆因此堆积起来,直到 JAVA GC 达到 运行,它分析将所有未引用的数据标记为空闲并准备好重新使用。
现在,在我看来,这是不必要的,而且偶尔会出现延迟。我想解决问题的方法是分配一块内存用作临时堆。该堆用于存储我在更新间隔期间创建和传递的所有临时内容。一旦我到达更新的结尾,就不需要任何分析,但我可以简单地说整个块是免费的,这样在更新后重用相同的 heap-space 更新。有点像尾递归栈帧。
另一种解决方案是仅使用可变 DTO 对象并分配其中的一堆对象并重用它们,但我喜欢利用这一优势使事物尽可能不可变。
这是为了用代码来说明我的意思:
void start(){
Data data = System.allocateDatainMb(500); //allocate 500Mb of virtual memory
MagicHeap() mh = System.createAMagicHeap(data); //make a heap of it
mh.use(); //set all constructors to allocate on this heap
while(true){
update();
mh.clear();
}
}
void update(){
TmpData dto = Gamesystem.createALargePieceOfTemporaryData();
someClass.doUpdateStuff(dto);
}
我不太熟悉 Java 如何管理内存,但我希望你明白我的意思。
某种程度上可以吗?
我可能会遗漏一些东西,但我很确定你不能在 Java 中这样做,因为它会干扰 Java 自己的内存管理。毕竟,Java 是为了保护用户免受低级内存管理例程的影响。
因此,我建议采用您提到的 DTO 池方法。一种改进方法可能是隐藏修改器(例如 setter 和其他修改方法)并且只允许工厂方法等访问它们(例如通过使用包私有可见性)。然后拥有工厂 create/reuse 看似不可变的实例并管理池。您甚至可以处理临时 "overflow",即当您需要比池实际提供的更多 DTO 实例时。
但是,如果您仔细考虑,您可能会发现很多您认为寿命很短的对象,如果使用得当,实际上它们的生命周期会更长。它可能只是一些计算的中间结果(比如 vector/matrix 数学),你在下一帧中不需要,但这些通常可以在创建它们后立即丢弃,并且在某些情况下也可以避免(例如,如果你使用手动基元或具有多个中间对象)。
您要做的是智取 JVM。这不是你想要做的。如果你真的需要直接玩内存分配,Java 不是合适的语言。
Java 的 GC 针对许多短命对象进行了优化,它希望能够在添加后不久从内存中释放大部分对象。不顾一切地设置某种缓存系统实际上会降低性能,因为现在这些对象无法从内存中释放,这意味着堆将有更少的 space 并且 (FULL) GC 暂停会更多频繁。这是缓存系统本身产生的任何开销的补充。
但这并不意味着您应该忽略 Java 的内存问题。您应该避免不必要地创建对象,更重要的是,您应该避免持有对象引用的时间超过必要的时间,这样您就可以让 GC 清理它。调整 GC 以满足您的程序需求并确保您使用的是现代 G1 GC 也可能是有益的。
与任何性能调整一样,实际衡量更改的性能差异非常重要。凭直觉你认为会更快的东西可能会导致你付出努力并增加复杂性而收效甚微,甚至毫无收获,或者更糟糕的是性能下降。确保您有真实的数字来支持任何性能问题。
我在java开发一款游戏,基本上每1/60秒更新一次。在这样的更新过程中,大量数据被创建、传递然后未被引用,我们称这些数据为 DTO。据我了解,JVM 和 OS 将此数据存储在程序的堆中。堆因此堆积起来,直到 JAVA GC 达到 运行,它分析将所有未引用的数据标记为空闲并准备好重新使用。
现在,在我看来,这是不必要的,而且偶尔会出现延迟。我想解决问题的方法是分配一块内存用作临时堆。该堆用于存储我在更新间隔期间创建和传递的所有临时内容。一旦我到达更新的结尾,就不需要任何分析,但我可以简单地说整个块是免费的,这样在更新后重用相同的 heap-space 更新。有点像尾递归栈帧。
另一种解决方案是仅使用可变 DTO 对象并分配其中的一堆对象并重用它们,但我喜欢利用这一优势使事物尽可能不可变。
这是为了用代码来说明我的意思:
void start(){
Data data = System.allocateDatainMb(500); //allocate 500Mb of virtual memory
MagicHeap() mh = System.createAMagicHeap(data); //make a heap of it
mh.use(); //set all constructors to allocate on this heap
while(true){
update();
mh.clear();
}
}
void update(){
TmpData dto = Gamesystem.createALargePieceOfTemporaryData();
someClass.doUpdateStuff(dto);
}
我不太熟悉 Java 如何管理内存,但我希望你明白我的意思。
某种程度上可以吗?
我可能会遗漏一些东西,但我很确定你不能在 Java 中这样做,因为它会干扰 Java 自己的内存管理。毕竟,Java 是为了保护用户免受低级内存管理例程的影响。
因此,我建议采用您提到的 DTO 池方法。一种改进方法可能是隐藏修改器(例如 setter 和其他修改方法)并且只允许工厂方法等访问它们(例如通过使用包私有可见性)。然后拥有工厂 create/reuse 看似不可变的实例并管理池。您甚至可以处理临时 "overflow",即当您需要比池实际提供的更多 DTO 实例时。
但是,如果您仔细考虑,您可能会发现很多您认为寿命很短的对象,如果使用得当,实际上它们的生命周期会更长。它可能只是一些计算的中间结果(比如 vector/matrix 数学),你在下一帧中不需要,但这些通常可以在创建它们后立即丢弃,并且在某些情况下也可以避免(例如,如果你使用手动基元或具有多个中间对象)。
您要做的是智取 JVM。这不是你想要做的。如果你真的需要直接玩内存分配,Java 不是合适的语言。
Java 的 GC 针对许多短命对象进行了优化,它希望能够在添加后不久从内存中释放大部分对象。不顾一切地设置某种缓存系统实际上会降低性能,因为现在这些对象无法从内存中释放,这意味着堆将有更少的 space 并且 (FULL) GC 暂停会更多频繁。这是缓存系统本身产生的任何开销的补充。
但这并不意味着您应该忽略 Java 的内存问题。您应该避免不必要地创建对象,更重要的是,您应该避免持有对象引用的时间超过必要的时间,这样您就可以让 GC 清理它。调整 GC 以满足您的程序需求并确保您使用的是现代 G1 GC 也可能是有益的。
与任何性能调整一样,实际衡量更改的性能差异非常重要。凭直觉你认为会更快的东西可能会导致你付出努力并增加复杂性而收效甚微,甚至毫无收获,或者更糟糕的是性能下降。确保您有真实的数字来支持任何性能问题。