Effective Akkaにコンパニオンオブジェクトのファクトリメソッドを使ってPropsのインスタンスを作ることでメモリリークに対処できる、といった記述があってピンと来なかったので試してみた。
リークするサンプル
package leak.sample
import java.lang.management.ManagementFactory
import akka.actor.{Actor, ActorSystem, Props}
object FactoryActorApp extends App {
val system = ActorSystem()
println("Creating instances.")
for (i <- 1 to 100000) {
system.actorOf(Props[SourceFActor]) ! "foo"
}
val pid = ManagementFactory.getRuntimeMXBean.getName.split('@').head
println("Created instances.")
println(s"jmap -histo:live ${pid}")
}
class SourceFActor extends Actor {
val i = 1
def receive = {
case "foo" =>
context.system.actorOf(Props(new TargetNActor(i)))
context.stop(self)
}
}
object TargetFActor {
def props(n: Int) = Props(classOf[TargetFActor], n)
}
class TargetFActor(n: Int) extends Actor {
def receive = {
case "bar" => println(n)
}
}
これを実行してコンソールから以下のコマンドをたたいてオブジェクトの数を確認する。
(findのところはunixではgrepとかにする。)
> jmap -histo:live 4024 | find "sample"
14: 100000 2400000 leak.sample.SourceNActor
15: 100000 2400000 leak.sample.TargetNActor
16: 100000 1600000 leak.sample.SourceNActor$$anonfun$receive$1
17: 100000 1600000 leak.sample.SourceNActor$$anonfun$receive$1$$anonfun$applyOrElse$1
18: 100000 1600000 leak.sample.TargetNActor$$anonfun$receive$2
344: 1 40 leak.sample.NewActorApp$
617: 1 16 leak.sample.NewActorApp$delayedInit$body
リークしないサンプル
package leak.sample
import java.lang.management.ManagementFactory
import akka.actor.{Actor, ActorSystem, Props}
object NewActorApp extends App {
val system = ActorSystem()
println("Creating instances.")
for (i <- 1 to 100000) {
system.actorOf(Props[SourceNActor]) ! "foo"
}
val pid = ManagementFactory.getRuntimeMXBean.getName.split('@').head
println("Created instances.")
println(s"jmap -histo:live ${pid}")
}
class SourceNActor extends Actor {
val i = 1
def receive = {
case "foo" =>
context.system.actorOf(TargetFActor.props(i))
context.stop(self)
}
}
class TargetNActor(n: Int) extends Actor {
def receive = {
case "bar" => println(n)
}
}
同じようにjmapする。
>jmap -histo:live 3868 | find "sample"
15: 100000 2400000 leak.sample.TargetFActor
17: 100000 1600000 leak.sample.TargetFActor$$anonfun$receive$2
340: 1 40 leak.sample.FactoryActorApp$
616: 1 16 leak.sample.FactoryActorApp$delayedInit$body
617: 1 16 leak.sample.TargetFActor$
リークする方のサンプルではSourceXXActorのインスタンスが残ってしまっている。
これはPropsのコンストラクタの引数が名前渡しになっていて実は引数の値と共に呼び出し元のActorの参照を渡してしまっているのが原因らしい。
名前渡し危険、というわけではないだろうからもう少し調べる必要がありそう。