Skip to content

Free up memory memory used by JRuby during teardown #8343

@laumacirule

Description

@laumacirule

Environment Information

  • JRuby version jruby 9.4.8.0 (3.1.4) 2024-07-02 4d41e55a67 OpenJDK 64-Bit Server VM 11.0.23+9 on 11.0.23+9 [arm64-darwin])

Actual Behavior
To allow garbage collection as soon as possible, we call the following methods to release memory once a JRuby plugin is shut down, but the base JVM process keeps running.

  def release_jruby_runtime_memory
    logger.info "Release JRuby runtime memory..."
    runtime = JRuby.runtime
    runtime.release_class_loader

    # Clear loaded features to allow sooner garbage collection of it.
    # JRuby runtime will not be garbage collected right away due to thread local soft references to JRuby ThreadContext objects.
    clear_loaded_features(runtime)

    # Clear boundMethods
    runtime.getBoundMethods.clear

    clear_concurrent_hash_map_fields(runtime)
    release_memory_of_large_objects(runtime)
  rescue => e
    logger.error "release_jruby_runtime_memory failed with #{e.class.name}: #{e.message}"
  end

  def clear_loaded_features(runtime)
    load_service = runtime.getLoadService
    load_service.getLoadedFeatures.clear

    f = load_service.java_class.declared_field("librarySearcher")
    f.accessible = true
    library_searcher = f.value(load_service)

    loaded_features_index_method = library_searcher.java_class.declared_method('getLoadedFeaturesIndex')
    loaded_features_index_method.accessible = true
    loaded_features_index = loaded_features_index_method.invoke(library_searcher)
    loaded_features_index.clear

    f = library_searcher.java_class.declared_field("loadedFeaturesIndex")
    f.accessible = true
    f.set_value(library_searcher, loaded_features_index.class.new)
  rescue => e
    logger.error "clear_loaded_features failed with #{e.class.name}: #{e.message}"
  end

  def clear_concurrent_hash_map_fields(runtime)
    %w(allModules constantNameInvalidators).each do |field_name|
      f = runtime.java_class.declared_field(field_name)
      f.accessible = true
      (value = f.value(runtime)).clear
      f.set_value(runtime, value.class.new)
    end
  rescue => e
    logger.error "clear_concurrent_hash_map_fields #{field_name} failed with #{e.class.name}: #{e.message}"
  end

  def release_memory_of_large_objects(runtime)
    {
      "symbolTable" => runtime.getSymbolTable.class.new(JRuby.runtime),
      "javaSupport" => runtime.loadJavaSupport
    }.each do |field_name, new_value|
      f = JRuby.runtime.java_class.declared_field(field_name)
      f.accessible = true
      f.set_value(runtime, new_value)
    end
  rescue => e
    logger.error "release_memory_of_large_objects #{field_name} failed with #{e.class.name}: #{e.message}"
  end

Expected Behavior

The above teardown actions could be added to the org.jruby.Ruby.teardown logic to fully free up the memory that JRuby used.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions