Share
Embed
JRuby 10: Improve your Ruby with JIT, GC, and Libraries from the JVM
JRuby 10: Improve your Ruby with JIT, GC, and Libraries from the JVM
Presented at Krakow Ruby User Group on May 13, 2025
headius
May 13, 2025
Transcript
84 slides
-
Hello! • Charles Oliver Nutter • @headius(@mastodon.social) • [email protected] •
JRuby developer since 2004 • Full-time JVM language advocate -
What is JRuby? • Ruby on the Java Virtual Machine
• Ruby implementation fi rst, JVM language second • Many bene fi ts from JVM ecosystem • Ruby code should "just work" • Different extension API, no forking, parallel threads • Thousands of production users, 18 years of real-world use -
What Is Important? • Usability: compatibility, startup time, warmup time
• Runtime features: GC, JIT, monitoring, pro fi ling, concurrency • Platform features: mobile, server, desktop, integration, deployment • Performance: straight line, scaling, parallelism, resource usage • Different applications needs different capabilities -
JRuby Install • Install a Java runtime • Java 21+
required for JRuby 10 • Latest Java recommended (Java 24) • Install JRuby • Recommended: Ruby installer, system package, Docker image • Download tarball/zip, Windows installer, build yourself -
$ java -version openjdk version "24" 2025-03-18 OpenJDK Runtime Environment
(build 24+36-3646) OpenJDK 64-Bit Server VM (build 24+36-3646, mixed mode, sharing) $ ruby-install jruby-10.0.0.1 >>> Updating jruby versions ... >>> Installing jruby 10.0.0.1 into /Users/headius/.rubies/jruby-10.0.0.1 ... >>> Downloading https://repo1.maven.org/maven2/org/jruby/jruby-dist/10.0.0.1/jruby- dist-10.0.0.1-bin.tar.gz into /Users/headius/src ... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 32.1M 100 32.1M 0 0 7887k 0 0:00:04 0:00:04 --:--:-- 7887k >>> Verifying jruby-dist-10.0.0.1-bin.tar.gz ... >>> Extracting jruby-dist-10.0.0.1-bin.tar.gz to /Users/headius/src/jruby-10.0.0.1 ... >>> Installing jruby 10.0.0.1 ... >>> Symlinking bin/ruby to bin/jruby ... >>> Successfully installed jruby 10.0.0.1 into /Users/headius/.rubies/jruby-10.0.0.1 -
[] ~ $ irb >> RUBY_VERSION => "3.4.2" >> JRUBY_VERSION
=> "10.0.1.0-SNAPSHOT" >> runtime = java.lang.Runtime.runtime => #<Java::JavaLang::Runtime:0x64a896b0> >> runtime.available_processors => 8 >> runtime.free_memory => 91420584 >> -
Ruby Compatibility • JRuby 10 leaps to Ruby 3.4 •
Language and core specs: 98% passing • Pure-Ruby standard library shared with CRuby • JRuby support for most native stdlib • JRuby 9.4 supports Ruby 3.1 • Maintained until April 2026 -
A Whole New World • JVM ecosystem has tens of
thousands of libraries • Graphics, GUIs, servers, document formats, AI/LLM • One of the largest collections in the dev world • All available to JRuby users! • Easy integration into Ruby apps and code -
Example: JFreeChart • Chart-generating library with support for document output
• Easily used from JRuby • https://blog.headius.com/ 2025/05/3d-charts-and-more-with- jruby-and-jfreechart.html -
Dependencies • Jar fi le, like Gem fi le •
Like Bundler's Gem fi le • List of libraries and versions from Maven • lock_jars command • Like bundle install jar 'org.jfree:jfreechart:1.5.5' jar 'org.jfree:org.jfree.chart3d:2.1.0' $ lock_jars -- jar root dependencies -- org.jfree:jfreechart:1.5.5:compile org.jfree:org.jfree.chart3d:2.1.0:compile org.jfree:org.jfree.svg:5.0.6:compile org.jfree:org.jfree.pdf:2.0:compile Jars.lock updated -
Import or Set Constants Color = java.awt.Color Rectangle = java.awt.Rectangle
BufferedImage = java.awt.image.BufferedImage java_import java.awt.Color java_import java.awt.Rectangle java_import java.awt.image.BufferedImage java_import javax.imageio.ImageIO java_import org.jfree.chart3d.Chart3DFactory java_import org.jfree.chart3d.data.DefaultKeyedValues ... -
Load Data into Dataset require 'json' dataset = StandardCategoryDataset3D.new data
= JSON.load(File.read("data/app_monitoring_revenue.json")) data.each do |name, subset| values = DefaultKeyedValues.new subset.each { values.put(_1, _2) } dataset.add_series_as_row name, values end -
Create a 3D Bar Chart chart = Chart3DFactory.create_bar_chart( "Quarterly Revenues",
"Application & Performance Monitoring Companies", dataset, nil, "Quarter", "$million Revenues") chart.chart_box_color = Color.new(255, 255, 255, 127) chart.legend_anchor = LegendAnchor::BOTTOM_RIGHT -
Adjust Rendering plot = chart.plot plot.gridline_paint_for_values = Color::BLACK renderer =
plot.renderer item_label_generator = StandardCategoryItemLabelGenerator.new( StandardCategoryItemLabelGenerator::VALUE_TEMPLATE) item_selection = StandardKeyedValues3DItemSelection.new item_label_generator.item_selection = item_selection renderer.item_label_generator = item_label_generator -
Render to PNG width, height = 600, 500 category_chart_image =
BufferedImage.new(width, height, BufferedImage::TYPE_INT_RGB) category_chart_graphics = category_chart_image.create_graphics chart.draw(category_chart_graphics, Rectangle.new(width, height)) category_chart_file = File.open("category_chart.png", "w") ImageIO.write(category_chart_image, "PNG", category_chart_file.to_outputstream) -
Render to SVG and PDF require_jar 'org.jfree', 'org.jfree.svg', '5.0.6' java_import
org.jfree.svg.SVGGraphics2D svg_graphics = SVGGraphics2D.new(width, height) svg_graphics.defs_key_prefix = "jruby_charts" chart.element_hinting = true chart.draw(svg_graphics, Rectangle.new(width, height)) svg = svg_graphics.get_svg_element chart.id File.write("category_chart.svg", svg) require_jar 'org.jfree', 'org.jfree.pdf', '2.0.1' java_import org.jfree.pdf.PDFDocument pdf_doc = PDFDocument.new pdf_doc.title = "Application & Performance Monitoring Companies Revenue" pdf_doc.author = "Charles Oliver Nutter"; page = pdf_doc.create_page(Rectangle.new(612, 468)) pdf_graphics = page.graphics2D chart.draw(pdf_graphics, Rectangle.new(0, 0, 612, 468)) File.write("category_chart.pdf", pdf_doc.pdf_bytes) -
GUI Libraries • Swing, built into JDK • Clean, cross-platform,
easy to build simple UIs • Scalable Windowing Toolkit (Eclipse SWT) • Native widgets, WebKit browser component, rich ecosystem • JavaFX (via JRubyFX, github/jruby/jrubyfx) • Scene-based, vector drawing, event-driven modern UI library -
Glimmer • Glimmer GUI DSL • Multiple backends (SWT, GTK,
...) • JRuby + SWT is the most mature • JRuby makes cross-platform GUI much easier! • Works same everywhere • GUI libraries shipped with gem • https://github.com/AndyObtiva/glimmer -
JavaFX • Scene-based UI toolkit • Cross-platform including mobile •
JRubyFX provides a Ruby wrapper • FXML layout or widget scripting -
Purugin for Minecraft event(:player_egg_throw) do |e| e.hatching = true e.num_hatches
= 120 e.player.mesg "hatched" end -
Ruboto: JRuby on Android • ruboto.org, https://github.com/ruboto/ruboto • Actively used
for commercial projects today • Build interface with GUI builder, wire it up with Ruby code • Neglected a bit but being updated for JRuby 10 now! -
Ruboto IRB • IRB in JRuby on Android! • Plus
an editor and script runner • Not currently in the store, but we will republish soon! • Search for "Ruboto Core" and "Ruboto IRB" APKs -
JVM GC • Many options to tune JVM GCs •
Heap size: small or large? • Throughput: faster allocations or shorter pause times? • Working set: large in-memory or mostly new objects? • Many options in standard OpenJDK • Serial, Parallel, G1, ZGC, Shenandoah -
JVM JIT • HotSpot JIT is most widely deployed •
C1 "client" JIT: fast, simple optimizations • C2 "server" JIT: needs pro fi le data, heavy optimization • Both enabled with various "tiers" of JIT • Graal JIT: newer, more aggressive optimizations • OpenJ9 JIT (IBM JDK): of fl ine AOT, JIT servers, CRIU -
Monitoring and Pro fi ling • VisualVM: GUI console for
basic JVM monitoring • JDK Flight Recorder: always-on monitoring with pro fi ling options • Low-overhead 1% to 5%, built into OpenJDK • JDK Mission Control: GUI client for Flight Recorder data • https://adoptium.net/jmc/ -
Scaling Applications • Classic problem on CRuby/MRI • No concurrent
threads, so we need worker processes • Processes duplicate runtime state and waste resources • JRuby is a good solution • Multi-threaded single process runs your entire site • Single process with leading-edge GC uses resources better -
Baseline Rails App • Scaffolded "blog" application on PostgreSQL, Puma
• IBM VPC instance: 8 vCPU, 32GB • CRuby 3.2, 16 workers • JRuby 9.4: 16 threads • Database, siege benchmark driver on same instance -
Requests per second 0 450 900 1350 1800 60s siege
iteration 1 2 3 4 5 6 7 JRuby CRuby 3.2 CRuby 3.2 + YJIT -
requests per second (higher is better) 0rps 450rps 900rps 1350rps
1800rps 1,550rps 1,280rps 1,705rps JRuby CRuby CRuby + YJIT -
Memory • JRuby: 3.4GB RSS • JRuby with 300MB heap:
955MB RSS • JRuby G1: 1.6G • CRuby: 16x 103MB = 1.6GB • CRuby YJIT: 16x 125MB = 2GB -
RPS per MB of memory (16-way concurrency) 0rps/mb 0.45rps/mb 0.9rps/mb
1.35rps/mb 1.8rps/mb 0.775 rps/MB 0.8 rps/MB 1.72 rps/MB JRuby CRuby CRuby + YJIT -
RPS per MB of memory (160-way concurrency) 0rps 3.5rps 7rps
10.5rps 14rps 0.775 rps/MB 0.8 rps/MB 13.692 rps/MB JRuby CRuby CRuby + YJIT -
Optimizing Startup • First impressions are important! • JRuby has
always had much longer startup than CRuby • JVM must load, parse, optimize each time • Delaying parts of startup only helps "hello world" • Newer JDKs shipping improved startup features -
JRuby Compiler Pipeline Ruby (.rb) JIT Java Instructions (java bytecode)
Ruby Instructions (IR) parse interpret interpreter interpret C1 compile native code better native code java bytecode interpreter execute C2 compile Java Virtual Machine JRuby Internals -
JVM Startup Projects • Application Class Data Store (AppCDS) •
Pre-cache classes, methods, code, metadata across runs • Machine code caching (AOTCache) • AppCDS + execution pro fi les and native code • Coordinated restore at checkpoint (CRaC) • Capture entire process state and restore later (repeatedly) -
0s 0.35s 0.7s 1.05s 1.4s -e 1 0.17 0.712 0.847s
1.051s 1.001s 1.327s 0.064s CRuby 3.4 JRuby 9.4 JRuby 9.4 --dev JRuby 10 JRuby 10 --dev JRuby 10 --dev AOTCache JRuby on CRaC -
Optimizing Performance • JRuby depends on JVM for most optimization
• JVM has world-class JIT compilers, garbage collectors • Similar design to ZJIT, but with 30 years of work in it • JRuby itself has an IR, basic block-based interpreter and JIT • Similar design to ZJIT starting in JRuby 9.0 (2015) -
Pure Ruby red/black tree 0 20 40 60 80 red/black
bench iters/s 76.8 iters/sec 65.5 iters/sec 15.1 iters/sec CRuby 3.4 CRuby 3.4 YJIT JRuby 10 -
Better Extension Performance 0M 0.3M 0.6M 0.9M 1.2M small medium
large 0.13 0.29 1.1 0.06 0.11 0.72 MRI (oj) JRuby (oj) Millions of loads per second (higher is better) -
Optimizing Fibers • Fibers were not well-supported on older JVMs
• Only abstraction was a native thread, too heavy for 1000s of fi bers • OpenJDK Project Loom adds fi bers to JVM • "Virtual threads" are lightweight, but use same thread API • JRuby can match CRuby for count and perf of fi bers -
Create, resume, and fi nish 1000 fi bers 0 75
150 225 300 Java 8 Java 21 288 iters/s 49 iters/s -
Class#new Optimization • CRuby adding opt_new to optimize Class#new •
Using C code to allocate + initialize hurts performance • JRuby implemented this in 2016 • If Class#new is default, allocate and call #initialize inline • Allocation and initialize both inline back to caller -
0M 3.5M 7M 10.5M 14M Object.new allocations per second 0M
13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10 -
0M 75M 150M 225M 300M Object.new allocations per second 217.5M
13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10 🤯 -
1M 10M 100M 1000M 10000M Object.new allocations per second 217.5M
13M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10 -
1M 10M 100M Object.new allocations per second Foo.new allocations per
second 216M 217.5M 22.1M 13M 10.3M 10.3M Ruby 3.4 + YJIT Ruby 3.5 + opt_new JRuby 10 -
Alternative JITs • HotSpot JIT • Standard JIT compiler in
OpenJDK • Best balance of performance, reliability • Graal JIT • Part of GraalVM project • Advanced optimizations, sometimes unpredictable perf -
0M 750M 1500M 2250M 3000M Foo.new allocations per second 2,591.1M
216M JRuby 10 JRuby 10 + Graal 🤯 -
Data • New immutable struct type in Ruby 3.2 •
Compact storage • Special #new method to allocate and initialize together -
0M 1.75M 3.5M 5.25M 7M Bar.new(1, 2, 3) allocations per
second 6.2M 2.9M CRuby 3.4 + YJIT JRuby 10 -
Threads or Ractors? • Ractors are designed to bring concurrency
to Ruby • You must write Ractor-friendly code • High overhead crossing Ractor boundary • Threads in JRuby are already 100% concurrent • You must right Thread-friendly code • But zero overhead due to shared object • Thanks to Maciej Mensfeld for the benchmark -
if THREADS queues = CONCURRENT.times.map { Queue.new } threads =
CONCURRENT.times.map do |i| Thread.new do queue = queues[i] data_chunk = queue.pop data_chunk.each { |item| JSON.parse(item) } end end end -
result = Benchmark.measure do if THREADS CONCURRENT.times do |i| queues[i]
<< data[i] end threads.each(&:join) else # Linear without any threads data.each do |piece| piece.each { JSON.parse(_1) } end end end -
Threads vs Ractors 0 1.25 2.5 3.75 5 times faster
than linear 4.2x 1.2x 1.1x CRuby threads CRuby ractors JRuby threads -
Supporting JRuby • I work for you! • Helping Ruby
users with JRuby • Keep making JRuby better • Sponsorships from users • Every little bit helps! • JRuby and Ruby support • Migration, updates, performance work -
JRuby 10.x • Many more optimizations coming! • Now that
we are 3.4 compatible, I can work on fun stuff • If you fi nd something slower than CRuby, tell me! • More JDK features • AOTCache for startup, Panama for fast native calls • Upgrade JVM, your JRuby code runs faster! -
Thank You! • Charles Oliver Nutter • @headius • @mastodon.social
• .bsky.social • [email protected] • Help me keep working on JRuby!