Garbage Collector Change in Go 1.3

I was in the middle of writing a post on using Go's Pointer data type and realized that Go 1.3 affects how it can be used.

Here's demonstration of a slight change in behavior of the Garbage collector from Go 1.2 to Go 1.3

As the release notes mentioned of the garbage collector:

... a non-pointer Go value such as an integer will never be mistaken for a pointer and prevent unused memory from being reclaimed... Starting with Go 1.3, the runtime assumes that values with pointer type contain pointers and other values do not.

In other words, addresses should only be assigned to pointers. Integers or any other data type which is used to store an address is at risk of having its space reclaimed by the GC.

Demonstration

You can view the example program I've created here or directly on the playground. Note that the playground is running the latest version of Go (1.3 at the time of this article).'

Warning, this example makes use of capabilities that are now considered bad practice and even illegal. See the last bullet on the Notes section for more details.

Explanation

tl;dr: Allocate two integers with new(). Take one pointer and assign the value of the second pointer to it. Store the address of the lonely portion of memory in an integer variable, in this case a uintptr. Invoke the GC and display the content of the address stored in the integer variable. Observe that:

  • In Go 1.2, the integer is still present.

  • In Go 1.3, the integer value will no longer be present, indicating that its space has been reclaimed. This is because the address was stored in an integer variable. The GC wouldn't have reclaimed it if a pointer was storing the address.

overview: The example program has three primary variables: two pointers to integers (intPtr1, intPtr2) and one uintptr(addressHolder). Space for two integers are allocated (with values 100 and 999) with their addresses stored in intPtr1 and intPtr2, respectively.

After that, addressHolder is assigned the address of intPtr1.

Then a copy of intPtr2 is assigned to intPtr1, essentially leaving nothing to point to the address held in addressHolder.

The garbage collector is finally called, reclaiming the memory at the address held in addressHolder.

To confirm, dereference the address held in addressHolder using the unsafe package (again, see last bullet in notes for details) which yields different results in 1.2 and 1.3. Pay attention to the last lines of output in the following section.

Output

Go 1.2

intPtr1 = 0xc210000018, dereferenced value = 100  
intPtr2 = 0xc210000020, dereferenced value = 999

addressHolder(uintptr) has been assigned the value of intPtr1 which is the address 0xc210000018.

intPtr1 has been assigned the value of inPtr2 which is the address 0xc210000020, dereferenced value = 999

Calling runtime.GC()

addressHolder(uintptr), value = 0xc210000018, dereferenced value = 100

Go 1.3

intPtr1 = 0x10334020, dereferenced value = 100  
intPtr2 = 0x10334024, dereferenced value = 999

addressHolder(uintptr) has been assigned the value of intPtr1 which is the address 0x10334020.

intPtr1 has been assigned the value of inPtr2 which is the address 0x10334024, dereferenced value = 999

Calling runtime.GC()

addressHolder(uintptr), value = 0x10334020, dereferenced value = 271794224  

That's about it, coming up next will be a post on Go's Pointer data type. I'd appreciate any input!

Notes

  • A uintptr is an unsigned integer of 32 or 64 bits depending on your machine.

  • The release notes also mention that:

    Programs that use package unsafe to store integers in pointer-typed values are illegal and will crash if the runtime detects the behavior. Programs that use package unsafe to store pointers in integer-typed values are also illegal but more difficult to diagnose during execution.

    The example code does violate this rule, ironically to enforce it.

    To detect this behavior on a package, run go vet PACKAGE_NAME. the vet command is used to examine code for anything fishy. Running this on my example resulted in only warnings. I was not able to make the program crash.

  • If you're looking for a version manager, I recommend looking into gvm if you haven't already. Inspired by rvm(Ruby Version Manager), it works pretty well and was very useful in testing the example under different versions of Go.

  • One way to monitor memory is to take advantage of the runtime package which contains a MemStats type, used to record memory allocator statistics. Populate the struct with runtime.ReadMemStats() and you're given valuable information such as the number of allocated bytes, the last time the GC ran in absolute time, and total number of allocated objects in the Heap. Click here for the MemStats type in the runtime package.

Things learned

  • gvm(Go Version Manager), not to be confused with gvm(Groovy enVerionment Manager)
  • Existence of the runtime package and the ability to manually call the Garbage Collector and monitor memory.
  • Usage of go vet