Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JRuby Everywhere! Desktop, Server, and Mobile

JRuby Everywhere! Desktop, Server, and Mobile

With JRuby, you can build cross-platform desktop apps, scalable server apps, and Android mobile apps. Charles Oliver Nutter will show you how to make it happen — and have fun along the way!

Presented June 13, 2025 at Baltic Ruby in Riga, Latvia

Avatar for headius

headius

June 13, 2025
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Hello! • Charles Oliver Nutter • @headius(@mastodon.social) • [email protected]

    Full-time JRuby dev since 2006 • Founder of Headius Enterprises • "The JRuby Company"
  2. Thank You! • eazyBI is a JRuby user! • World-class

    reporting and business intelligence • Integration with the whole Atlassian stack • I'm here because of them! • Sponsor of Baltic Ruby • Sponsor of my trip to Riga
  3. 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" • JVM-based extensions (no need for C build tools) • Parallel threading (no need to fork) • Thousands of production users, 18 years of real-world use
  4. Leading the Way! • JRuby has already solved many Ruby

    challenges! • YJIT/ZJIT: JRuby added JIT in 2008 • Deoptimization, saving JIT code: JVMs already can do this • Class.new optimization: since 2015 • Namespaces for isolation: always! • We move fast and help solve Ruby's problems!
  5. World of Opportunities • Large enterprises on JVM • Java,

    Kotlin, Scala, Clojure • Mobile platforms built on Android • TVs, POS, control terminals • Desktop applications • 68% of of fi ce work done on desktops
  6. 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
  7. $ 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
  8. $ 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
  9. [] ~ $ irb >> RUBY_VERSION => "3.4.2" >> JRUBY_VERSION

    => "10.0.1.0" >> runtime = java.lang.Runtime.runtime => #<Java::JavaLang::Runtime:0x64a896b0> >> runtime.available_processors => 8 >> runtime.free_memory => 91420584
  10. Ruby Compatibility • JRuby 10 supports 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
  11. 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
  12. Example: 3D Bar Chart with 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
  13. Dependencies • Jar fi le, like Gem fi le •

    Like Bundler's Gem fi le • Maven "coordinates" • 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 Jar fi le
  14. java_import org.jfree.chart3d.data.StandardCategoryDataset3D java_import org.jfree.chart3d.data.DefaultKeyedValues ... require 'json' data = JSON.load(File.read("data/app_monitoring_revenue.json"))

    dataset = StandardCategoryDataset3D.new data.each do |name, subset| values = DefaultKeyedValues.new subset.each { values.put(_1, _2) } dataset.add_series_as_row name, values end
  15. java_import org.jfree.chart3d.data.StandardCategoryDataset3D java_import org.jfree.chart3d.data.DefaultKeyedValues ... require 'json' data = JSON.load(File.read("data/app_monitoring_revenue.json"))

    dataset = StandardCategoryDataset3D.new data.each do |name, subset| values = DefaultKeyedValues.new subset.each { values.put(_1, _2) } dataset.add_series_as_row name, values end
  16. java_import org.jfree.chart3d.data.StandardCategoryDataset3D java_import org.jfree.chart3d.data.DefaultKeyedValues ... require 'json' data = JSON.load(File.read("data/app_monitoring_revenue.json"))

    dataset = StandardCategoryDataset3D.new data.each do |name, subset| values = DefaultKeyedValues.new subset.each { values.put(_1, _2) } dataset.add_series_as_row name, values end
  17. java_import org.jfree.chart3d.data.StandardCategoryDataset3D java_import org.jfree.chart3d.data.DefaultKeyedValues ... require 'json' data = JSON.load(File.read("data/app_monitoring_revenue.json"))

    dataset = StandardCategoryDataset3D.new data.each do |name, subset| values = DefaultKeyedValues.new subset.each { values.put(_1, _2) } dataset.add_series_as_row name, values end
  18. 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
  19. Generate 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)
  20. Generate 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)
  21. Generate 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)
  22. Generate 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)
  23. Render to SVG and PDF 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) 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)
  24. 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
  25. 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
  26. JavaFX • Scene-based UI toolkit • Cross-platform including mobile •

    JRubyFX provides a Ruby wrapper • FXML layout or widget scripting • https://github.com/jruby/jrubyfx
  27. 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!
  28. 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
  29. 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
  30. 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
  31. 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/
  32. 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)
  33. JRuby Architecture 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
  34. 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
  35. 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
  36. 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
  37. 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 🤯
  38. 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
  39. 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
  40. 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
  41. respond_to? • Commonly used for duck-typing • obj.to_str if respond_to?(:to_str)

    • Literal symbol form is by far the most common • Currently compiled as a call to respond_to? • respond_to? then does a method lookup every time • Can we do better?
  42. respond_to? :foo invoke respond_to? :foo respond_to? method cached? look up

    respond_to? method invoke respond_to? method look up :foo cache respond_to? method return true or false is :foo implemented?
  43. respond_to? :foo invoke respond_to? :foo respond_to? method cached? look up

    respond_to? method invoke respond_to? method look up :foo cache respond_to? method return result is :foo implemented? respond_to? :foo result cached? cache respond_to? :foo
  44. 0M 20M 40M 60M 80M foo if respond_to?(:foo) 70.8M 22.3M

    16.8M CRuby 3.4 + YJIT JRuby 10 JRuby 10.next
  45. 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
  46. 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
  47. Requests per second 0 450 900 1350 1800 60s siege

    iteration 1 2 3 4 5 6 7 JRuby CRuby CRuby + YJIT
  48. requests per second (higher is better) 0rps 450rps 900rps 1350rps

    1800rps 1,705rps 1,550rps 1,280rps CRuby CRuby + YJIT JRuby
  49. Memory • JRuby tuned with 300MB heap: 955MB RSS •

    Additional users only require a little more memory • CRuby single-user: 103MB RSS • Additional users eventually need additional processes • Even perfect Copy-On-Write still duplicates live data, JIT, GC
  50. 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
  51. 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
  52. 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
  53. JRuby 10.x • Many more optimizations coming! • Now that

    we are 3.4 compatible, we can work on fun stuff • If you fi nd something that's slow, let us! • More JDK features • JVM features to improve startup, fast native library integration • Upgrade JVM, your JRuby code runs faster!
  54. JRuby and You • Building an app? Try JRuby! •

    bundle install, run on Puma • Try out some JVM libraries • Library author? Test on JRuby! • We to support your project! • Let's talk!
  55. JRuby Development • Funded by Headius Enterprises LLC • JRuby

    support: updates, bugs, security • JVM, Ruby support: pro fi ling, optimization • Partner with us! • User support • GitHub sponsorships • Try JRuby out, fi le issues, push PRs
  56. Thank You! • Charles Oliver Nutter • @headius • @mastodon.social

    • .bsky.social • [email protected] • Stickers on the sticker table! • Help me keep working on JRuby!