# Sometimes cannot reclaim memory by manual GC.gc()

**URL:** <https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158>\
**Category:** General Usage\
**Tags:** question, gurobi, gc\
**Created:** [January 20, 2026, 5:36am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158 "2026-01-20T05:36:10Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 20, 2026, 5:36am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/1 "2026-01-20T05:36:10Z")

</div>

**Edit:** Please see the #7 post directly if you’re interested. Thanks.

* * *

The idea is: I at first create a Vector

```julia-auto
v = Vector{Matrix{Float64}}(undef, 100);

```

Then I need to fill it with some concrete objects (here are matrices):

```julia-auto
for i = 1:100
   v[i] = rand(9999,9999)
end

```

These operations will use much (say 60GB) memory.  
Suppose now I’ve finished using the entry matrices and now I want to reclaim my memory.  
I can do

```julia-auto
dumb = rand(3, 3)
for i = 1:100
    v[i] = dumb
end
GC.gc()

```

Then the memory occupation will drop to the initial level (say, 60GB are freed).

* * *

The question now is that the above procedure appears not to be effective for JuMP’s direct\_models (with the common `GRB_ENV`).

```julia-auto
const GRB_ENV = Gurobi.Env();
const m = Vector{JuMP.Model}(undef, S); # S is some large number (of scenarios)
# fill `m` with `S` concrete large distinct models, which are all initialized by this function
function model(GRB_ENV)
    m = JuMP.direct_model(Gurobi.Optimizer(GRB_ENV))
    JuMP.set_attribute(m, "OutputFlag", 0)
    JuMP.set_attribute(m, "Threads", 1)
    JuMP.set_attribute(m, "Method", 2)
    JuMP.set_attribute(m, "Crossover", 0)
    m
end;
# then create a common dumb model also using the above function
# then flush the `m` with the dumb model
# I fail to see my memory reclaimed, why?

```

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 20, 2026, 6:00am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/2 "2026-01-20T06:00:53Z")

</div>

According to my observation from the `htop` in zsh, the occupation at three instances are illustrated below

```julia-auto
const S = 256
const GRB_ENV = Gurobi.Env();
const m = Vector{JuMP.Model}(undef, S);
function my_initialize(GRB_ENV)
    m = JuMP.direct_model(Gurobi.Optimizer(GRB_ENV))
    JuMP.set_attribute(m, "OutputFlag", 0)
    JuMP.set_attribute(m, "Threads", 1)
    JuMP.set_attribute(m, "Method", 2)
    JuMP.set_attribute(m, "Crossover", 0)
    m
end
function fill_with_concrete!(m, s, Data)
    model = my_initialize(GRB_ENV)
    x = add_decision_and_constrs!(model, s, Data) # this operation will allocate much memory
    m[s] = model
end

# Mem[873M/256G]

foreach(wait, [Threads.@spawn(fill_with_concrete!(m, s, Data)) for s=1:S])

# Mem[4.68G/256G]

dumb = my_initialize(GRB_ENV)
for s=1:S
    m[s] = dumb
end
GC.gc()

# Mem[4.62G/256G]

```

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 20, 2026, 6:53am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/3 "2026-01-20T06:53:21Z")

</div>

The `Gurobi.Env` object is **not** thread-safe.

I guess I need to update:

> **[GitHub - jump-dev/Gurobi.jl: A Julia interface to the Gurobi Optimizer](https://github.com/jump-dev/Gurobi.jl?tab=readme-ov-file#environments-are-not-thread-safe)**
>
> A Julia interface to the Gurobi Optimizer

In addition, you must not simultaneously create multiple models using the same environment.

Edit: [Update thread-safety notes for Gurobi.Env by odow · Pull Request #665 · jump-dev/Gurobi.jl · GitHub](https://github.com/jump-dev/Gurobi.jl/pull/665)

Otherwise: this code should work. If memory isn’t being freed, you must have a reference to the model somewhere that is preventing the GC from freeing it. You can see how many models are attached to the environment using `GRB_ENV.attached_models`.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 20, 2026, 7:39am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/4 "2026-01-20T07:39:27Z")

</div>

> [@odow](#):
>
> Otherwise: this code should work.

The behavior is now 858M → 4.69G → 4.65G, even if I’ve removed the `GRB_ENV` dependency (see the cyan arrow below).

 ![image](https://global.discourse-cdn.com/julialang/original/3X/8/c/8c07605c3cbee44e62e481f49fba43c1e394d6c6.png)

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 20, 2026, 7:55am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/5 "2026-01-20T07:55:08Z")

</div>

I even further removed the Gurobi dependency and work with `JuMP.Model()` now.

The major memory is still unreclaimed (793M → 3.14G → 2.73G)

 ![image](https://global.discourse-cdn.com/julialang/original/3X/f/b/fb304c4b8e5274fdc46359508dc69a635427229d.png)

If I further enlarge `S` from `256` to `2560`, then memory pattern now is 878M → 11.2G → 9.21G

---

<div class="post-metadata">

**Author:** ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)\
**Post date:** [January 20, 2026, 8:39am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/6 "2026-01-20T08:39:10Z")

</div>

Try running `GC.gc()` multiple times. And add a `return nothing` to the end of `perfect_consts!`. You must have a reference somewhere that is preventing the models from being garbage collected.

Does it happen if you remove `Threads.@spawn`?

I don’t think this is related to JuMP.

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 20, 2026, 12:33pm UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/7 "2026-01-20T12:33:37Z")

</div>

> [@odow](#):
>
> I don’t think this is related to JuMP.

I agree.

I find that the manual `GC.gc()` appears to be nondeterministic, via this self-contained general example:

```julia-auto
function f!(v)
    m = v[end]
    for i = eachindex(v)
        v[i] = m
    end
end;
# 662M here
v = map(fetch, [Threads.@spawn(rand(333,333)) for _ = 1:10000]);
# 8.91G here
f!(v)
GC.gc()
# 8.91G here
v1 = map(fetch, [Threads.@spawn(rand(333,555)) for _ = 1:10000]);
# 22.1G here
f!(v1)
GC.gc()
# 999M here

```

The 5 comments are observed from my `htop` in zsh.

---

<div class="post-metadata">

**Author:** ![BioTurboNick](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bioturbonick/32/6380_2.png) [@BioTurboNick](https://discourse.julialang.org/u/BioTurboNick)\
**Post date:** [January 20, 2026, 4:53pm UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/8 "2026-01-20T16:53:05Z")

</div>

This might have changed in the last few versions since I learned this, but Julia doesn’t always release memory to the OS when it’s released within Julia, depending on memory pressure on the system and within Julia.

You may try `ccall(:malloc_trim, Int32, (Int32,), 0)` to release held memory.

---

<div class="post-metadata">

**Author:** ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)\
**Post date:** [January 20, 2026, 5:40pm UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/9 "2026-01-20T17:40:47Z")

</div>

FYI this isn’t Julia, this is just the allocator in libc (Julia will even call that function automatically for you depending on the condition)

---

<div class="post-metadata">

**Author:** ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)\
**Post date:** [January 21, 2026, 10:09am UTC](https://discourse.julialang.org/t/sometimes-cannot-reclaim-memory-by-manual-gc-gc/135158/10 "2026-01-21T10:09:24Z")

</div>

> [@odow](#):
>
> In addition, you must not simultaneously create multiple models using the same environment.

I’ve discovered how to build _independent_ models properly (probably).

> **Code**
>
> ```julia-auto
> module Settings
> import JuMP, Gurobi
> 
> const C = Dict{String, Any}("OutputFlag" => 0, "Threads" => 1, "Method" => 2, "Crossover" => 0)
> Env() = Gurobi.Env(C) # generate a _new_ one as defined by `Gurobi.Env`
> Model() = JuMP.direct_model(Gurobi.Optimizer(Env()))
> 
> function _fill!(v, i)
> v[i] = Model()
> return
> end
> _undef(N) = Vector{JuMP.Model}(undef, N)
> function Model(N) # still faster than 1-thread serial construction, i.e. [Model() for i=1:N]
> v = _undef(N)
> foreach(wait, [Threads.@spawn(_fill!(v, i)) for i=1:N])
> v
> end
> 
> end
> 
> ```
