by Kangqing Yu (Simon)
School of Computer Science
Carleton University
16 May 2018
BSc degree in Computer Science from University of Surrey, UK (2014)
BSc degree in Information Management and System from DUFE, China (Same year)
Software Developer at The ai Corporation, UK (2014-2015)
Software Developer at Lifecycle Software, UK (2015-2017)
Full-stack Rails Developer at MicroMetrics, Ottawa, Canada (2017 Summer breaks)
Currently a MSc student at School of Computer Science, Carleton University
Still Ruby, but run on JVM
Interpreter implemented in Java
Tightly Integrated with Java with two-way access
Open sourced project, started in 2002 by Charles Oliver Nutter (Headius)
Aim to be compatible with MRI Ruby (Currently support 2.3)
Corporates don't prefer Rails
Rails has no explicit build step (code not compiled)
Great for managed service and multi-tenancy
But not ideal for enterprise and single-tenancy
jRuby programming
Install and compiler options
Java integration
Multi-threading
Memory management
jRuby Applications
jRuby on Rails
Further applications
rvm install jruby #latest version 9.1.13.0
rvm use jruby
jruby version | MRI ruby version | Latest Java version supported |
---|---|---|
9.1.X | 2.3 | 9.0 |
9.0.X | 2.2 | 8.0 |
1.7.X | 1.9.3 | 8.0 |
Download at official site: http://jruby.org/download
Use RVM
A Head Of Time Compilation (AOT)
#will output bytecode foo/bar/test.class
jrubyc foo/bar/test.rb
java -cp .:/path/to/jruby.jar foo.bar.test
Just-In-Time Compilation (JIT)
#JIT compile source code
jruby -S foo/bar/test.rb
jRuby runtime options:
invokedynamics
compile
jit
thread.pool
jruby --properties
JVM runtime options:
Heap space
Profiling
Set JRUBY_OPTS environment variable
export JRUBY_OPTS="-X<property>=<value>"
Command Line Argument
jruby -J-Djruby.<property>=<value> -S foo.rb
In .jrubyrc file (after version 1.6.5)
<property>=<value>
Scripting Java from jRuby
Embedding Ruby in Java
Generating Java classes in jRuby
Java extension
require 'java'
Load bootstrap classes:
Load additional classes:
require 'path/to/mycode.jar'
$CLASSPATH << "target/classes"
Use gems like jbundler, jar-dependencies to handle complex java dependencies
Java: org.foo.department.Widget
Ruby: Java::OrgFooDepartment::Widget
Full-qualified class name
Importing into a constant
java_import java.util.ArrayList
list = ArrayList.new
Importing into current namespace
module MyApp
include_package 'org.apache.logging.log4j'
Logger = LogManager.getLogger('MyApp')
end
Camel case <-> Snake case
java.lang.System.currentTimeMillis
java.lang.System.current_time_millis
Bean convensions
x.getSomething becomes x.something
x.setSomething(newValue) becomes x.something = new_value
x.isSomething becomes x.something?
Additional Java methods
java_class
java_kind_of? #the same as instanceof operator
java_object
java_send #based on reflection
java_method
java_alias
require 'java'
class HelloThread < java.lang.Thread
def run
puts 'hello world'
end
end
HelloThread.new.start
Note: use field_accessor to access protected fields
require 'java'
class HelloThread
# This is optional as jruby supports duck-typing
include java.lang.Runnable
def run
puts "hello world"
end
end
java.lang.Thread.new(HelloThread.new).start
jRuby supports duck-typing
However, it is still necessary in some cases (see following)
Note:
public interface Balloon {
void pop();
}
public interface Bubble {
void pop();
}
public class Child{
public void give(Bubble bubble){
System.out.println("Thanks for the bubble.");
bubble.pop();
}
public void give(Balloon balloon){
System.out.println("Thanks for the balloon.");
balloon.pop();
}
}
require 'java'
class MyBalloon
#required as jruby runtime will
#use this to infer the type information
include Java::Ballon
def pop
puts 'Oh No!!!'
end
end
child = Java::Child.new
child.give(MylarBalloon.new)
Java:
jRuby:
button = new javax.swing.JButton("Press me!");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
((javax.swing.JButton) e.getSource()).setText("You did it!");
}
});
button = javax.swing.JButton.new "Press me!"
button.add_action_listener do |event|
event.source.text = "You did it!"
end
begin
java.lang.Integer.parse_int("asdf")
rescue java.lang.NumberFormatException => e
puts "Failed to parse integer: #{e.message}"
puts e.print_stack_trace
end
Catch exceptions
Raise exceptions
raise java.lang.IllegalArgumentException.new("Bad param")
Include Enumerable module in Ruby by default
ni = java.net.NetworkInterface.networkInterfaces
puts ni.class.ancestors
#=> [#<Class:01x7e666f>, Java::JavaUtil::Enumeration, Enumerable,
# Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy,
# JavaProxyMethods, Object, Java, Kernel]
java.net.NetworkInterface.networkInterfaces.each {|i| puts i }
Allocate new Java enumeration class
# Equivalent to Java's bytes = new byte[1024];
bytes = Java::byte[1024].new
list = java.util.ArrayList[1024].new
map = java.util.HashMap[1024].new
[1,2,3].to_java
["a","b","c"].to_java(:string)
[1, 2, 3.5].to_java Java::double
java_arry.to_a
java_map.to_hash
java_set.to_set
bytes = 'a string'.to_java_bytes
string = String.from_java_bytes bytes
# works for InputStreams, OutputStreams, and NIO Channels
io = java_input_stream.to_io
# also to_outputstream and to_channel
stream = io.to_inputstream
Ruby Type | Java Type | Literal |
---|---|---|
String | java.lang.String | "foo" |
Fixnum | java.lang.Long, short, long, int, byte, char | 1 |
Float | java.lang.Double, java.lang.Float, float, double | 1.0 |
TrueClass, FalseClass | java.lang.Boolean | true, false |
Bignum | java.math.BigInteger | 1 << 128 |
NilClass | null | nil |
Constructor signature
# Get the the three-integer constructor for this class
construct = JavaClass.java_class.constructor(Java::int, Java::int, Java::int)
object = construct.new_instance(0xa4, 0x00, 0x00)
Method signature
java_import java.util.ArrayList
list = ArrayList.new
list.java_send :add, [Java::int, java.lang.Object], 0, 'foo'
add = list.java_method :add, [Java::int, java.lang.Object]
add.call(0, 'foo')
class ArrayList
java_alias :simple_add, :add, [Java::int, java.lang.Object]
end
In 1.8.7, all threads map to a single native thread
In 2.0.0, each thread maps to a native OS thread (with GIL)
In jRuby, Ruby thread map to java.lang.Thread in Java (real-threading, with NO GIL) 😃
def bench_mark(n)
start = Time.now
1.upto(n).map do |n|
Thread.new { sleep(0.5) }
end.each(&:join)
Time.now - start
end
IO operations
Number of threads
Running time
def bench_mark(n)
start = Time.now
1.upto(n).map do |n|
Thread.new { fib(32) }
end.each(&:join)
Time.now - start
end
def fib(n)
n < 2 ? n : fib(n-1) + fib(n-2)
end
Arithmetic operations
Number of threads
Running time
Don't do it, if possible
If do it, don's share data
If share data, don's share mutable data
If share mutable data, make sure it is thread-safty
Rule of thumb:
Synchronized keyword
obj.synchronized do
obj.wait 1000
end
Most operations in jruby are implemented thread-safely
Some gems might not be thread-safe e.g. dragonfly, json-schema
Checkout java.util.concurrent package and concurrent-ruby gem
Mark and Sweep GC
Generational GC in JVM
Young generation && old generation
Major GC run && minor GC run
GCs are triggered lazily 😥
Some tips:
Get rid of unused objects early
Set heap space manually by setting:
jruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m -J-server
Do not enable ObjectSpace
Running Rails app in thread-safe mode
C extension alternatives
Debugging
Dependency management
Deployment options
Background jobs
#Gemfile
gem 'activerecord-jdbcmysql-adapter'# for mysql
gem 'activerecord-jdbcsqlite3-adapter'# for sqlite
gem 'activerecord-jdbcpostgresql-adapter'# for postgres
Currently support Rails 5.1 (except for postgres)
Check out https://github.com/jruby/activerecord-jdbc-adapter/issues/780
pg -> activerecord-jdbcpostgresql-adaptor
memcached -> jruby-memcached
RMagick -> RMagick4J
therubyracer -> therubyrhino
...etc.
Use Bundler gem for managing gem dependencies
#Jarfile
# Custom repos which are not Maven Central
repository :aicorporation, "LOCAL_REPO_URL"
jar 'com.fasterxml.jackson.module:jackson-module-mrbean', '2.4.3'
jar 'com.fasterxml.jackson.core:jackson-core', '2.4.3'
jar 'com.fasterxml.jackson.core:jackson-databind', '2.4.3'
#in Gemfile
platform :jruby do
gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.7'
end
platform :ruby do
gem 'pg', '~> 0.17.1'
end
Use jbundler gem for managing jar depedencies
jruby-debug (not jRuby 9k compatible)
Gems byebug and pry-byebug are MRI only
These gems can be used for jRuby
# add the --debug flag to your ruby/jruby command
def index
binding.pry
@posts = Post.all
end
Traditional Rails Servers (Rack-based)
Puma
Trinidad (Tomcat-based)
TorqueBox (JBoss AS-based)
Mizuno, Jetty-Rails (Jetty-based)
War file deployment (J2EE-based)
Tomcat
Jetty
JBoss
GlassFish
WebLogic
WebSphere
Asynchronous web servers (non-blocking)
Thick & WildWeb (based on Netty NIO)
Jubilee (based on Vert.x)
package cz.wildweb.example;
import static cz.wildweb.api.Wildweb.*;
public class Main {
public static void main(String[] args) {
get("/", (req, res) -> { res.close("Welcome"); });
startServer(8080);
}
}
Add warbler gem
jruby -S gem install warbler
Generate config file and configure warble (optional)
# generate config file in config/warble.rb
warble config
Compile assets input public folder
jruby -S rake assets:precompile
Generate war file
# at root of rails project
warble
Rails is based on Rack interface (as middleware)
J2EE app servers are compliant with Java Servlet API
Warbler depends on jruby-rack gem that 'translate' the Rack-interface used in Rails into Java Servlet API
Rails Rack jruby-rack Java Servlet J2EE App Server
J2EE App
Rack App
// in web.xml file
<listener>
<listener-class>org.jruby.rack.rails.RailsServletContextListener</listener-class>
</listener>
config/warble.rb
config/web.xml.erb
# only these four features supported atm
config.features = %w(gemjar executable runnable compiled)
# Application directories to be included in the webapp.
config.dirs = %w(app config db lib log script vendor tmp)
# Public ROOT mapping
config.public.root = ''
# Value of RAILS_ENV for the webapp -- default as shown below
config.webxml.rails.env = ENV['RAILS_ENV'] || 'production'
# Control the pool of Rails runtimes.
config.webxml.jruby.min.runtimes = 2
config.webxml.jruby.max.runtimes = 4
Thread-based workers
On top of jruby-rack (runs in J2EE container)
Support Resque, Deplayed::Job,Navvy natively
Custom worker can be provided
mach_hare gem
jruby client for RabbitMQ
Similar to the bunny gem in MRI
//in web.xml file
<context-param>
<param-name>jruby.worker</param-name>
<param-value>resque</param-value>
</context-param>
<context-param>
<param-name>QUEUES</param-name>
<param-value>mails,posts</param-value>
</context-param>
<context-param>
<param-name>INTERVAL</param-name>
<param-value>2.5</param-value>
</context-param>
<listener>
<listener-class>org.kares.jruby.rack.WorkerContextListener</listener-class>
</listener>
For Rails 2.x, you need to install jruby-openssl gem
jruby -S gem install jruby-openssl
Don't forget JCE Policy Files
Some jars in jBundler may not be packaged correctly in war file
#in config/warble.rb
config.java_libs += FileList["lib/java/*.jar"]
You might need this jar manny in some cases
#in Jarfile
jar 'org.bouncycastle:bcprov-jdk15on', '1.47'
Java Swing GUI
Spring MVC
JNDI
JMS
LDAP
.etc
jRuby Wiki page
jRuby Cookbook by Justin Edelson
Deploying with jRuby by Joe Kutner
My contact details:
simonykq@gmail.com
kangqingyu@cmail.carleton.ca
Questions?