2018: The Year of JRuby
Charles Oliver Nutter
@headius
Hello!
• Charles Oliver Nutter
• headius@headius.com, @headius
• JVM language advocate at Red Hat
• 20-year JVM veteran, 12 years on
JRuby
• Excited to be back in Kyiv!
The JRuby Guys
• Shout out to my buddy Tom Enebo
• Just presented at RubyKaigi in
Sendai, Japan
• We've worked together on JRuby for
over 12 years!
Beer For You!
• I am a beer lover!
• I brought some American craft
beers to give away!
• Try running something on JRuby,
show me, and take your pick:
• Bender: oatmeal brown ale
• Furious: dark American IPA
• Foggy Geezer: New England IPA
What is JRuby
• It's just Ruby!
• Ruby 2.5 compatible, if something's broken tell us
• Supports pure-Ruby gems, many extensions
• We want to be a Ruby first!
• It's a JVM language
• Full access to the power of the JVM platform!
Ruby!
• JRuby 9.2 is now Ruby 2.5 compatible
• Modulo a few things we couldn't finish
• You won't notice unless you use some weird edge features
• Primary focus is always on compatibility and experience
• Performance comes along later
Compatibility
JRuby 9.2
• Ruby 2.5, of course
• Better support for non-ASCII identifiers
• Method names, constants, symbols...
• Keyword arguments optimizations
• Preparing for new optimizations and JVMs
Supporting Ruby Versions
• Checklist based off CRuby's NEWS file
• Update our copy of CRuby's tests and make them pass
• Same process for ruby/spec, our own tests, and key libraries
New Feature? You Can Help!
• New features are great opportunities to contribute!
• Learn more about how Ruby and JRuby work!
• Help us keep up with Ruby development!
• Profit!
• We are always standing by on IRC, Gitter, Twitter to help you
Library Compatibility
• We also run library tests
• Rails, Rake, RubyGems, ...
• Core Ruby tests will never cover 100% of users
• Rails in particular is a key target
Performance
JRuby Architecture
• Mixed-mode runtime
• Interpreter runs for a while, gathering information
• Just-in-time (JIT) compiler compiles hot code to JVM
• JVM turns that into optimized native code
• Heavy dependence on JVM to optimize well
• Newer JVM JITs showing great promise!
JRuby
JVM
Client (C1)
Native JIT
Server (C2)
Native JIT
Interpreter Bytecode JIT
Bytecode
Interpreter
CPU
Microbenching
• Very fun to show off, see improve
• Practically useless
• Like judging a person by how much they can bench press
• JRuby has won microbenchmarks for years, never faster on Rails
• Easier to isolate specific measurements
• Great for exploring new runtimes and tech
bench_aref
• Testing calls to ary[n]
• Reduced overhead in JRuby 9.2
• String#[] needs to store $~
• We have to track that just in case ary is a String
• JRuby 9.1 deoptimized more than necessary
require 'benchmark/ips'
@a = [1]
def foo(a)
a[0]
end
Benchmark.ips do |x|
x.time = 10
x.warmup = 15
x.report("Instantiation") {
a = @a
i = 0; while i < 1_000_000; foo(a); i += 1; end
}
end
bench_aref iterations per second (higher is better)
0 iter/s
10 iter/s
20 iter/s
30 iter/s
40 iter/s
JDK10
JRuby 9.1 JRuby 9.2
39.132
30.248
InvokeDynamic
• JVM support for dynamic invocation
• Let the JVM see through all the dynamic bits of Ruby
• Added in Java 7, with much input and testing from JRuby
• Steadily improving performance, reducing overhead
• -Xcompile.invokedynamic
• May be default soon!
bench_aref iterations per second (higher is better)
0 iter/s
22.5 iter/s
45 iter/s
67.5 iter/s
90 iter/s
JDK10 JDK10 with Indy
JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2
89.169
39.132
68.122
30.248
Graal
• New JVM native JIT written in Java
• Faster evolution
• More advanced optimization
• Plugs into JDK9+ via command line flags
• Shipped with JDK10...try it today!
bench_aref iterations per second (higher is better)
0 iter/s
45 iter/s
90 iter/s
135 iter/s
180 iter/s
JDK10 JDK10 with Indy JDK10 Graal with Indy
JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2
172.541
89.169
39.132
100.401
68.122
30.248
bench_mandelbrot
• Generate a text Mandelbrot fractal
• See? Useful!
• Test of numeric performance
• Heavy reliance on JVM to optimize
• Graal is especially good to us here
bench_mandelbrot.rb
def mandelbrot(size)
sum = 0
byte_acc = 0
bit_num = 0
y = 0
while y < size
ci = (2.0*y/size)-1.0
x = 0
while x < size
zrzr = zr = 0.0
zizi = zi = 0.0
cr = (2.0*x/size)-1.5
escape = 0b1
z = 0
while z < 50
bench_mandelbrot total execution time (lower is better)
0s
1s
2s
3s
4s
CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal
0.139s
1.33s
2.95s
3.5s3.57s
bench_mandelbrot total execution time (lower is better)
0s
0.036s
0.071s
0.107s
0.142s
JRuby Indy Graal TruffleRuby
0.142s0.139s
bench_red_black
• Pure-Ruby red/black tree implementation
• Good for comparing object-heavy, indirection-heavy code
• Lots of blocks, instance variable accesses
• Benchmark creates tree, searches it, modifies it, clears it
bench_red_black.rb
def rbt_bm
n = 100_000
a1 = []; n.times { a1 << rand(999_999) }
a2 = []; n.times { a2 << rand(999_999) }
start = Time.now
tree = RedBlackTree.new
n.times {|i| tree.add(i) }
n.times { tree.delete(tree.root) }
tree = RedBlackTree.new
a1.each {|e| tree.add(e) }
a2.each {|e| tree.search(e) }
tree.inorder_walk {|key| key + 1 }
tree.reverse_inorder_walk {|key| key + 1 }
n.times { tree.minimum }
n.times { tree.maximum }
return Time.now - start
end
bench_red_black total execution time (lower is better)
0s
0.325s
0.65s
0.975s
1.3s
CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal
0.526s
0.431s
1.171s1.143s
1.222s
JRuby on Rails
A Long, Hard Journey
• JRuby first ran Rails in 2006
• Almost as long as Rails has existed!
• Thousands of JRoR instances around the world
• JRuby 9000, Ruby 2.4, 2.5 work slowed down Rails support
• Rails 5.0 not supported for at least a year
• ActiveRecord suffered the most
ActiveRecord JDBC
• ActiveRecord atop Java DataBase Connectivity API
• No C extensions, no -devel packages, no headers or compiling
• Rebooted last year to reduce maintenance hassle
• 1:1 match with Rails versions (e.g. Rails 5.1 = ARJDBC 51.x)
• SQLite3, MySQL/Maria, PostgreSQL
• Sharing 90%+ of code with Rails
Example: MySQL support
• Old adapter: 1600+ lines of Ruby code
• 50.0 adapter: 284 lines
• More can be removed, moved into Rails
• Reduced Java/JDBC code as well
• 51.0 adapter: only 6 lines had to change!
Rails 5.1.6
actioncable: 139 runs, 733 assertions, 10 failures, 2 errors
actionpack: 3063 runs, 14947 assertions, 2 failures, 0 errors
actionmailer: 204 runs, 456 assertions, 0 failures, 0 errors
actionview: 1957 runs, 4303 assertions, 3 failures, 4 errors
activejob: 137 runs, 302 assertions, 0 failures, 0 errors
activemodel: 713 runs, 2017 assertions, 0 failures, 0 errors
activerecord: 4991 runs, 13902 assertions, 0 failures, 0 errors
activesupport: 3671 runs, 760486 assertions, 14 failures, 0 errors
railties: 40 runs, 73 assertions, 0 failures, 1 errors
99.995% pass
Failure:
TimeWithZoneTest#test_minus_with_time_precision [activesupport/
test/core_ext/time_with_zone_test.rb:340]:
Expected: 86399.999999998
Actual: 86399.99999999799
😩
Rails 5.2.0
actioncable: something broken bootstrapping
actionpack: 3148 runs, 15832 assertions, 1 failures, 0 errors
actionmailer: 204 runs, 457 assertions, 0 failures, 0 errors
actionview: 1990 runs, 4395 assertions, 4 failures, 4 errors
activejob: 173 runs, 401 assertions, 0 failures, 0 errors
activemodel: 803 runs, 2231 assertions, 0 failures, 0 errors
activerecord: 5226 runs, 14665 assertions, 8 failures, 6 errors
activesupport: 4135 runs, 762864 assertions, 17 failures, 2 errors
railties: uses fork()
Rails 5.2 Status
• Tests are running pretty well
• Scaffolded apps work fine
• SQLite3 looks good
• MySQL, PostgreSQL need updates
JRuby on Rails Performance
ActiveRecord Performance
• Rails apps live and die by ActiveRecord
• Largest CPU consumer by far
• Heavy object churn, GC overhead
• Create, read, and update measurements
• If delete is your bottleneck, we need to talk
• CRuby 2.5.1 vs JRuby 9.2 on JDK10
ActiveRecord
create operations per second
0
40
80
120
160
JRuby JRuby Indy JRuby Graal CRuby
157.233
144.092140.449135.135
ActiveRecord
find(id) operations per second
0
1250
2500
3750
5000
JRuby JRuby Indy JRuby Graal CRuby
3,940
4,672
4,999
3,937
ActiveRecord
select operations per second
0
1050
2100
3150
4200
JRuby JRuby Indy JRuby Graal CRuby
3,125
3,703
4,132
2,403
ActiveRecord
find_all operations per second
0
525
1050
1575
2100
JRuby JRuby Indy JRuby Graal CRuby
1,597
2,016
1,908
1,677
ActiveRecord
update operations per second
0
1750
3500
5250
7000
JRuby JRuby Indy JRuby Graal CRuby
2,604
6,250
6,944
4,000
Scaling Rails
• Classic problem on MRI
• No concurrent threads, so we need processes
• Processes inevitably duplicate runtime state
• Much effort and lots of money wasted
• JRuby is a great answer!
• Multi-threaded single process runs your whole site
Measuring Rails Performance
• Requests per second
• ActiveRecord operations per second
• CRuby versus JRuby, various configurations
Requests Per Second
• Rails 5.1.6, Postgresql 10, scaffolded view
• 4k requests to warm up, then measure every 10k
• EC2 c4.xlarge: 4 vCPUs, 7.5GB
• Bench, database, and app on same instance
Requests per second, full stack scaffolded read on Postgresql
0
325
650
975
1300
JRuby JDK8 Indy JRuby JDK10 Indy CRuby
Requestspersecond
0
325
650
975
1300
Requests over time
10k 20k 30k 40k 50k 60k 70k 80k 90k 100k
CRuby 2.5 JRuby JDK8 JRuby JDK8 Indy JRuby JDK10
JRuby JDK10 Indy JRuby JDK10 Graal Indy CRuby 2.6 JIT
JRuby on Rails Memory
• Single instance is much bigger, 400-500MB versus 50MB
• Ten CRuby processes = 500MB
• Ten JRuby threads = 400-500MB
• May need to tell JVM a memory cap
• For 100-way or 1000-way...you do the math
JRuby is the fastest way
to run Rails applications.
Summary
JRuby Flags
• -Xcompile.invokedynamic
• Enable the use of JVM's InvokeDynamic feature
• Faster straight-line perf, maybe slower startup/warmup
• -Xfixnum.cache=false
• Disable caching -256..256 Fixnum objects to help Graal
• --dev for improved startup time (disables optimization)
Getting Graal
• Download JDK10, it's in there
• -XX:+UnlockExperimentalVMOptions

-XX:+EnableJVMCI

-XX:+UseJVMCICompiler
• Put in JAVA_OPTS or prefix with -J at JRuby CLI
• Download "GraalVM" as your JDK
• Commercial product, but maybe that's your thing
JDK10 Warnings
• JDK9 introduced stricter encapsulation
• We poke through that encapsulation to support Ruby features
• You'll see warnings...they're harmless, but we'll deal with them
Thank You!
Charles Oliver Nutter
headius@headius.com
@headius
http://jruby.org
https://github.com/jruby/jruby

The Year of JRuby - RubyC 2018

  • 1.
    2018: The Yearof JRuby Charles Oliver Nutter @headius
  • 2.
    Hello! • Charles OliverNutter • headius@headius.com, @headius • JVM language advocate at Red Hat • 20-year JVM veteran, 12 years on JRuby • Excited to be back in Kyiv!
  • 3.
    The JRuby Guys •Shout out to my buddy Tom Enebo • Just presented at RubyKaigi in Sendai, Japan • We've worked together on JRuby for over 12 years!
  • 4.
    Beer For You! •I am a beer lover! • I brought some American craft beers to give away! • Try running something on JRuby, show me, and take your pick: • Bender: oatmeal brown ale • Furious: dark American IPA • Foggy Geezer: New England IPA
  • 7.
    What is JRuby •It's just Ruby! • Ruby 2.5 compatible, if something's broken tell us • Supports pure-Ruby gems, many extensions • We want to be a Ruby first! • It's a JVM language • Full access to the power of the JVM platform!
  • 8.
    Ruby! • JRuby 9.2is now Ruby 2.5 compatible • Modulo a few things we couldn't finish • You won't notice unless you use some weird edge features • Primary focus is always on compatibility and experience • Performance comes along later
  • 9.
  • 10.
    JRuby 9.2 • Ruby2.5, of course • Better support for non-ASCII identifiers • Method names, constants, symbols... • Keyword arguments optimizations • Preparing for new optimizations and JVMs
  • 11.
    Supporting Ruby Versions •Checklist based off CRuby's NEWS file • Update our copy of CRuby's tests and make them pass • Same process for ruby/spec, our own tests, and key libraries
  • 12.
    New Feature? YouCan Help! • New features are great opportunities to contribute! • Learn more about how Ruby and JRuby work! • Help us keep up with Ruby development! • Profit! • We are always standing by on IRC, Gitter, Twitter to help you
  • 13.
    Library Compatibility • Wealso run library tests • Rails, Rake, RubyGems, ... • Core Ruby tests will never cover 100% of users • Rails in particular is a key target
  • 14.
  • 15.
    JRuby Architecture • Mixed-moderuntime • Interpreter runs for a while, gathering information • Just-in-time (JIT) compiler compiles hot code to JVM • JVM turns that into optimized native code • Heavy dependence on JVM to optimize well • Newer JVM JITs showing great promise!
  • 16.
    JRuby JVM Client (C1) Native JIT Server(C2) Native JIT Interpreter Bytecode JIT Bytecode Interpreter CPU
  • 17.
    Microbenching • Very funto show off, see improve • Practically useless • Like judging a person by how much they can bench press • JRuby has won microbenchmarks for years, never faster on Rails • Easier to isolate specific measurements • Great for exploring new runtimes and tech
  • 18.
    bench_aref • Testing callsto ary[n] • Reduced overhead in JRuby 9.2 • String#[] needs to store $~ • We have to track that just in case ary is a String • JRuby 9.1 deoptimized more than necessary
  • 19.
    require 'benchmark/ips' @a =[1] def foo(a) a[0] end Benchmark.ips do |x| x.time = 10 x.warmup = 15 x.report("Instantiation") { a = @a i = 0; while i < 1_000_000; foo(a); i += 1; end } end
  • 20.
    bench_aref iterations persecond (higher is better) 0 iter/s 10 iter/s 20 iter/s 30 iter/s 40 iter/s JDK10 JRuby 9.1 JRuby 9.2 39.132 30.248
  • 21.
    InvokeDynamic • JVM supportfor dynamic invocation • Let the JVM see through all the dynamic bits of Ruby • Added in Java 7, with much input and testing from JRuby • Steadily improving performance, reducing overhead • -Xcompile.invokedynamic • May be default soon!
  • 22.
    bench_aref iterations persecond (higher is better) 0 iter/s 22.5 iter/s 45 iter/s 67.5 iter/s 90 iter/s JDK10 JDK10 with Indy JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 89.169 39.132 68.122 30.248
  • 23.
    Graal • New JVMnative JIT written in Java • Faster evolution • More advanced optimization • Plugs into JDK9+ via command line flags • Shipped with JDK10...try it today!
  • 24.
    bench_aref iterations persecond (higher is better) 0 iter/s 45 iter/s 90 iter/s 135 iter/s 180 iter/s JDK10 JDK10 with Indy JDK10 Graal with Indy JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 JRuby 9.1 JRuby 9.2 172.541 89.169 39.132 100.401 68.122 30.248
  • 25.
    bench_mandelbrot • Generate atext Mandelbrot fractal • See? Useful! • Test of numeric performance • Heavy reliance on JVM to optimize • Graal is especially good to us here
  • 26.
    bench_mandelbrot.rb def mandelbrot(size) sum =0 byte_acc = 0 bit_num = 0 y = 0 while y < size ci = (2.0*y/size)-1.0 x = 0 while x < size zrzr = zr = 0.0 zizi = zi = 0.0 cr = (2.0*x/size)-1.5 escape = 0b1 z = 0 while z < 50
  • 27.
    bench_mandelbrot total executiontime (lower is better) 0s 1s 2s 3s 4s CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal 0.139s 1.33s 2.95s 3.5s3.57s
  • 28.
    bench_mandelbrot total executiontime (lower is better) 0s 0.036s 0.071s 0.107s 0.142s JRuby Indy Graal TruffleRuby 0.142s0.139s
  • 29.
    bench_red_black • Pure-Ruby red/blacktree implementation • Good for comparing object-heavy, indirection-heavy code • Lots of blocks, instance variable accesses • Benchmark creates tree, searches it, modifies it, clears it
  • 30.
    bench_red_black.rb def rbt_bm n =100_000 a1 = []; n.times { a1 << rand(999_999) } a2 = []; n.times { a2 << rand(999_999) } start = Time.now tree = RedBlackTree.new n.times {|i| tree.add(i) } n.times { tree.delete(tree.root) } tree = RedBlackTree.new a1.each {|e| tree.add(e) } a2.each {|e| tree.search(e) } tree.inorder_walk {|key| key + 1 } tree.reverse_inorder_walk {|key| key + 1 } n.times { tree.minimum } n.times { tree.maximum } return Time.now - start end
  • 31.
    bench_red_black total executiontime (lower is better) 0s 0.325s 0.65s 0.975s 1.3s CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy JRuby Indy Graal 0.526s 0.431s 1.171s1.143s 1.222s
  • 32.
  • 33.
    A Long, HardJourney • JRuby first ran Rails in 2006 • Almost as long as Rails has existed! • Thousands of JRoR instances around the world • JRuby 9000, Ruby 2.4, 2.5 work slowed down Rails support • Rails 5.0 not supported for at least a year • ActiveRecord suffered the most
  • 34.
    ActiveRecord JDBC • ActiveRecordatop Java DataBase Connectivity API • No C extensions, no -devel packages, no headers or compiling • Rebooted last year to reduce maintenance hassle • 1:1 match with Rails versions (e.g. Rails 5.1 = ARJDBC 51.x) • SQLite3, MySQL/Maria, PostgreSQL • Sharing 90%+ of code with Rails
  • 35.
    Example: MySQL support •Old adapter: 1600+ lines of Ruby code • 50.0 adapter: 284 lines • More can be removed, moved into Rails • Reduced Java/JDBC code as well • 51.0 adapter: only 6 lines had to change!
  • 37.
    Rails 5.1.6 actioncable: 139runs, 733 assertions, 10 failures, 2 errors actionpack: 3063 runs, 14947 assertions, 2 failures, 0 errors actionmailer: 204 runs, 456 assertions, 0 failures, 0 errors actionview: 1957 runs, 4303 assertions, 3 failures, 4 errors activejob: 137 runs, 302 assertions, 0 failures, 0 errors activemodel: 713 runs, 2017 assertions, 0 failures, 0 errors activerecord: 4991 runs, 13902 assertions, 0 failures, 0 errors activesupport: 3671 runs, 760486 assertions, 14 failures, 0 errors railties: 40 runs, 73 assertions, 0 failures, 1 errors 99.995% pass
  • 38.
  • 39.
    Rails 5.2.0 actioncable: somethingbroken bootstrapping actionpack: 3148 runs, 15832 assertions, 1 failures, 0 errors actionmailer: 204 runs, 457 assertions, 0 failures, 0 errors actionview: 1990 runs, 4395 assertions, 4 failures, 4 errors activejob: 173 runs, 401 assertions, 0 failures, 0 errors activemodel: 803 runs, 2231 assertions, 0 failures, 0 errors activerecord: 5226 runs, 14665 assertions, 8 failures, 6 errors activesupport: 4135 runs, 762864 assertions, 17 failures, 2 errors railties: uses fork()
  • 40.
    Rails 5.2 Status •Tests are running pretty well • Scaffolded apps work fine • SQLite3 looks good • MySQL, PostgreSQL need updates
  • 41.
    JRuby on RailsPerformance
  • 42.
    ActiveRecord Performance • Railsapps live and die by ActiveRecord • Largest CPU consumer by far • Heavy object churn, GC overhead • Create, read, and update measurements • If delete is your bottleneck, we need to talk • CRuby 2.5.1 vs JRuby 9.2 on JDK10
  • 43.
    ActiveRecord create operations persecond 0 40 80 120 160 JRuby JRuby Indy JRuby Graal CRuby 157.233 144.092140.449135.135
  • 44.
    ActiveRecord find(id) operations persecond 0 1250 2500 3750 5000 JRuby JRuby Indy JRuby Graal CRuby 3,940 4,672 4,999 3,937
  • 45.
    ActiveRecord select operations persecond 0 1050 2100 3150 4200 JRuby JRuby Indy JRuby Graal CRuby 3,125 3,703 4,132 2,403
  • 46.
    ActiveRecord find_all operations persecond 0 525 1050 1575 2100 JRuby JRuby Indy JRuby Graal CRuby 1,597 2,016 1,908 1,677
  • 47.
    ActiveRecord update operations persecond 0 1750 3500 5250 7000 JRuby JRuby Indy JRuby Graal CRuby 2,604 6,250 6,944 4,000
  • 48.
    Scaling Rails • Classicproblem on MRI • No concurrent threads, so we need processes • Processes inevitably duplicate runtime state • Much effort and lots of money wasted • JRuby is a great answer! • Multi-threaded single process runs your whole site
  • 49.
    Measuring Rails Performance •Requests per second • ActiveRecord operations per second • CRuby versus JRuby, various configurations
  • 50.
    Requests Per Second •Rails 5.1.6, Postgresql 10, scaffolded view • 4k requests to warm up, then measure every 10k • EC2 c4.xlarge: 4 vCPUs, 7.5GB • Bench, database, and app on same instance
  • 51.
    Requests per second,full stack scaffolded read on Postgresql 0 325 650 975 1300 JRuby JDK8 Indy JRuby JDK10 Indy CRuby
  • 52.
    Requestspersecond 0 325 650 975 1300 Requests over time 10k20k 30k 40k 50k 60k 70k 80k 90k 100k CRuby 2.5 JRuby JDK8 JRuby JDK8 Indy JRuby JDK10 JRuby JDK10 Indy JRuby JDK10 Graal Indy CRuby 2.6 JIT
  • 53.
    JRuby on RailsMemory • Single instance is much bigger, 400-500MB versus 50MB • Ten CRuby processes = 500MB • Ten JRuby threads = 400-500MB • May need to tell JVM a memory cap • For 100-way or 1000-way...you do the math
  • 54.
    JRuby is thefastest way to run Rails applications.
  • 55.
  • 57.
    JRuby Flags • -Xcompile.invokedynamic •Enable the use of JVM's InvokeDynamic feature • Faster straight-line perf, maybe slower startup/warmup • -Xfixnum.cache=false • Disable caching -256..256 Fixnum objects to help Graal • --dev for improved startup time (disables optimization)
  • 58.
    Getting Graal • DownloadJDK10, it's in there • -XX:+UnlockExperimentalVMOptions
 -XX:+EnableJVMCI
 -XX:+UseJVMCICompiler • Put in JAVA_OPTS or prefix with -J at JRuby CLI • Download "GraalVM" as your JDK • Commercial product, but maybe that's your thing
  • 59.
    JDK10 Warnings • JDK9introduced stricter encapsulation • We poke through that encapsulation to support Ruby features • You'll see warnings...they're harmless, but we'll deal with them
  • 61.
    Thank You! Charles OliverNutter headius@headius.com @headius http://jruby.org https://github.com/jruby/jruby