LoginSignup
22
20

More than 5 years have passed since last update.

ファクトリメソッドを使ってAkkaのメモリリークを防ぐ

Last updated at Posted at 2015-03-03

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の参照を渡してしまっているのが原因らしい。

名前渡し危険、というわけではないだろうからもう少し調べる必要がありそう。

22
20
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
20