わかる!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のようです。
Scala 2.8 と Apache Wicket で昼間のお仕事
昼間のお仕事のシステムをこんなアーキテクチャで作りました。
ようやくリリースできそうな雰囲気。追々成果をまとめます。
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がいました。どうやらEclipseのWTPがscopeを無視してすべての依存ライブラリをコピーしているから、のようです。
というわけで、とりあえずjavaee-web-apiをpomから削除すればいけます。ただし、JAX-RS以外のJavaEE APIがコンパイル時に使用できなくなる。。。はず。