close


Summarized using AI

Optimizing JRuby 10

Charles Oliver Nutter • April 18, 2025 • Matsuyama, Ehime, Japan • Talk

In the talk "Optimizing JRuby 10" presented by Charles Nutter at RubyKaigi 2025, the speaker discusses the significant enhancements made in JRuby 10, which now supports Ruby 3.4 and Rails 8 compatibility. After years of feature catch-up, this release focuses on optimizations tailored to improve performance in JRuby applications.

Key points discussed include:

- JRuby 10 Release: Announcement of the JRuby 10 release, highlighting the achievement of Ruby 3.4 compatibility and the requirement for Java 21.

- Performance Optimizations: Several performance enhancements were made, particularly in startup time, warm-up time, and overall application performance across multiple cores.

- Startup Time Improvements: Techniques such as application class data sharing (App CDS) and machine code caching (Project Loom) were introduced to accelerate application startup times.

- Benchmarking Performance: The speaker compares the performance of JRuby against C Ruby, showcasing JRuby's ability to run benchmarks faster, particularly with certain data types and JSON parsing tasks.

- Concurrency Enhancements: The introduction of project Loom and lightweight virtual threads improve JRuby's handling of concurrency, enabling more efficient operations compared to traditional threads and ractors in C Ruby.

- Future Development: Charles emphasizes the need for community involvement and contributions to address ongoing challenges like optimizing keyword arguments and integrating future JDK features into JRuby.

He concludes by inviting developers to try JRuby, offer feedback, and collaborate on optimizing their Ruby applications for better performance in enterprise settings, ensuring that JRuby can effectively support high-performance Ruby applications across diverse environments, including Android and enterprise-level software.

Overall, the talk reveals the substantial advancements in JRuby's compatibility and performance, positioning it as a robust alternative for Ruby developers seeking Java platform capabilities.

Optimizing JRuby 10
Charles Oliver Nutter • Matsuyama, Ehime, Japan • Talk

Held on: April 18, 2025
Published: May 27, 2025

JRuby 10 is out now with Ruby 3.4 and Rails 8 compatibility! After years of catching up on features, we've finally been able to spend time on long-delayed optimizations. This talk will show some of the best examples, including real-world application performance, and teach you how to find and fix performance problems in your JRuby applications.

https://rubykaigi.org/2025/presentations/headius

RubyKaigi 2025

00:00:08 Okay — I'm excited you're all here. I'll get right into it; we have a lot to cover about JRuby 10 and some of the optimizations we're working on.
00:00:19 First, thanks to my friend Mufan: I want to promote RubyConf Taiwan 2025 combined with KUSKUP, the open-source conference in Taiwan. If you want to know more, find him — he'll tell you everything. I hope to see many of you there.
00:00:40 Konnichiwa — I'm very excited to be back here to present JRuby 10. Here's my basic contact information. I've been developing and maintaining JRuby for 20 years.
00:00:54 I've tried to bring the best of the Java platform to the Ruby world. Last year I'm listed as unemployed because Red Hat, which sponsored the project for 12 years, moved on. Right now I work for the Ruby community: I continue JRuby development full-time and fund the project through sponsorships and support contracts for organizations running critical production applications.
00:01:41 So far, sponsorship and support contracts have allowed me to maintain the project as a one-person core team, but I'm looking for new partnerships. If you can help the project or need help deploying or profiling code, please reach out. The big announcement this week is that we finally released JRuby 10 — our biggest release in many years. We've jumped to Ruby 3.4 compatibility and added thousands of new tests and assertions to improve compatibility and stability.
00:02:26 We now require a minimum of Java 21 so we can take advantage of modern JVM features; running on older Java versions made it too hard to utilize them. There's a lot of additional performance work planned for this year. "Optimizing JRuby" covers many topics: compatibility, startup and warm-up time (traditionally hard on the JVM), straight-line performance, and multi-core scalability. I'll go through each of these areas and show what we're doing in JRuby.
00:03:17 Tracking Ruby updates and maintaining compatibility is a challenging, ongoing project. The big leap to JRuby 10 (supporting Ruby 3.4) means we've finally caught up with CRuby again. On a chart of supported versions we sometimes fall behind, miss a release or two, and then catch up — that's the nature of balancing compatibility work against other development.
00:04:05 Another view is days of lag: historically JRuby has lagged CRuby by varying amounts, but the 3.4 block is the smallest — the closest we've ever been to being on schedule. We could have released JRuby 10 in December, but we finished more stabilization and testing to get as many green tests as possible. We're very excited to be caught up with CRuby.
00:04:58 All of this compatibility work is done by the JRuby team, with some external contributors. CRuby has a large list of contributors; JRuby is a much smaller team. Tom does a lot of parser and user-support work. My son, in the audience, is the project's third-largest committer — he implemented the new JRuby launcher and helped with some C/native code. We need more contributors who can test, implement features (in Ruby or otherwise), and help us keep up with Ruby releases.
00:06:06 What about Rails 8 compatibility? Generally, any pure-Ruby parts of Rails should work when JRuby supports the required Ruby version. Rails 8 requires Ruby 3.2; JRuby 10 supports Ruby 3.4, so Ruby-based parts of Rails ought to work. In practice, most of the work to support Rails is updating our ActiveRecord implementation: database adapters contain a lot of special-case code that must be translated to the Java database APIs. That's where most of the lag comes from. If you're familiar with ActiveRecord adapters, we'd love help keeping up on the JRuby side.
00:07:15 Startup is one of the first issues people notice with JRuby, and first impressions matter. We want JRuby to feel as close to CRuby as possible as a development experience. Unfortunately there are many steps to get JRuby running: code is parsed and loaded, the JVM runs it for a while, and eventually the JVM optimizes it to native code. That whole process takes time, so we've explored new JDK features to speed up startup and warm-up. JRuby's internal IR is similar to the zjit IR coming to CRuby, and the JVM itself uses a similar SSA compiler design. Our goal is to shorten the time from source to fast native code.
00:08:41 On the JVM side, we've been examining exciting projects. First is the Application Class Data Sharing (App CDS) feature, which arrived around Java 17. It allows the JVM to preload and pre-cache parsed code, metadata, methods, and class data so we don't have to reparse everything at startup. App CDS has been a primary source of recent startup improvements.
00:09:12 Looking further ahead, Java 24 includes previews of machine-code caching (Project Leaden) that provide an AOT cache of optimized native code. Ideally, when the JVM generates optimized native code we won't need to repeat that work on restart: we can save off the optimized code and resume quickly. There's also an unusual approach called Coordinated Restore at Checkpoint (Project CRaC), which can save an entire process's memory and execution state and restore it rapidly. CRaC currently only supports Linux and has limitations, but it can dramatically improve startup.
00:10:02 To compare: CRuby is optimized for developer happiness and startup speed, so it's very quick to get going. Plain JRuby 9.4 was noticeably slower. There are flags (for example, -d--dev) to reduce optimizations and improve developer experience, but JRuby 10 — using App CDS — brings the baseline much closer to CRuby. Combined with early AOT-cache experiments and CRaC, we are getting back under a second for simple startup cases. Many engineers across the JDK community are working on these technologies.
00:11:29 Now let's talk about straight-line performance: making Ruby code run fast. JRuby relies heavily on the JVM to do the heavy lifting. The JVM uses an SSA compiler design similar to zjit but with decades of development and many engineers improving it. JRuby has its own IR and basic-block compilation that we feed to the JVM, and we try to give the JVM enough information to optimize Ruby well. In a pure-Ruby red-black tree benchmark, CRuby 3.4 with YJIT could do about 15 iterations per second. YJIT improves CRuby a lot, but JRuby can still be faster on many pure-Ruby workloads because the JVM's optimizer is very powerful.
00:12:58 Native C extensions are a barrier to some optimizations on CRuby: the VM can't always optimize across C extension boundaries. JRuby can often do better because ports of extensions to Java can be inlined with Ruby code and optimized together by the JVM. For example, the OJ JSON parser port runs faster on JRuby because the JVM can optimize the combined code at runtime better than standalone C code.
00:14:03 Fibers have become increasingly important in the Ruby community, but older JVMs made them difficult to support efficiently. Previously, JRuby implemented fibers by wrapping full native threads, which are heavy and limited in number per process. Project Loom on OpenJDK brought lightweight fibers to the JVM as virtual threads. In a benchmark that spins up a thousand fibers and resumes them, the native-thread version achieved about 49 iterations per second; the virtual-thread implementation on the JVM is many times faster. With some additional JRuby work to reduce fiber-creation overhead, I expect JRuby fibers on the JVM to be much faster, likely faster than CRuby soon.
00:15:36 Aaron Patterson presented optimizations for class allocation (inlining allocate and initialize). JRuby implemented a similar optimization almost ten years ago: the JVM can inline allocate/initialize sequences and optimize them strongly. That helps make object allocation very cheap on the JVM. We run object-allocation microbenchmarks: CRuby 3.4 with YJIT does around 10.3 million allocations per second; Aaron's opt-new patch adds ~30% (about 13 million/s). JRuby 10 can be significantly faster because the JVM inlines allocations and makes them low-cost.
00:16:58 I was surprised by how fast allocations were, so I attached a JVM profiler to ensure objects were actually being created. The profiler confirmed the objects are real, but the JVM's optimizations (escape analysis, allocation elimination) make the allocation path extremely cheap. Even when initialize is written in Ruby, JRuby's allocation performance sees little impact. The JVM's excellent garbage collectors also reclaim those objects efficiently. Because we've inlined allocation and initialization logic, newer JVM JITs such as Graal (Growl/JIT work) can eliminate temporary allocations entirely, improving throughput even further.
00:18:43 Some Ruby language features also help. The 'data' feature introduced in Ruby 3.2 provides an immutable, compact- storage struct-like type with a special constructor that allocates and initializes in one step. On a small benchmark allocating a data instance with three fields, CRuby 3.4 with YJIT gets about 2.9 million allocations per second. JRuby runs this nearly three times faster because the JVM optimizes those compact objects effectively.
00:19:57 Another area is concurrency. People talk about ractors as a way to use multiple cores in Ruby, but writing ractor-friendly code is challenging. Ractors require sharable, often frozen objects, and crossing ractor boundaries has high overhead. That overhead can negate concurrency benefits. In JRuby, if your code is thread-safe, you can use real threads to utilize all cores without major application changes. Threads in JRuby allow you to pass objects between threads and exploit multi-core systems in a single process.
00:21:07 We benchmarked threads versus ractors for a JSON-parsing workload. Threads are spun up to wait on a queue; when they receive work they parse JSON. In CRuby, threads give slight improvements but often don't fully exploit cores due to locks and other bottlenecks. Ractors can slow things down because of the boundary-crossing overhead (though recent patches have improved this to about 1.3x). On my four-core machine, JRuby using real threads shows the expected scaling: all cores light up and you can maximize parallel processing in a single process.
00:22:58 This translates to Rails applications as well. In CRuby, you need multiple processes to get concurrency, which multiplies memory usage, duplicative work, and database round trips. With JRuby you can often run your entire site in a single process, using less memory and getting better requests-per-second per megabyte of memory. That crossover point can make JRuby more resource efficient for concurrent web workloads.
00:24:08 JRuby also gives opportunities to use Ruby anywhere Java runs. You can take your Rails apps and libraries and deploy them in Java shops, package them as single binaries, and ship them as commercial software. At the conference I spoke with people who packaged Rails apps and deployed them in Java organizations without anyone realizing they were Ruby. As an example, using Java Swing from JRuby you can build a cross-platform GUI: create a frame, add a button, set an action listener to run Ruby code on click, and display the window with just a small Ruby program. Projects like Shoes 4 (JRuby-based) and Glimmer (with SWT) provide richer UI toolkits on JRuby.
00:25:44 There are many other integrations: JRuby supports Minecraft plugins written in Ruby, Android applications via frameworks like Rub, and other embedded use cases. Commercial users use JRuby on Android for point-of-sale devices, ad terminals, and other applications. JRuby runs wherever Java runs, which is nearly everywhere.
00:26:34 Looking forward to JRuby's future (10.x maintenance releases over the next year), we have many optimizations planned now that this release is out. There is a long list of improvements we can make: for example, keyword-argument handling still allocates a hash in JRuby. The JVM can remove some of those allocations, but we should avoid allocating a whole hash where it's not needed. That's one of the first items I'll be working on. If you find a case where JRuby is slower than CRuby, please tell me — it's likely something I can fix.
00:27:21 We'll continue to explore JDK features such as AOT cache to improve startup, Project Panama to make optimized native calls from Ruby (speeding up FFI), and other JVM advances. Often, upgrading the JVM delivers better garbage collectors, improved JITs, and cool new features that make your Ruby code run faster without changes. I need the Ruby community's help to keep JRuby healthy: JRuby can solve many Ruby community performance problems and open Ruby to difficult enterprise environments. If JRuby disappeared, Ruby would be measurably damaged. This is an opportunity to expand Ruby into a larger world of high-performance, high-concurrency processing while using the Ruby we love.
00:28:44 Please try JRuby and let me know how I can help: scaling, profiling, and optimizing your code. If you do use JRuby, let's talk about partnerships so I can help you run your application better and you can help the JRuby community stay healthy. I'll have JRuby office hours after this talk; come by with problems and I'll try to help. I brought some American IPAs and I also have stickers and business cards for anyone who wants them. Thank you.

RubyKaigi is a yearly conference held in Japan and features 70 talks from various speakers, including keynotes by Mari Imaizumi, Ivo Anjo, and Yukihiro "Matz" Matsumoto.

70
Talks
74
Speakers
3
Days
21
Editions
Sub Hall #rubykaigiB

Full Schedule

Day 1 - Wednesday, Apr 16

09:00 - 10:00 Door Open
Image
Keynote: Ruby Taught Me About Encoding Under the Hood
Mari Imaizumi
Main Hall #rubykaigiA
Image
Make Parsers Compatible Using Automata Learning
Hiroya Fujinami
Main Hall #rubykaigiA
Image
Bringing Linux pidfd to Ruby
Maciej Mensfeld
Sub Hall #rubykaigiB
Image
Introducing Type Guard to Steep
Takeshi KOMIYA
Pearls Room #rubykaigiC
Image
The Evolution of the CRuby Build System
Yuta Saito
Main Hall #rubykaigiA
Image
A side gig for RuboCop, the Bookworm code crawler
David T. Crosby
Sub Hall #rubykaigiB
Image
Continuation is to be continued
Masayuki Mizuno
Pearls Room #rubykaigiC
12:20 - 14:00 Lunch Break
Image
Deoptimization: How YJIT Speeds Up Ruby by Slowing Down
Takashi Kokubun
Main Hall #rubykaigiA
Image
Empowering Developers with HTML-Aware ERB Tooling
Marco Roth
Sub Hall #rubykaigiB
Image
Goodbye fat gem 2025
Kouhei Sutou
Pearls Room #rubykaigiC
Image
Ruby's Line Breaks
Yuichiro Kaneko
Main Hall #rubykaigiA
Image
SDB: Efficient Ruby Stack Scanning Without the GVL
Mike Yang
Sub Hall #rubykaigiB
Image
Automatically generating types by running tests
Takumi Shotoku
Pearls Room #rubykaigiC
15:10 - 15:40 Afternoon Break
Image
State of Namespace
Satoshi Tagomori
Main Hall #rubykaigiA
Image
Embracing Ruby magic: Statically analyzing DSLs
Vinícius Stock
Sub Hall #rubykaigiB
Image
50.000 processed records per second: a CRuby & JRuby story
Cristian Planas
Pearls Room #rubykaigiC
Image
mruby/c and data-flow programming for small devices
Kazuaki Tanaka
Main Hall #rubykaigiA
Image
Parsing and generating SQLite's SQL dialect with Ruby
Stephen Margheim
Sub Hall #rubykaigiB
Image
dRuby on Browser Again!
Shigeru Nakajima, Yoh Osaki
Pearls Room #rubykaigiC
Image
TRICK 2025: Episode I
Yusuke Endoh, Yukihiro "Matz" Matsumoto, Tomoya Ishida, Koichiro Eto, Shinichiro Hamaji, Yutuka Hara, Sun Park, Darre...
Main Hall #rubykaigiA

Day 2 - Thursday, Apr 17

Image
Keynote: Performance Bugs and Low-level Ruby Observability APIs
Ivo Anjo
Main Hall #rubykaigiA
Image
Dissecting and Reconstructing Ruby Syntactic Structures
Yudai Takada
Main Hall #rubykaigiA
Image
Benchmark and profile every single change
Daisuke Aritomo
Sub Hall #rubykaigiB
Image
Running JavaScript within Ruby
Kengo Hamasaki
Pearls Room #rubykaigiC
Image
ZJIT: Building a Next Generation Ruby JIT
Maxime Chevalier-Boisvert
Main Hall #rubykaigiA
Image
Keeping Secrets: Lessons Learned From Securing GitHub
Dennis Pacewicz, Wei Lin Ngo
Sub Hall #rubykaigiB
Image
Improvement of REXML and speed up using StringScanner
NAITOH Jun
Pearls Room #rubykaigiC
12:20 - 14:00 Lunch Break
Image
Writing Ruby Scripts with TypeProf
Yusuke Endoh
Main Hall #rubykaigiA
Image
Demystifying Ruby Debuggers: A Deep Dive into Internals
Dmitry Pogrebnoy
Sub Hall #rubykaigiB
Image
How to make the Groovebox
Yuya Fujiwara
Pearls Room #rubykaigiC
Image
MicroRuby: True Microcontroller Ruby
HASUMI Hitoshi
Main Hall #rubykaigiA
Image
Bazel for Ruby
Alex Rodionov
Sub Hall #rubykaigiB
Image
RuboCop: Modularity and AST Insights
Koichi ITO
Pearls Room #rubykaigiC
15:10 - 15:40 Afternoon Break
Image
Speeding up Class#new
Aaron Patterson
Main Hall #rubykaigiA
Image
You Can Save Lives With End-to-end Encryption in Ruby
Ryo Kajiwara
Sub Hall #rubykaigiB
Image
Write you a Barrier - Automatic Insertion of Write Barriers
Martin J. Dürst, Joichiro Okoshi
Pearls Room #rubykaigiC
Image
Making TCPSocket.new 'Happy'!
Misaki Shioi
Main Hall #rubykaigiA
Image
From C extension to pure C: Migrating RBS
Alexander Momchilov
Sub Hall #rubykaigiB
Image
The Implementations of Advanced LR Parser Algorithm
Junichi Kobayashi
Pearls Room #rubykaigiC
Image
Lightning Talks
Shunsuke Michii, Maki Kawahara, White-Green, Yudai Takada, Chris Hasiński, Taketo Takashima, Kazuhiro NISHIYAMA, Haya...
Main Hall #rubykaigiA

Day 3 - Friday, Apr 18

Image
Ruby Committers and the World
Ruby Committers
Main Hall #rubykaigiA
Image
API for docs
Soutaro Matsumoto
Main Hall #rubykaigiA
Image
Improving my own Ruby
Soichiro Isshiki
Sub Hall #rubykaigiB
Image
Running ruby.wasm on Pure Ruby Wasm Runtime
Kondo Uchio
Pearls Room #rubykaigiC
Image
Eliminating Unnecessary Implicit Allocations
Jeremy Evans
Main Hall #rubykaigiA
Image
A taxonomy of Ruby calls
Alan Wu
Sub Hall #rubykaigiB
Image
The Ruby One-Binary Tool, Enhanced with Kompo
ahogappa
Pearls Room #rubykaigiC
12:20 - 14:00 Lunch Break
Image
Toward Ractor local GC
Koichi Sasada
Main Hall #rubykaigiA
Image
Inline RBS comments for seamless type checking with Sorbet
Alexandre Terrasa
Sub Hall #rubykaigiB
Image
Road to Go gem
Go Sueyoshi
Pearls Room #rubykaigiC
Image
Analyzing Ruby Code in IRB
Tomoya Ishida
Main Hall #rubykaigiA
Image
Optimizing JRuby 10
Charles Oliver Nutter
Sub Hall #rubykaigiB
Image
Porting PicoRuby to Another Microcontroller: ESP32
Yuhei Okazaki
Pearls Room #rubykaigiC
15:10 - 15:40 Afternoon Break
Image
Modular Garbage Collectors in Ruby
Peter Zhu
Main Hall #rubykaigiA
Image
The Challenges of Building sigstore-ruby
Samuel Giddins
Sub Hall #rubykaigiB
Image
On-the-fly Suggestions of Rewriting Method Deprecations
Masato Ohba
Pearls Room #rubykaigiC
Image
Keynote: Programming Language for AI age
Yukihiro "Matz" Matsumoto
Main Hall #rubykaigiA

Similar Talks that share the same topics:

Explore all talks recorded at RubyKaigi 2025
Image
Image
Image
Image
Image
Image
Image
Image