OK, here goes a reorganization of the code that looks a bit simpler:

def try_analyze *words
  analyzer = IO.popen( 'cat', IO::RDWR )
  res = []

  t = Thread.new(  (IO::for_fd analyzer.fileno),words.dup){|fd,ary|
    ary.each{|w|
      STDERR.putc '.'[0]
      fd.puts w
    }
    fd.close_write rescue nil
  }
  words.each do
    STDERR.putc 8
    res.push analyzer.gets
  end
  analyzer.close rescue nil # hopefully prevents zombie hordes
  res
end

1.upto(10000){|_|
  try_analyze *%w(a b c d e f g h i)
  STDERR.puts _
}

The downside here is that the code relies on the text analyzer to
produce the same number of lines it was sent.

Now the code fails on both JRuby and MRI in first iteration. The
IO::for_fd call was added specifically to make the earlier example
with swapped thread roles work on JRuby.

Removing the IO::for_fd call makes the code work in MRI but it still
fails within few iterations on JRuby, sometimes throwing a nice null
pointer exception.

java.lang.NullPointerException
        at org.jruby.util.io.OpenFile.finalize(OpenFile.java:241)
        at org.jruby.util.io.OpenFile.cleanup(OpenFile.java:226)
        at org.jruby.RubyIO.close2(RubyIO.java:1684)
        at org.jruby.RubyIO.close(RubyIO.java:1662)
        at org.jruby.RubyIOInvoker$close_method_0_0.call(Unknown Source)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:123)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.call(CallSite.java:333)
        at ruby.Users.hramrach.testt2.rescue_2$RUBY$__rescue__(testt2.rb)
        at ruby.Users.hramrach.testt2.method__0$RUBY$try_analyze(testt2.rb:16)
        at ruby.Users.hramrach.testt2Invokermethod__0$RUBY$try_analyzeOpt.call(Unknown
Source)
        at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:134)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:111)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.call(CallSite.java:282)
        at ruby.Users.hramrach.testt2.block_3$RUBY$__block__(testt2.rb:21)
        at ruby.Users.hramrach.testt2BlockCallback$block_3$RUBY$__block__xx1.call(Unknown
Source)
        at org.jruby.runtime.CompiledBlock.yield(CompiledBlock.java:100)
        at org.jruby.runtime.Block.yield(Block.java:100)
        at org.jruby.RubyInteger.upto(RubyInteger.java:122)
        at org.jruby.RubyIntegerInvoker$upto_method_1_0.call(Unknown Source)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.cacheAndCall(CallSite.java:159)
        at org.jruby.runtime.CallSite$InlineCachingCallSite.callIter(CallSite.java:415)
        at ruby.Users.hramrach.testt2.__file__(testt2.rb:20)
        at ruby.Users.hramrach.testt2.__file__(testt2.rb)
        at ruby.Users.hramrach.testt2.load(testt2.rb)
        at org.jruby.Ruby.runScript(Ruby.java:541)
        at org.jruby.Ruby.runNormally(Ruby.java:454)
        at org.jruby.Ruby.runFromMain(Ruby.java:327)
        at org.jruby.Main.run(Main.java:194)
        at org.jruby.Main.run(Main.java:91)
        at org.jruby.Main.main(Main.java:82)

Thanks

Michal