package main
import "fmt"
func main() {
// Эти переменные размещаются на стеке
x := 10
y := 20
z := x + y
fmt.Println(z) // 30
// Когда функция завершится, все переменные автоматически удалятся со стека
}
package main
import "fmt"
func createUser() *User {
// Эта структура размещается в куче
user := &User{
Name: "Alice",
Age: 30,
}
// Возвращаем указатель - данные останутся в памяти
return user
}
type User struct {
Name string
Age int
}
func main() {
user := createUser()
fmt.Println(user.Name) // Alice
// Данные живут в куче, пока на них есть ссылки
}
package main
import "fmt"
// Данные НЕ "убегают" - размещаются на стеке
func stackExample() {
x := 42 // остаётся внутри функции
fmt.Println(x)
}
// Данные "убегают" - размещаются в куче
func heapExample() *int {
x := 42
return &x // ← возвращаем указатель - данные "убегают"!
}
func main() {
stackExample()
ptr := heapExample()
fmt.Println(*ptr)
} go build -gcflags="-m" main.go package main
func stackOnly() {
x := 10
y := 20
_ = x + y
}
func escapeToHeap() *int {
x := 42
return &x
}
func main() {
stackOnly()
escapeToHeap()
} ./main.go:8:2: moved to heap: x package main
func smallData() {
// Маленькие данные - размещаются на стеке
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
_ = arr
}
func largeData() {
// Большие данные - размещаются в куче
arr := [1000000]int{} // ~8MB
_ = arr
}
func main() {
smallData()
largeData()
} package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// Создаём структуру
p := Person{Name: "Alice", Age: 30}
// Создаём указатель на структуру
ptr := &p
// Два способа доступа к полям через указатель:
fmt.Println((*ptr).Name) // Классический способ со скобками
fmt.Println(ptr.Name) // Go позволяет опускать * и скобки (синтаксический сахар)
// Изменение полей через указатель
ptr.Age = 31
fmt.Println(p.Age) // 31 (оригинальная структура изменилась)
} package main
import (
"fmt"
"runtime"
)
type Data struct {
Value [1000]int // ~8KB данных
}
func createGarbage() {
// Создаём данные в куче
data := &Data{}
data.Value[0] = 42
fmt.Println("Данные созданы")
// Когда функция завершится, на data больше нет ссылок
// Сборщик мусора сможет удалить эти данные
}
func main() {
for i := 0; i < 10; i++ {
createGarbage()
}
// Принудительный запуск сборщика мусора (обычно не нужен)
runtime.GC()
fmt.Println("Сборка мусора выполнена")
} Начальное состояние:
├─ Корневые объекты → серые
└─ Все остальные объекты → белые
Шаг 1: Берём серый объект
├─ Проверяем все указатели из этого объекта
├─ Белые объекты, на которые он ссылается, красим в серый
└─ Сам объект красим в чёрный
Шаг 2: Повторяем, пока есть серые объекты
Результат:
├─ Чёрные объекты — оставляем в памяти
└─ Белые объекты — удаляем (на них нет ссылок) package main
type Node struct {
Value int
Next *Node
Prev *Node
}
func createCycle() *Node {
// Создаём двусвязный список
first := &Node{Value: 1}
second := &Node{Value: 2}
// Циклические ссылки
first.Next = second
second.Prev = first
return first // ← данные "убегают" в кучу
}
func main() {
// Получаем указатель на список с циклическими ссылками
head := createCycle()
// Всё в порядке - сборщик мусора справится с циклами
head = nil
// Оба узла будут удалены, несмотря на циклические ссылки,
// так как они недостижимы из корневых указателей
} package main
import (
"testing"
)
var globalPtr *int // глобальная переменная для "убегания" данных
// Размещение на стеке - быстро
func BenchmarkStack(b *testing.B) {
for i := 0; i < b.N; i++ {
x := 42
_ = x
}
}
// Размещение в куче - медленнее
func BenchmarkHeap(b *testing.B) {
for i := 0; i < b.N; i++ {
x := new(int)
*x = 42
globalPtr = x // ← данные "убегают" в глобальную переменную
}
} BenchmarkStack-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op
BenchmarkHeap-8 50000000 30.00 ns/op 8 B/op 1 allocs/op package main
import "fmt"
// ❌ Плохо: маленький слайс удерживает весь большой массив
func getHeaderBad(data []byte) []byte {
// Читаем большой файл (10MB)
// Но нам нужны только первые 100 байт (заголовок)
header := data[:100]
// Проблема: header хранит указатель на весь массив data!
// Даже если data выйдет из области видимости, память не освободится,
// пока существует header
return header
}
func processBad() []byte {
// Загружаем 10MB данных
bigData := make([]byte, 10*1024*1024)
// ... читаем из файла ...
// Извлекаем заголовок
header := getHeaderBad(bigData)
// bigData выходит из области видимости, НО
// header всё ещё ссылается на базовый массив 10MB!
return header // проблема: 100 байт удерживают 10MB
}
// ✅ Хорошо: создаём копию нужных данных
func getHeaderGood(data []byte) []byte {
// Создаём новый независимый слайс
header := make([]byte, 100)
copy(header, data[:100])
// Теперь header не связан с data
return header
}
func processGood() []byte {
// Загружаем 10MB данных
bigData := make([]byte, 10*1024*1024)
// ... читаем из файла ...
// Извлекаем заголовок с копированием
header := getHeaderGood(bigData)
// bigData выходит из области видимости и может быть удалён GC
return header // ✅ возвращаем только 100 байт
}
func main() {
header := processGood()
fmt.Printf("Обработан заголовок: %d байт\n", len(header))
// Теперь в памяти только 100 байт, а не 10MB
}