Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suboptimal memory usage for zio.Atomic (inside zio.Ref) #9709

Closed
maximskripnik opened this issue Mar 18, 2025 · 0 comments · Fixed by #9710
Closed

Suboptimal memory usage for zio.Atomic (inside zio.Ref) #9709

maximskripnik opened this issue Mar 18, 2025 · 0 comments · Fixed by #9710
Labels
bug Something isn't working

Comments

@maximskripnik
Copy link
Contributor

maximskripnik commented Mar 18, 2025

Hello 👋

This issue is introduced in version 2.1.15 with #9500.

The root cause is this part:

override def toString: String = s"Ref.Atomic(initial = $initial)"

This usage of initial inside final class Atomic[A] prevents it from being garbage collected. The only other usage is inside val unsafe = new AtomicReference[A](initial), but that is mutable so if it wasn't for such toString implementation, it would have eventually be garbage collected after the referenced value is changed using any of the mutation methods.

It might not be a problem for cases when the initial value for Ref is not a large object, but in our case we happen to initiate it with very large values. The fact that those values are kept in memory even after the ref is modified, makes us waste gigabytes of memory with data that we will never access.

The previous toString version used to print the current value:

override def toString: String =
  s"Ref(${value.get})"

@hearnadam argues that this is unnecessary, which I can relate to, especially since it's not suspended. However, I think printing the initial value is arguably even less useful. It is possible to keep this behaviour it by memoizing initial.toString though:

private[zio] final class Atomic[A](initial: A) extends Ref[A] { self =>
  private val initialStr = initial.toString
  ...
  override def toString: String = s"Ref.Atomic(initial = $initialStr)"
  ...
}

I think it's fair to assume that in many cases the string representation of the initial will be considerably smaller than the actual value. But this way the initial value can be garbaged collected once the ref is modified, so there is much less memory usage. Alternatively, toString could be simplified even further and just not print any content of the wrapped value (initial or current). I guess the old behavior can also be brought back with usafe.get, though I agree with @hearnadam that it's not worth it

@hearnadam hearnadam added the bug Something isn't working label Mar 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants