わかる!JavaVM

JVM勉強会行ってきました。


わかる!JavaVM ― 2時間でわかる?JavaVM入門
http://atnd.org/events/5871


クラスローダーとスタックマシンとJVMバイトコードGCとかそんな話。


資料はきっとどこかにまとまると思うけど、とりあえずこのへん。(不完全)
http://www.slideshare.net/ashigeru/classloader
http://www.slideshare.net/nekop/classloader-leak-patterns
http://www.slideshare.net/kmizushima/java-4912958


@ashigeru さんのクラスローダーの解説は大変参考になった。開発中にありがちな謎のClassNotFoundExceptionとかClassCastExceptionの正体がわかった気がします。
@kimizu さんのバイトコードの話はガチだった。勉強不足でついていけなかったけど、講義としてぜひもっと聞きたい。
@skrb さんのスタックマシンの話と @asigeru さんのGCの話は、入門的な内容で非常に分かりやすかった。あのスライドはそのまま社内勉強会で使わせてもらいたい。
JBossの中の人 @nekop さんとOracleの中の人のクラスローダーがらみのLT2本。


という内容でした。
あとは、

  • ブログ書くまでが勉強会。
  • いつの間にか中の人になったOracleの人、LTでも懇親会でもすごく楽しそうだった。
  • Scalaにはわりとみんな興味ある。
  • プログラミング言語Javaについてはあんまり好意的じゃない。


という感じでした。
さて、夏休みだー。

Scalaのコンパイル時にStackOverflowErrorが出てしまう

EclipseとかでScalaソースコードを書いていると、たまーにうまくビルドできないことがあります。

Mavenでビルドしていると、こんな感じになります。

[ERROR] Caused by: java.lang.StackOverflowError
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4078)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4145)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4151)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3964)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4078)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4145)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4151)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3964)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4078)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4145)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4151)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3964)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4078)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4145)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4151)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed1(Typers.scala:3964)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typed(Typers.scala:4078)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4145)
[INFO]  at scala.tools.nsc.typechecker.Typers$Typer.typedQualifier(Typers.scala:4153)
.....

どうやら本当にstackのサイズが足らないようで、拡張してやるとうまくいきます。
Eclipseの場合は、eclipse.iniに -Xss1m と追加してやればOKです。
Mavenの場合は、Scalaコンパイラが独立したVMで起動するので、Maven起動時のVM引数ではなく、pom.xmlに記述します。

  <plugin>
    <groupId>org.scala-tools</groupId>
    <artifactId>maven-scala-plugin</artifactId>
    <executions>
        <execution>
        <id>compile</id>
        <goals>
            <goal>compile</goal>
        </goals>
        <phase>compile</phase>
        </execution>

        <execution>
        <id>test-compile</id>
        <goals>
            <goal>testCompile</goal>
        </goals>
        <phase>test-compile</phase>
        </execution>
    </executions>
    <configuration>
        <scalaVersion>2.8.0.final</scalaVersion>
        <jvmArgs><jvmArg>-Xss1m</jvmArg></jvmArgs>
    </configuration>
  </plugin>

なお、stackのデフォルトサイズは、あんまりはっきりとした情報源は見つかりませんでしたが、32bitのJVMではデフォルト256k、64bitでは1Mのようです。

ソースコードの1ステートメントが長すぎるんですかね・・・

LL Tiger

Scalaの話もあるってことで、LL Tigerに行ってきました。
@yuroyoro さんのLanguage Update以外でも、いろんなセッションでScalaの話が出てきて、ニヤニヤ。


あと @mootoh さんの"threadはプリミティブすぎる"ってのには納得。
直前のエントリで書いたように、Actorモデルってのに退化したようなイメージを感じるのだけれど、
それは抽象化されて制御が容易になった、と捉えたほうがいいのかも。
仕事で大量メール配信だったり、MQのデータ処理だったりで並列処理を書いていたけど、たぶん量的にも複雑さ的にも簡単だったからthreadで扱えたのかな、と思う。
それに、有限のリソース上での最適化された処理スピードに執着するからthreadにこだわらなきゃいけなくなってたわけで、これがサーバー台数やコア数が可変だったり、同時に動く他の処理とかで未知数な要素が多くなれば、メッセージパッシング方式のほうが格段に使いやすいのだろうなと思う。
この話を含め、エッジな人が考える5年後10年後のプログラミングモデルの展望を聞けたのは貴重でした。


仕事がらみでは、チームラボのトイレセンサシステムはマジ欲しい。
トイレのドアの開閉検知→どっかにデータおいてApacheから流す→firefoxアドオンとかchromeエクステンションで通知→さらにはcactiで負荷傾向の監視を!
このシステムさえ組めば、200人オーバーのフロアに個室3つという勤務先の劣悪なトイレ事情が改善され、不毛な待ち時間がなくなり業務効率が劇的に向上する。いや真剣に。

しかし発表者の皆様方、ほぼ全員Macだった。いいなぁ。

Scala 2.8のActorで使われるスレッド数

scala.actors.scheduler.ThreadPoolConfigの中に定義されていますが、CPUコア数×2スレッドです。

 val corePoolSize = getIntegerProp("actors.corePoolSize") match {
   case Some(i) if i > 0 => i
   case _ => {
     val byCores = rt.availableProcessors() * 2
     if (byCores > minNumThreads) byCores else minNumThreads
   }
 }

 val maxPoolSize = {
   val preMaxSize = getIntegerProp("actors.maxPoolSize") getOrElse 256
   if (preMaxSize >= corePoolSize) preMaxSize else corePoolSize
 }

厳密には、corePoolSizeがCPUコア数×2で、maxPoolSizeが256です。それぞれ、VM引数のactors.corePoolSizeとactors.maxPoolSizeで変更が可能です。が、後述するとおりスレッドプールの挙動的には、よっぽどのことが無い限りcorePoolSizeを超えるスレッドは生成されません。
というわけで、IO待ち(通信待ちとか)をするActorをたくさん動かしたい場合は、actors.corePoolSizeをでかくしたほうが効果的です。

内部的には、java.util.concurrent.ThreadPoolExecutorか、scala.actors.schedule.ForkJoinSchedulerが使われており、ThreadPoolConfigにて定義されているcorePoolSizeとmaxPoolSizeの値に基づいてスレッドプールが作成されます。
このExecutor/Schedulerはscala.actors.Reactorに定義されています。

 val sched = if (!ThreadPoolConfig.useForkJoin) {
   // default is non-daemon
   val workQueue = new LinkedBlockingQueue[Runnable]
   ExecutorScheduler(
     new ThreadPoolExecutor(ThreadPoolConfig.corePoolSize,
                            ThreadPoolConfig.maxPoolSize,
                            60000L,
                            TimeUnit.MILLISECONDS,
                            workQueue,
                            new ThreadPoolExecutor.CallerRunsPolicy))
 } else {
   // default is non-daemon, non-fair
   val s = new ForkJoinScheduler(ThreadPoolConfig.corePoolSize, ThreadPoolConfig.maxPoolSize, false, false)
   s.start()
   s
 }

このReactorはReactor traitのcompanion objectなので、Actorが使うスレッドプールは、すべてのActorを通じて1つだけになります。
ThreadPoolExecutorを使うのかForkJoinScheudlerを使うのかは、ThreadPoolConfig.useForkJoin で定義されていて、以下のような条件になります。

VM引数 actors.enableForkJoin がfalseならThreadPoolExecutor
VM引数 actors.enableForkJoin がtrueならForkJoinPool
VM引数が無くて Sun/Apple Java 1.6以上ならForkJoinPool
VM引数が無くて それ以外のJVMならThreadPoolExecutor

!propIsSetTo("actors.enableForkJoin", "false") &&
      (propIsSetTo("actors.enableForkJoin", "true") || {
        Debug.info(this+": java.version = "+javaVersion)
        Debug.info(this+": java.vm.vendor = "+javaVmVendor)
      
        // on IBM J9 1.6 do not use ForkJoinPool
        // XXX this all needs to go into Properties.
        isJavaAtLeast("1.6") && ((javaVmVendor contains "Sun") || (javaVmVendor contains "Apple"))
      })

ThreadPoolExecutorのほうは、コンストラクタで指定されているworkQueueのサイズに制限がないため、実行待ちのタスクの数(=Actorの数)がInteger.MAX _VALUEを超えるまでは全部キューに入って実行待ちとなります。つまり、corePoolSizeを超えるタスクを実行しようとしてもキューに入るだけで、このキューがあふれるほどのタスクを投入して初めてスレッドプールが拡張されます。

ForkJoinSchedulerのほうは、まだ見れてませんが、どうやら同じ挙動となるようです。
ソースコードJavaで書かれていて、↓から見れます。
http://lampsvn.epfl.ch/svn-repos/scala/scala/tags/R_2_8_0_final/src/forkjoin/scala/concurrent/forkjoin/

なんかActorモデルの並列処理って、スレッドを明示的にコントロールできないんで、Javaと比べて使いにくい気がします。退化したようなイメージ。
気軽に使えるのかもしれないけど、何が良いのか良くわからない。

Scala 2.8のtry...catchでfinallyに行かないことがある

Scala version 2.8.0.r22367-b20100620020114 で確認。

def exp : Int = {
  try {
    println("try")
    throw new Exception
  } catch {
    case _ => println("catch")
    return 1
  } finally {
    println("finally")
  }
}

として、expを呼び出すと。。。

try
catch
 Int = 1

となります。returnなんか明示的に呼ぶな、ということなんですが。そういう挙動みたいです。

JBoss Resteasy+Maven+Eclipse WTPの件

Mavenを使ってServletのようなJavaEE仕様を使う場合、pom.xmlに以下のdependencyを追加します。

<dependency>
  <groupId>javax</groupId>
  <artifactId>javaee-web-api</artifactId>
  <version>6.0</version>
  <scope>provided</scope>
</dependency>

たいていはTomcatやJettyなどのコンテナに載せると思うので、scopeはprovidedでOKです。

と、ここでJavaEE6のJAX-RS1.1を実装した、JBoss Resteasyを使う場合は注意が必要です。↓はResteasyのpom.xmlに書かれた依存関係ですが、ここにjaxrs-apiが含まれていて、javaee-web-apiのクラス定義と重複します

クラス定義は正しいのでコンパイルは通りますが、そのまま実行するとこんな例外が出ます。
Resteasyは、自前のjaxrs-apiじゃないとダメみたい。

java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/ws/rs/ext/RuntimeDelegate
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
	at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)

コンテナにデプロイされたファイルを見てみると、scopeをprovidedにしたはずのjavaee-web-apiがいました。どうやらEclipseWTPがscopeを無視してすべての依存ライブラリをコピーしているから、のようです。

というわけで、とりあえずjavaee-web-apiをpomから削除すればいけます。ただし、JAX-RS以外のJavaEE APIコンパイル時に使用できなくなる。。。はず。