How to manage memory when using JuMP+Gurobi?

Observe this

usr@usr:~/julia_projects/uc/Uc$ julia --project=. --threads=1,0
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.6 (2026-04-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> import Gurobi

julia> e = Gurobi.Env(); # omit output

julia> Gurobi.GRBfreeenv(e)

julia> sleep(0.001)

julia> exit()

[3525351] signal 11 (1): Segmentation fault
in expression starting at REPL[5]:1
pthread_mutex_lock at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
GRBfreeenv at /opt/gurobi1301/linux64cuda13/lib/libgurobi130.so (unknown line)
GRBfreeenv at /home/amd/.julia/packages/Gurobi/K2XSK/src/gen130/libgrb_api.jl:914 [inlined]
#4 at /home/amd/.julia/packages/Gurobi/K2XSK/src/MOI_wrapper/MOI_wrapper.jl:158
jfptr_YY.4_5073 at /home/amd/.julia/compiled/v1.12/Gurobi/do9v6_POo9T.so (unknown line)
run_finalizer at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/gc-common.c:180
jl_gc_run_finalizers_in_list at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/gc-common.c:270
run_finalizers at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/gc-common.c:316
ijl_atexit_hook at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/init.c:292
ijl_exit at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/init.c:196
jlplt_ijl_exit_25543.1 at /home/amd/.julia/juliaup/julia-1.12.6+0.x64.linux.gnu/lib/julia/sys.so (unknown line)
exit at ./initdefs.jl:28
exit at ./initdefs.jl:29
jfptr_exit_63577.1 at /home/amd/.julia/juliaup/julia-1.12.6+0.x64.linux.gnu/lib/julia/sys.so (unknown line)
jl_apply at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/julia.h:2391 [inlined]
do_call at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/interpreter.c:123
eval_value at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/interpreter.c:243
eval_stmt_value at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/interpreter.c:194 [inlined]
eval_body at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/interpreter.c:707
jl_interpret_toplevel_thunk at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/interpreter.c:898
jl_toplevel_eval_flex at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/toplevel.c:1035
__repl_entry_eval_expanded_with_loc at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:301
jl_apply at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/julia.h:2391 [inlined]
jl_f_invokelatest at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/builtins.c:881
toplevel_eval_with_hooks at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:308
toplevel_eval_with_hooks at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:312
toplevel_eval_with_hooks at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:305 [inlined]
eval_user_input at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:330
repl_backend_loop at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:452
#start_repl_backend#41 at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:427
start_repl_backend at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:424 [inlined]
#run_repl#50 at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:653
run_repl at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/usr/share/julia/stdlib/v1.12/REPL/src/REPL.jl:639
jfptr_run_repl_19665.1 at /home/amd/.julia/juliaup/julia-1.12.6+0.x64.linux.gnu/share/julia/compiled/v1.12/REPL/u0gqU_E4m7X.so (unknown line)
run_std_repl at ./client.jl:478
jfptr_run_std_repl_24985.1 at /home/amd/.julia/juliaup/julia-1.12.6+0.x64.linux.gnu/lib/julia/sys.so (unknown line)
jl_apply at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/julia.h:2391 [inlined]
jl_f_invokelatest at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/builtins.c:881
run_main_repl at ./client.jl:499
repl_main at ./client.jl:586 [inlined]
_start at ./client.jl:561
jfptr__start_63319.1 at /home/amd/.julia/juliaup/julia-1.12.6+0.x64.linux.gnu/lib/julia/sys.so (unknown line)
jl_apply at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/julia.h:2391 [inlined]
true_main at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/jlapi.c:971
jl_repl_entrypoint at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/src/jlapi.c:1139
main at /cache/build/builder-amdci5-4/julialang/julia-release-1-dot-12/cli/loader_exe.c:58
unknown function (ip: 0x7cfc3c42a1c9) at /lib/x86_64-linux-gnu/libc.so.6
__libc_start_main at /lib/x86_64-linux-gnu/libc.so.6 (unknown line)
unknown function (ip: 0x4010b8) at /workspace/srcdir/glibc-2.17/csu/../sysdeps/x86_64/start.S
Allocations: 2947422 (Pool: 2947289; Big: 133); GC: 4
Segmentation fault (core dumped)

Another question is on the title, since I notice that julia GC seems not to automatically release all memory. So it appears that within one julia REPL session I’ll have memory leakage. What should I do to recycle memory?

At the moment it is illegal to call GRBfreeenv on an environment because GRBfreeenv is called as part of the finaliser.

One option is:

julia> import Gurobi

julia> env = Gurobi.Env();
Set parameter WLSAccessID
Set parameter WLSSecret
Set parameter LicenseID to value 722777
WLS license 722777 - registered to JuMP Development

julia> finalize(env)

julia> env
Gurobi.Env(Ptr{Nothing}(0x0000000000000000), true, 0)

julia> exit()

I have been thinking to add the syntax:

Gurobi.Env() do env
    # use the env
end

which would explicitly scope when the environment is created and destroyed.

Edit: I remember why I didn’t do this before. Consider the situation:

using Gurobi
Gurobi.Env() do env
    model_1 = Gurobi.Optimizer(env)
    # Stuff
    model_2 = Gurobi.Optimizer(env)
    # Stuff
    return
end

Before we can run the finaliser for env we must first have finalised all the referenced models. If the function runs, we finalise env, and then sometime later the GC finalises model_1 Gurobi crashes.

1 Like

I think for practical use cases, the Envs are allocated once. (For me, solving Gurobi models in parallel, I’ll have to allocate a vector of independent Envs.)

So the Envs may not be needed to be freed manually by the user.

But the models built on top of the Envs may required to be cleaned. Otherwise the only option for the user is to abandon the existing julia REPL and start a new REPL, which is not very ideal.

A more relevant question is thus:

import JuMP, Gurobi
const S = 3;
const C = Dict{String, Any}("OutputFlag"=>0,"Threads"=>1);
env_vec = [Gurobi.Env(C) for _ = 1:S];
model_vec = map(JuMP.direct_model ∘ Gurobi.Optimizer, env_vec)
for model = model_vec
    JuMP.@variables(model, ...) # add many
    JuMP.@constraints(model, ...) # add many
end; # now the model_vec is huge in memory

how to reclaim memory pertaining to model_vec? The associated memory both in julia and in Gurobi’s C-libs.

You need to ensure that there are no more references to the models in model_vec, nor any of their component parts like variables or constraints. Then, run the GC.

An example would be:

using JuMP, Gurobi
const NUM_SCENARIOS = 3
const GUROBI_CONFIG = Dict{String,Any}("OutputFlag" => 0, "Threads" => 1)
grb_environments = [Gurobi.Env(GUROBI_CONFIG) for s in 1:NUM_SCENARIOS]
models = [direct_model(Gurobi.Optimizer(env)) for env in grb_environments]
for model in models
    @variables(model, ...)
    @constraints(model, ...)
end
models = nothing
GC.gc()

The fundamental issue is that Julia is a GC’d language. We don’t have a strong control around when objects are finalised.

Gurobipy does this: Python Env Class Reference - Gurobi Optimizer Reference Manual

with gp.Env() as env:
    with gp.Model(env=env) as model:
        model.optimize()

so it has explicit control over when the model and env is GC’d.

I guess we could do something like:

Gurobi.Env() do env
    Gurobi.Optimizer(env) do optimizer
        direct_model(optimizer)
        # ...
        return
    end
    return
end

but it doesn’t compose very well with JuMP.Model or your list of models.