Memory leaks are a common issue regardless of the programming language used. However, it’s relatively complex to write memory-leaking code in Go. This article will illustrate several scenarios where memory leaks may occur, allowing us to learn how to avoid them by studying these anti-patterns.
This article was first published in the Medium MPP plan. If you’re a Medium user, please follow me on Medium. Thank you!
Resource Leaks
Not Closing Opened Files
When you finish an open file, you should always call its Close
method. Failing to do so may result in reaching the maximum number of file descriptors, preventing you from opening new files or connections. This can lead to a “too many open files” error, as shown below:
Code Example 1: Not closing files leads to file descriptor exhaustion.
|
|
Error Output:
|
|
On my Mac, the maximum number of file handles a process can open is 61,440. Go processes typically open three file descriptors (stderr
, stdout
, stdin
), leaving a maximum of 61,437 open files. You can manually adjust this limit.
Not Closing http.Response.Body
Go has a well-known bug where forgetting to close the body of an HTTP request can lead to memory leaks. For example:
Code Example 2: Not closing the HTTP body causes a memory leak.
|
|
For more details on this issue, you can refer to:
- Is
resp.Body.Close()
necessary if we don’t read anything from the body? - Must Close GoLang HTTP Response
String and Slice Memory Leaks
The Go spec does not explicitly state whether substrings share memory with their original string. However, the compiler allows this behavior, which is generally good as it reduces memory and CPU usage. But this can sometimes lead to temporary memory leaks.
Code Example 3: Memory leak caused by strings.
https://gist.github.com/hxzhouh/e09587195e2d7aa2d5f6676777c6cb16
To prevent temporary memory leaks, we can use strings.Clone()
.
Code Example 4: Using strings.Clone()
to avoid temporary memory leaks.
|
|
Goroutine Leaks
Goroutine Handler
Most memory leaks are due to goroutine
leaks. For instance, the example below can quickly exhaust memory, resulting in an OOM
(Out of Memory) error.
Code Example 5: goroutine
handler leak.
|
|
Misusing Channels
Incorrect usage of channels can also easily lead to goroutine leaks. For unbuffered channels, both the producer and consumer must be ready before writing to the channel, or it will block. In the example below, the function exits early, causing a goroutine leak.
Code Example 6: Unbuffered channel misuse causes a goroutine leak.
|
|
Simply changing it to a buffered channel can solve this issue: c := make(chan error, 1)
.
Misusing range
with Channels
Channels can be iterated using range
. However, if the channel is empty,the range
will wait for new data, potentially blocking the goroutine
.
Code Example 7: Misusing range
causes a goroutine
leak.
|
|
To fix this, close the channel after calling wg.Wait()
.
Misusing runtime.SetFinalizer
If two objects are both set with runtime.SetFinalizer
and they reference each other, they will not be garbage collected, even if they’re no longer in use. For more information, you can refer to my other article.
time.Ticker
This was an issue before Go 1.23. If ticker.Stop()
is not called, it could cause a memory leak. As of Go 1.23, this issue has been fixed. More details here.
Misusing defer
Although using defer
to release resources does not directly cause memory leaks, it can lead to temporary memory leaks in two ways:
- Execution time:
defer
is always executed when the function ends. If your function runs for too long or never ends, the resources indefer
may not be released in time. - Memory usage: Each
defer
adds a call point in memory. If used inside a loop, this may cause a temporary memory leak.
Code Example 8: Temporary memory leak caused by defer
.
|
|
This code causes a temporary memory leak and can lead to a “too many open files” error. Avoid using defer
excessively unless necessary.
Conclusion
This article covered several behaviors in Go that can lead to memory leaks, with goroutine
leaks being the most common. The improper use of channels, especially with select
and range
, can make detecting leaks more difficult. When faced with memory leaks, pprof can help quickly locate the problem, ensuring you write more robust code.