Performance Optimization and Production Deployment: Taking Scala Applications to Production

Introduction

Building high-performance Scala applications requires understanding both language-specific optimizations and JVM tuning techniques. This lesson covers performance optimization strategies, monitoring, profiling, and production deployment best practices to ensure your Scala applications perform excellently in real-world environments.

You'll learn to identify performance bottlenecks, optimize critical code paths, configure the JVM for optimal performance, and deploy applications with confidence using modern deployment strategies.

Performance Analysis and Profiling

JVM Performance Monitoring

// Performance monitoring utilities
import java.lang.management.ManagementFactory
import java.util.concurrent.TimeUnit
import scala.concurrent.duration._

object PerformanceMonitor {
  private val runtime = Runtime.getRuntime
  private val memoryMX = ManagementFactory.getMemoryMXBean
  private val gcMX = ManagementFactory.getGarbageCollectorMXBeans

  case class MemoryStats(
    heapUsed: Long,
    heapMax: Long,
    nonHeapUsed: Long,
    nonHeapMax: Long,
    heapUtilization: Double
  )

  case class GCStats(
    collectorName: String,
    collectionCount: Long,
    collectionTime: Long
  )

  case class SystemStats(
    availableProcessors: Int,
    maxMemory: Long,
    totalMemory: Long,
    freeMemory: Long,
    usedMemory: Long
  )

  def getMemoryStats: MemoryStats = {
    val heapMemory = memoryMX.getHeapMemoryUsage
    val nonHeapMemory = memoryMX.getNonHeapMemoryUsage

    MemoryStats(
      heapUsed = heapMemory.getUsed,
      heapMax = heapMemory.getMax,
      nonHeapUsed = nonHeapMemory.getUsed,
      nonHeapMax = nonHeapMemory.getMax,
      heapUtilization = heapMemory.getUsed.toDouble / heapMemory.getMax * 100
    )
  }

  def getGCStats: List[GCStats] = {
    import scala.jdk.CollectionConverters._
    gcMX.asScala.map { gc =>
      GCStats(
        collectorName = gc.getName,
        collectionCount = gc.getCollectionCount,
        collectionTime = gc.getCollectionTime
      )
    }.toList
  }

  def getSystemStats: SystemStats = {
    SystemStats(
      availableProcessors = runtime.availableProcessors(),
      maxMemory = runtime.maxMemory(),
      totalMemory = runtime.totalMemory(),
      freeMemory = runtime.freeMemory(),
      usedMemory = runtime.totalMemory() - runtime.freeMemory()
    )
  }

  def printStats(): Unit = {
    val memory = getMemoryStats
    val gc = getGCStats
    val system = getSystemStats

    println("=== System Performance Stats ===")
    println(f"Heap: ${memory.heapUsed / 1024 / 1024}%,d MB / ${memory.heapMax / 1024 / 1024}%,d MB (${memory.heapUtilization}%.1f%%)")
    println(f"Non-Heap: ${memory.nonHeapUsed / 1024 / 1024}%,d MB / ${memory.nonHeapMax / 1024 / 1024}%,d MB")
    println(f"Used Memory: ${system.usedMemory / 1024 / 1024}%,d MB")
    println(f"Available Processors: ${system.availableProcessors}")

    println("\n=== Garbage Collection ===")
    gc.foreach { stats =>
      println(f"${stats.collectorName}: ${stats.collectionCount} collections, ${stats.collectionTime} ms total")
    }
    println("=" * 35)
  }

  // Memory pressure test
  def simulateMemoryPressure(): Unit = {
    println("Simulating memory pressure...")
    val lists = scala.collection.mutable.ListBuffer[List[String]]()

    try {
      for (i <- 1 to 1000) {
        lists += List.fill(10000)(s"String_$i")
        if (i % 100 == 0) {
          println(s"Created $i lists...")
          printStats()
        }
      }
    } catch {
      case _: OutOfMemoryError =>
        println("Out of memory!")
        printStats()
    } finally {
      lists.clear()
      System.gc()  // Suggest garbage collection
      Thread.sleep(1000)
      println("After cleanup:")
      printStats()
    }
  }
}

// Benchmark utilities
object BenchmarkUtils {

  def time[T](description: String)(block: => T): T = {
    val start = System.nanoTime()
    val result = block
    val elapsed = System.nanoTime() - start
    println(f"$description: ${elapsed / 1000000.0}%.3f ms")
    result
  }

  def timeWithWarmup[T](description: String, warmupRuns: Int = 5, measureRuns: Int = 10)(block: => T): T = {
    // Warmup runs
    for (_ <- 1 to warmupRuns) block

    // Measured runs
    val times = for (_ <- 1 to measureRuns) yield {
      val start = System.nanoTime()
      val result = block
      val elapsed = System.nanoTime() - start
      (result, elapsed)
    }

    val (results, durations) = times.unzip
    val avgTime = durations.sum / durations.length / 1000000.0
    val minTime = durations.min / 1000000.0
    val maxTime = durations.max / 1000000.0

    println(f"$description: avg=${avgTime}%.3f ms, min=${minTime}%.3f ms, max=${maxTime}%.3f ms")
    results.head
  }

  // Throughput benchmark
  def throughput[T](description: String, durationSeconds: Int = 5)(operation: => T): Double = {
    val startTime = System.currentTimeMillis()
    val endTime = startTime + (durationSeconds * 1000)
    var operations = 0L

    while (System.currentTimeMillis() < endTime) {
      operation
      operations += 1
    }

    val actualDuration = (System.currentTimeMillis() - startTime) / 1000.0
    val opsPerSecond = operations / actualDuration

    println(f"$description: ${operations} operations in ${actualDuration}%.1f seconds = ${opsPerSecond}%.0f ops/sec")
    opsPerSecond
  }

  // Memory allocation benchmark
  def measureAllocations[T](description: String)(block: => T): T = {
    val beforeGC = PerformanceMonitor.getGCStats
    System.gc()
    Thread.sleep(100)
    val beforeMemory = PerformanceMonitor.getMemoryStats

    val result = block

    val afterMemory = PerformanceMonitor.getMemoryStats
    val afterGC = PerformanceMonitor.getGCStats

    val allocatedMemory = afterMemory.heapUsed - beforeMemory.heapUsed
    val gcCollections = afterGC.map(_.collectionCount).sum - beforeGC.map(_.collectionCount).sum

    println(f"$description: ${allocatedMemory / 1024 / 1024}%,d MB allocated, $gcCollections GC collections")
    result
  }
}

// Performance testing examples
object PerformanceTests {

  // Collection performance comparison
  def testCollectionPerformance(): Unit = {
    val size = 1000000
    val data = (1 to size).toList

    println("=== Collection Performance Tests ===")

    // List vs Vector vs Array performance
    BenchmarkUtils.timeWithWarmup("List creation") {
      (1 to size).toList
    }

    BenchmarkUtils.timeWithWarmup("Vector creation") {
      (1 to size).toVector
    }

    BenchmarkUtils.timeWithWarmup("Array creation") {
      (1 to size).toArray
    }

    val list = data
    val vector = data.toVector
    val array = data.toArray

    // Access patterns
    val indices = List.fill(10000)(scala.util.Random.nextInt(size))

    BenchmarkUtils.timeWithWarmup("List random access") {
      indices.map(list(_)).sum
    }

    BenchmarkUtils.timeWithWarmup("Vector random access") {
      indices.map(vector(_)).sum
    }

    BenchmarkUtils.timeWithWarmup("Array random access") {
      indices.map(array(_)).sum
    }

    // Append operations
    BenchmarkUtils.timeWithWarmup("List prepend") {
      (1 to 10000).foldLeft(List.empty[Int])(_ +: _)
    }

    BenchmarkUtils.timeWithWarmup("Vector append") {
      (1 to 10000).foldLeft(Vector.empty[Int])(_ :+ _)
    }

    // Map operations
    BenchmarkUtils.timeWithWarmup("List map") {
      list.map(_ * 2)
    }

    BenchmarkUtils.timeWithWarmup("Vector map") {
      vector.map(_ * 2)
    }

    BenchmarkUtils.timeWithWarmup("Array map") {
      array.map(_ * 2)
    }
  }

  // String concatenation performance
  def testStringPerformance(): Unit = {
    val iterations = 10000

    println("\n=== String Performance Tests ===")

    BenchmarkUtils.timeWithWarmup("String concatenation") {
      (1 to iterations).foldLeft("")(_ + _)
    }

    BenchmarkUtils.timeWithWarmup("StringBuilder") {
      val sb = new StringBuilder()
      for (i <- 1 to iterations) sb.append(i)
      sb.toString
    }

    BenchmarkUtils.timeWithWarmup("String interpolation") {
      (1 to iterations).map(i => s"Item $i").mkString
    }

    BenchmarkUtils.timeWithWarmup("String formatting") {
      (1 to iterations).map(i => f"Item $i%05d").mkString
    }
  }

  // Functional vs imperative performance
  def testFunctionalVsImperative(): Unit = {
    val data = (1 to 1000000).toList

    println("\n=== Functional vs Imperative Performance ===")

    // Functional approach
    BenchmarkUtils.timeWithWarmup("Functional filter/map/reduce") {
      data.filter(_ % 2 == 0).map(_ * 2).sum
    }

    // Imperative approach
    BenchmarkUtils.timeWithWarmup("Imperative loop") {
      var sum = 0
      var i = 0
      while (i < data.length) {
        val value = data(i)
        if (value % 2 == 0) {
          sum += value * 2
        }
        i += 1
      }
      sum
    }

    // View for lazy evaluation
    BenchmarkUtils.timeWithWarmup("Lazy view") {
      data.view.filter(_ % 2 == 0).map(_ * 2).sum
    }

    // Iterator for memory efficiency
    BenchmarkUtils.timeWithWarmup("Iterator") {
      data.iterator.filter(_ % 2 == 0).map(_ * 2).sum
    }
  }

  // Concurrency performance
  def testConcurrencyPerformance(): Unit = {
    import scala.concurrent.{Future, ExecutionContext}
    import scala.concurrent.duration._
    import scala.util.{Success, Failure}

    implicit val ec: ExecutionContext = ExecutionContext.global

    val data = (1 to 1000000).toList
    val cpuCount = Runtime.getRuntime.availableProcessors()

    println(s"\n=== Concurrency Performance Tests (${cpuCount} CPUs) ===")

    // Sequential processing
    BenchmarkUtils.timeWithWarmup("Sequential processing") {
      data.map(x => x * x + math.sqrt(x)).sum
    }

    // Parallel processing
    BenchmarkUtils.timeWithWarmup("Parallel processing") {
      data.par.map(x => x * x + math.sqrt(x)).sum
    }

    // Future-based parallel processing
    BenchmarkUtils.timeWithWarmup("Future-based parallel") {
      val chunkSize = data.length / cpuCount
      val chunks = data.grouped(chunkSize).toList

      val futures = chunks.map { chunk =>
        Future {
          chunk.map(x => x * x + math.sqrt(x)).sum
        }
      }

      val result = Future.sequence(futures).map(_.sum)
      scala.concurrent.Await.result(result, 10.seconds)
    }
  }
}

// Run performance tests
println("Starting performance analysis...")
PerformanceMonitor.printStats()

PerformanceTests.testCollectionPerformance()
PerformanceTests.testStringPerformance()
PerformanceTests.testFunctionalVsImperative()
PerformanceTests.testConcurrencyPerformance()

println("\nFinal stats after tests:")
PerformanceMonitor.printStats()

Memory Management and Optimization

// Memory-efficient data structures and patterns
object MemoryOptimization {

  // Avoid memory leaks with proper resource management
  trait Resource {
    def close(): Unit
  }

  def withResource[R <: Resource, T](resource: R)(operation: R => T): T = {
    try {
      operation(resource)
    } finally {
      resource.close()
    }
  }

  // Memory-efficient streaming
  def processLargeFile(filename: String): Long = {
    import scala.io.Source

    val source = Source.fromFile(filename)
    try {
      source.getLines()
        .filter(_.nonEmpty)
        .map(_.length)
        .sum
    } finally {
      source.close()
    }
  }

  // Object pooling for frequent allocations
  class ObjectPool[T](factory: () => T, reset: T => Unit, maxSize: Int = 100) {
    private val pool = scala.collection.mutable.Queue[T]()
    private var created = 0

    def acquire(): T = {
      pool.synchronized {
        if (pool.nonEmpty) {
          pool.dequeue()
        } else {
          created += 1
          factory()
        }
      }
    }

    def release(obj: T): Unit = {
      pool.synchronized {
        if (pool.size < maxSize) {
          reset(obj)
          pool.enqueue(obj)
        }
      }
    }

    def stats: (Int, Int) = pool.synchronized((created, pool.size))
  }

  // Example usage of object pool
  case class DataProcessor(var data: List[String] = List.empty) {
    def process(input: String): String = {
      data = input :: data
      data.mkString(",")
    }
  }

  val processorPool = new ObjectPool[DataProcessor](
    factory = () => DataProcessor(),
    reset = processor => processor.data = List.empty,
    maxSize = 50
  )

  def processWithPool(inputs: List[String]): List[String] = {
    inputs.map { input =>
      val processor = processorPool.acquire()
      try {
        processor.process(input)
      } finally {
        processorPool.release(processor)
      }
    }
  }

  // Memory-efficient caching with weak references
  import java.lang.ref.WeakReference
  import scala.collection.mutable

  class WeakCache[K, V] {
    private val cache = mutable.Map[K, WeakReference[V]]()

    def get(key: K): Option[V] = {
      cache.get(key).flatMap(ref => Option(ref.get()))
    }

    def put(key: K, value: V): Unit = {
      cache(key) = new WeakReference(value)
    }

    def cleanup(): Unit = {
      cache.retain((_, ref) => ref.get() != null)
    }

    def size: Int = cache.size
  }

  // Flyweight pattern for immutable data
  object StringPool {
    private val pool = mutable.Map[String, String]()

    def intern(s: String): String = {
      pool.getOrElseUpdate(s, s)
    }

    def size: Int = pool.size
  }

  // Memory-efficient builders
  class EfficientStringBuilder(initialCapacity: Int = 16) {
    private var buffer = new Array[Char](initialCapacity)
    private var length = 0

    def append(s: String): this.type = {
      val newLength = length + s.length
      if (newLength > buffer.length) {
        val newCapacity = math.max(newLength, buffer.length * 2)
        val newBuffer = new Array[Char](newCapacity)
        Array.copy(buffer, 0, newBuffer, 0, length)
        buffer = newBuffer
      }

      s.getChars(0, s.length, buffer, length)
      length = newLength
      this
    }

    def result(): String = new String(buffer, 0, length)

    def clear(): this.type = {
      length = 0
      this
    }
  }

  // Compact data structures
  case class CompactUser(
    id: Int,
    name: String,  // Use String interning for common names
    email: String,
    flags: Byte    // Pack boolean flags into a single byte
  ) {
    def isActive: Boolean = (flags & 1) != 0
    def isVerified: Boolean = (flags & 2) != 0
    def isPremium: Boolean = (flags & 4) != 0

    def withActive(active: Boolean): CompactUser = {
      val newFlags = if (active) (flags | 1).toByte else (flags & ~1).toByte
      copy(flags = newFlags)
    }

    def withVerified(verified: Boolean): CompactUser = {
      val newFlags = if (verified) (flags | 2).toByte else (flags & ~2).toByte
      copy(flags = newFlags)
    }

    def withPremium(premium: Boolean): CompactUser = {
      val newFlags = if (premium) (flags | 4).toByte else (flags & ~4).toByte
      copy(flags = newFlags)
    }
  }

  object CompactUser {
    def apply(id: Int, name: String, email: String, active: Boolean = false, 
              verified: Boolean = false, premium: Boolean = false): CompactUser = {
      var flags: Byte = 0
      if (active) flags = (flags | 1).toByte
      if (verified) flags = (flags | 2).toByte
      if (premium) flags = (flags | 4).toByte

      CompactUser(id, StringPool.intern(name), email, flags)
    }
  }

  // Memory usage monitoring
  def measureMemoryUsage[T](description: String)(block: => T): T = {
    System.gc()
    Thread.sleep(100)
    val memBefore = PerformanceMonitor.getMemoryStats

    val result = block

    System.gc()
    Thread.sleep(100)
    val memAfter = PerformanceMonitor.getMemoryStats

    val allocated = memAfter.heapUsed - memBefore.heapUsed
    println(f"$description: ${allocated / 1024 / 1024}%,d MB allocated")

    result
  }

  // Demonstration of memory optimization techniques
  def demonstrateOptimizations(): Unit = {
    println("=== Memory Optimization Demonstrations ===")

    // Object pooling
    val inputs = List.fill(1000)("test input")

    measureMemoryUsage("Without object pooling") {
      inputs.map { input =>
        val processor = DataProcessor()
        processor.process(input)
      }
    }

    measureMemoryUsage("With object pooling") {
      processWithPool(inputs)
    }

    val (created, pooled) = processorPool.stats
    println(s"Object pool stats: $created created, $pooled pooled")

    // String interning
    val names = List.fill(1000)("John") ++ List.fill(1000)("Jane") ++ List.fill(1000)("Bob")

    measureMemoryUsage("Without string interning") {
      names.map(name => s"User: $name")
    }

    measureMemoryUsage("With string interning") {
      names.map(name => s"User: ${StringPool.intern(name)}")
    }

    println(s"String pool size: ${StringPool.size}")

    // Compact data structures
    measureMemoryUsage("Standard data structure") {
      (1 to 10000).map { i =>
        (i, s"User$i", s"user$i@example.com", i % 2 == 0, i % 3 == 0, i % 5 == 0)
      }
    }

    measureMemoryUsage("Compact data structure") {
      (1 to 10000).map { i =>
        CompactUser(i, s"User$i", s"user$i@example.com", i % 2 == 0, i % 3 == 0, i % 5 == 0)
      }
    }
  }
}

// Run memory optimization demonstrations
MemoryOptimization.demonstrateOptimizations()

JVM Tuning and Configuration

JVM Performance Tuning

// JVM configuration examples and monitoring
object JVMTuning {

  // JVM arguments for different scenarios
  object JVMArgs {

    // Development environment (laptop/desktop)
    val development = List(
      "-Xms512m",                    // Initial heap size
      "-Xmx2g",                      // Maximum heap size
      "-XX:+UseG1GC",                // Use G1 garbage collector
      "-XX:MaxGCPauseMillis=200",    // Target GC pause time
      "-XX:+UnlockExperimentalVMOptions",
      "-XX:+UseCGroupMemoryLimitForHeap",  // Container awareness
      "-XX:+PrintGC",                // Enable GC logging
      "-XX:+PrintGCDetails",
      "-XX:+PrintGCTimeStamps",
      "-XX:+UseStringDeduplication", // Deduplicate strings
      "-server"                      // Server mode
    )

    // Production high-throughput application
    val highThroughput = List(
      "-Xms8g",                      // Large initial heap
      "-Xmx8g",                      // Fixed heap size
      "-XX:+UseG1GC",
      "-XX:MaxGCPauseMillis=100",    // Low latency target
      "-XX:G1HeapRegionSize=16m",    // Larger regions for large heap
      "-XX:+UseStringDeduplication",
      "-XX:+OptimizeStringConcat",
      "-XX:+UseFastUnorderedTimeStamps",
      "-XX:+AggressiveOpts",         // Aggressive optimizations
      "-XX:+TieredCompilation",      // Enable tiered compilation
      "-server"
    )

    // Production low-latency application
    val lowLatency = List(
      "-Xms4g",
      "-Xmx4g",
      "-XX:+UnlockExperimentalVMOptions",
      "-XX:+UseZGC",                 // Ultra-low latency GC (Java 11+)
      "-XX:+UseLargePages",          // Large memory pages
      "-XX:+UnlockDiagnosticVMOptions",
      "-XX:+LogVMOutput",
      "-XX:+TraceClassLoading",
      "-XX:+PrintCompilation",
      "-server"
    )

    // Memory-constrained environment (containers)
    val containerOptimized = List(
      "-Xms256m",
      "-Xmx1g",
      "-XX:+UseSerialGC",            // Simple GC for small heaps
      "-XX:+UseCGroupMemoryLimitForHeap",
      "-XX:+UnlockExperimentalVMOptions",
      "-XX:+UseCGroupCPUCount",      // Container CPU awareness
      "-Djava.security.egd=file:/dev/./urandom",  // Faster random
      "-server"
    )

    // Debugging and profiling
    val debugging = List(
      "-Xms1g",
      "-Xmx2g",
      "-XX:+UseG1GC",
      "-XX:+PrintGC",
      "-XX:+PrintGCDetails",
      "-XX:+PrintGCApplicationStoppedTime",
      "-XX:+PrintStringDeduplicationStatistics",
      "-XX:+FlightRecorder",         // Enable Java Flight Recorder
      "-XX:+UnlockCommercialFeatures",
      "-XX:StartFlightRecording=duration=60s,filename=app.jfr",
      "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005",  // Remote debugging
      "-server"
    )
  }

  // GC analysis utilities
  object GCAnalysis {
    import java.lang.management.{GarbageCollectorMXBean, ManagementFactory}
    import scala.jdk.CollectionConverters._

    case class GCInfo(
      name: String,
      collectionCount: Long,
      collectionTime: Long,
      averageTime: Double
    )

    def analyzeGC(): List[GCInfo] = {
      ManagementFactory.getGarbageCollectorMXBeans.asScala.toList.map { gc =>
        val count = gc.getCollectionCount
        val time = gc.getCollectionTime
        val avgTime = if (count > 0) time.toDouble / count else 0.0

        GCInfo(gc.getName, count, time, avgTime)
      }
    }

    def printGCAnalysis(): Unit = {
      val gcInfo = analyzeGC()
      println("=== Garbage Collection Analysis ===")
      gcInfo.foreach { info =>
        println(f"${info.name}:")
        println(f"  Collections: ${info.collectionCount}")
        println(f"  Total time: ${info.collectionTime} ms")
        println(f"  Average time: ${info.averageTime}%.2f ms")
      }
    }

    // GC pressure simulation
    def simulateGCPressure(rounds: Int = 10): Unit = {
      println(s"Simulating GC pressure with $rounds rounds...")

      for (round <- 1 to rounds) {
        val beforeGC = analyzeGC()

        // Allocate and discard memory
        val data = (1 to 100000).map(i => s"String number $i with extra data").toList
        val processed = data.map(_.toUpperCase).filter(_.contains("5"))

        val afterGC = analyzeGC()

        // Calculate GC activity in this round
        val gcActivity = (beforeGC zip afterGC).map { case (before, after) =>
          (after.name, after.collectionCount - before.collectionCount, after.collectionTime - before.collectionTime)
        }.filter(_._2 > 0)

        if (gcActivity.nonEmpty) {
          println(f"Round $round: ${gcActivity.map(a => s"${a._1}:${a._2}collections/${a._3}ms").mkString(", ")}")
        }

        // Keep some references to simulate memory pressure
        if (round % 3 == 0) {
          Thread.sleep(100)  // Allow GC to catch up
        }
      }
    }
  }

  // JIT compilation monitoring
  object JITAnalysis {
    import java.lang.management.ManagementFactory

    def getCompilationInfo(): Unit = {
      val compilationMX = ManagementFactory.getCompilationMXBean
      if (compilationMX.isCompilationTimeMonitoringSupported) {
        println(s"JIT Compilation time: ${compilationMX.getTotalCompilationTime} ms")
      }
    }

    // Hot spot identification through repeated execution
    def identifyHotSpots(): Unit = {
      println("=== JIT Hot Spot Analysis ===")

      // Method that will become a hot spot
      def computeIntensive(n: Int): Long = {
        var sum = 0L
        for (i <- 1 to n) {
          sum += i * i + math.sqrt(i).toLong
        }
        sum
      }

      val iterations = 50000
      val warmupRuns = 10
      val measuredRuns = 5

      // Warmup phase to trigger JIT compilation
      println("Warming up JIT compiler...")
      getCompilationInfo()

      for (_ <- 1 to warmupRuns) {
        computeIntensive(iterations)
      }

      getCompilationInfo()

      // Measure performance after JIT compilation
      println("Measuring performance after JIT compilation...")
      val times = for (_ <- 1 to measuredRuns) yield {
        val start = System.nanoTime()
        val result = computeIntensive(iterations)
        val elapsed = System.nanoTime() - start
        (result, elapsed)
      }

      val avgTime = times.map(_._2).sum / times.length / 1000000.0
      println(f"Average execution time after JIT: $avgTime%.3f ms")

      getCompilationInfo()
    }
  }

  // Memory pool analysis
  object MemoryPoolAnalysis {
    import java.lang.management.{ManagementFactory, MemoryPoolMXBean, MemoryType}
    import scala.jdk.CollectionConverters._

    case class PoolInfo(
      name: String,
      poolType: MemoryType,
      used: Long,
      committed: Long,
      max: Long,
      utilization: Double
    )

    def analyzeMemoryPools(): List[PoolInfo] = {
      ManagementFactory.getMemoryPoolMXBeans.asScala.toList.map { pool =>
        val usage = pool.getUsage
        val utilization = if (usage.getMax > 0) {
          usage.getUsed.toDouble / usage.getMax * 100
        } else 0.0

        PoolInfo(
          name = pool.getName,
          poolType = pool.getType,
          used = usage.getUsed,
          committed = usage.getCommitted,
          max = usage.getMax,
          utilization = utilization
        )
      }
    }

    def printMemoryPoolAnalysis(): Unit = {
      val pools = analyzeMemoryPools()
      println("=== Memory Pool Analysis ===")

      val heapPools = pools.filter(_.poolType == MemoryType.HEAP)
      val nonHeapPools = pools.filter(_.poolType == MemoryType.NON_HEAP)

      println("Heap Pools:")
      heapPools.foreach { pool =>
        println(f"  ${pool.name}:")
        println(f"    Used: ${pool.used / 1024 / 1024}%,d MB")
        println(f"    Committed: ${pool.committed / 1024 / 1024}%,d MB")
        println(f"    Max: ${if (pool.max > 0) s"${pool.max / 1024 / 1024} MB" else "Unlimited"}")
        println(f"    Utilization: ${pool.utilization}%.1f%%")
      }

      println("\nNon-Heap Pools:")
      nonHeapPools.foreach { pool =>
        println(f"  ${pool.name}:")
        println(f"    Used: ${pool.used / 1024 / 1024}%,d MB")
        println(f"    Committed: ${pool.committed / 1024 / 1024}%,d MB")
        println(f"    Max: ${if (pool.max > 0) s"${pool.max / 1024 / 1024} MB" else "Unlimited"}")
        println(f"    Utilization: ${pool.utilization}%.1f%%")
      }
    }
  }

  // Class loading analysis
  object ClassLoadingAnalysis {
    import java.lang.management.ManagementFactory

    def analyzeClassLoading(): Unit = {
      val classLoadingMX = ManagementFactory.getClassLoadingMXBean

      println("=== Class Loading Analysis ===")
      println(s"Current loaded classes: ${classLoadingMX.getLoadedClassCount}")
      println(s"Total loaded classes: ${classLoadingMX.getTotalLoadedClassCount}")
      println(s"Unloaded classes: ${classLoadingMX.getUnloadedClassCount}")
      println(s"Verbose class loading: ${classLoadingMX.isVerbose}")
    }
  }

  // Comprehensive JVM analysis
  def performJVMAnalysis(): Unit = {
    println("=== Comprehensive JVM Performance Analysis ===")

    MemoryPoolAnalysis.printMemoryPoolAnalysis()
    println()

    GCAnalysis.printGCAnalysis()
    println()

    ClassLoadingAnalysis.analyzeClassLoading()
    println()

    JITAnalysis.identifyHotSpots()
    println()

    // Simulate some load and analyze GC behavior
    GCAnalysis.simulateGCPressure(5)

    println("=== Final Analysis ===")
    GCAnalysis.printGCAnalysis()
  }
}

// Run JVM analysis
JVMTuning.performJVMAnalysis()

Production Deployment Strategies

Containerization and Orchestration

// Docker configuration for Scala applications
object DockerConfiguration {

  // Dockerfile generation
  val baseDockerfile = """
    |FROM openjdk:11-jre-slim
    |
    |# Install additional packages if needed
    |RUN apt-get update && apt-get install -y \
    |    curl \
    |    && rm -rf /var/lib/apt/lists/*
    |
    |# Create non-root user
    |RUN groupadd -r appuser && useradd -r -g appuser appuser
    |
    |# Set working directory
    |WORKDIR /app
    |
    |# Copy application jar
    |COPY target/scala-*/myapp-assembly-*.jar app.jar
    |
    |# Copy configuration files
    |COPY conf/ /app/conf/
    |
    |# Set JVM options for containers
    |ENV JAVA_OPTS="-Xms256m -Xmx1g -XX:+UseContainerSupport -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
    |
    |# Expose port
    |EXPOSE 8080
    |
    |# Health check
    |HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    |    CMD curl -f http://localhost:8080/health || exit 1
    |
    |# Switch to non-root user
    |USER appuser
    |
    |# Run application
    |ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
    |""".stripMargin

  // Multi-stage Docker build
  val multiStageDockerfile = """
    |# Build stage
    |FROM sbtscala/scala-sbt:openjdk-11.0.2_9_1.3.8_2.13.1 as builder
    |
    |WORKDIR /app
    |
    |# Copy build files
    |COPY build.sbt .
    |COPY project/ project/
    |
    |# Download dependencies
    |RUN sbt update
    |
    |# Copy source code
    |COPY src/ src/
    |
    |# Build application
    |RUN sbt assembly
    |
    |# Runtime stage
    |FROM openjdk:11-jre-slim
    |
    |RUN groupadd -r appuser && useradd -r -g appuser appuser
    |
    |WORKDIR /app
    |
    |# Copy only the built JAR from build stage
    |COPY --from=builder /app/target/scala-*/myapp-assembly-*.jar app.jar
    |
    |# Runtime configuration
    |ENV JAVA_OPTS="-Xms256m -Xmx1g -XX:+UseContainerSupport"
    |
    |EXPOSE 8080
    |
    |HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    |    CMD curl -f http://localhost:8080/health || exit 1
    |
    |USER appuser
    |
    |ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
    |""".stripMargin

  // Kubernetes deployment configuration
  val kubernetesDeployment = """
    |apiVersion: apps/v1
    |kind: Deployment
    |metadata:
    |  name: scala-app
    |  labels:
    |    app: scala-app
    |spec:
    |  replicas: 3
    |  selector:
    |    matchLabels:
    |      app: scala-app
    |  template:
    |    metadata:
    |      labels:
    |        app: scala-app
    |    spec:
    |      containers:
    |      - name: scala-app
    |        image: myregistry/scala-app:latest
    |        ports:
    |        - containerPort: 8080
    |        env:
    |        - name: JAVA_OPTS
    |          value: "-Xms512m -Xmx2g -XX:+UseG1GC -XX:+UseContainerSupport"
    |        - name: DATABASE_URL
    |          valueFrom:
    |            secretKeyRef:
    |              name: app-secrets
    |              key: database-url
    |        resources:
    |          requests:
    |            memory: "1Gi"
    |            cpu: "500m"
    |          limits:
    |            memory: "2Gi"
    |            cpu: "1000m"
    |        livenessProbe:
    |          httpGet:
    |            path: /health
    |            port: 8080
    |          initialDelaySeconds: 60
    |          periodSeconds: 30
    |        readinessProbe:
    |          httpGet:
    |            path: /ready
    |            port: 8080
    |          initialDelaySeconds: 30
    |          periodSeconds: 10
    |---
    |apiVersion: v1
    |kind: Service
    |metadata:
    |  name: scala-app-service
    |spec:
    |  selector:
    |    app: scala-app
    |  ports:
    |  - port: 80
    |    targetPort: 8080
    |  type: LoadBalancer
    |""".stripMargin

  // Horizontal Pod Autoscaler
  val hpaConfiguration = """
    |apiVersion: autoscaling/v2
    |kind: HorizontalPodAutoscaler
    |metadata:
    |  name: scala-app-hpa
    |spec:
    |  scaleTargetRef:
    |    apiVersion: apps/v1
    |    kind: Deployment
    |    name: scala-app
    |  minReplicas: 2
    |  maxReplicas: 10
    |  metrics:
    |  - type: Resource
    |    resource:
    |      name: cpu
    |      target:
    |        type: Utilization
    |        averageUtilization: 70
    |  - type: Resource
    |    resource:
    |      name: memory
    |      target:
    |        type: Utilization
    |        averageUtilization: 80
    |  behavior:
    |    scaleUp:
    |      stabilizationWindowSeconds: 60
    |      policies:
    |      - type: Percent
    |        value: 100
    |        periodSeconds: 60
    |    scaleDown:
    |      stabilizationWindowSeconds: 300
    |      policies:
    |      - type: Percent
    |        value: 10
    |        periodSeconds: 60
    |""".stripMargin
}

// Application health checks and monitoring
object HealthMonitoring {

  // Health check implementation
  trait HealthChecker {
    def name: String
    def check(): HealthStatus
  }

  sealed trait HealthStatus
  case object Healthy extends HealthStatus
  case class Unhealthy(reason: String) extends HealthStatus

  // Database health checker
  class DatabaseHealthChecker(connectionPool: String) extends HealthChecker {
    def name: String = "database"

    def check(): HealthStatus = {
      try {
        // Simulate database connection check
        if (connectionPool.nonEmpty) {
          Healthy
        } else {
          Unhealthy("No database connection pool")
        }
      } catch {
        case e: Exception => Unhealthy(s"Database connection failed: ${e.getMessage}")
      }
    }
  }

  // External service health checker
  class ExternalServiceHealthChecker(serviceUrl: String) extends HealthChecker {
    def name: String = "external-service"

    def check(): HealthStatus = {
      try {
        // Simulate HTTP health check
        if (serviceUrl.startsWith("http")) {
          Healthy
        } else {
          Unhealthy("Invalid service URL")
        }
      } catch {
        case e: Exception => Unhealthy(s"External service check failed: ${e.getMessage}")
      }
    }
  }

  // Disk space health checker
  class DiskSpaceHealthChecker(thresholdPercent: Double = 90.0) extends HealthChecker {
    def name: String = "disk-space"

    def check(): HealthStatus = {
      val file = new java.io.File(".")
      val totalSpace = file.getTotalSpace
      val freeSpace = file.getFreeSpace
      val usedPercent = ((totalSpace - freeSpace).toDouble / totalSpace) * 100

      if (usedPercent > thresholdPercent) {
        Unhealthy(f"Disk usage too high: $usedPercent%.1f%%")
      } else {
        Healthy
      }
    }
  }

  // Memory health checker
  class MemoryHealthChecker(thresholdPercent: Double = 85.0) extends HealthChecker {
    def name: String = "memory"

    def check(): HealthStatus = {
      val memStats = PerformanceMonitor.getMemoryStats
      if (memStats.heapUtilization > thresholdPercent) {
        Unhealthy(f"Memory usage too high: ${memStats.heapUtilization}%.1f%%")
      } else {
        Healthy
      }
    }
  }

  // Comprehensive health service
  class HealthService(checkers: List[HealthChecker]) {

    case class OverallHealth(
      status: String,
      checks: Map[String, String],
      timestamp: Long
    )

    def checkHealth(): OverallHealth = {
      val results = checkers.map { checker =>
        val status = try {
          checker.check() match {
            case Healthy => "UP"
            case Unhealthy(reason) => s"DOWN: $reason"
          }
        } catch {
          case e: Exception => s"ERROR: ${e.getMessage}"
        }
        checker.name -> status
      }.toMap

      val overallStatus = if (results.values.forall(_.startsWith("UP"))) "UP" else "DOWN"

      OverallHealth(
        status = overallStatus,
        checks = results,
        timestamp = System.currentTimeMillis()
      )
    }

    def printHealthReport(): Unit = {
      val health = checkHealth()
      println(s"=== Health Check Report - ${health.status} ===")
      println(s"Timestamp: ${new java.util.Date(health.timestamp)}")

      health.checks.foreach { case (name, status) =>
        val icon = if (status.startsWith("UP")) "✓" else "✗"
        println(s"  $icon $name: $status")
      }
    }
  }

  // Application metrics
  class ApplicationMetrics {
    private var requestCount = 0L
    private var errorCount = 0L
    private val responseTimeHistory = scala.collection.mutable.ListBuffer[Long]()
    private val maxHistorySize = 1000

    def recordRequest(responseTimeMs: Long, isError: Boolean = false): Unit = {
      synchronized {
        requestCount += 1
        if (isError) errorCount += 1

        responseTimeHistory += responseTimeMs
        if (responseTimeHistory.size > maxHistorySize) {
          responseTimeHistory.remove(0)
        }
      }
    }

    def getMetrics: Map[String, Any] = synchronized {
      val avgResponseTime = if (responseTimeHistory.nonEmpty) {
        responseTimeHistory.sum.toDouble / responseTimeHistory.size
      } else 0.0

      val errorRate = if (requestCount > 0) {
        (errorCount.toDouble / requestCount) * 100
      } else 0.0

      Map(
        "total_requests" -> requestCount,
        "error_count" -> errorCount,
        "error_rate_percent" -> f"$errorRate%.2f",
        "avg_response_time_ms" -> f"$avgResponseTime%.2f",
        "memory_usage" -> PerformanceMonitor.getMemoryStats,
        "system_stats" -> PerformanceMonitor.getSystemStats
      )
    }

    def printMetrics(): Unit = {
      val metrics = getMetrics
      println("=== Application Metrics ===")
      metrics.foreach { case (key, value) =>
        println(s"$key: $value")
      }
    }
  }

  // Create health service with all checkers
  val healthCheckers = List(
    new DatabaseHealthChecker("mock-pool"),
    new ExternalServiceHealthChecker("https://api.example.com"),
    new DiskSpaceHealthChecker(90.0),
    new MemoryHealthChecker(85.0)
  )

  val healthService = new HealthService(healthCheckers)
  val applicationMetrics = new ApplicationMetrics()

  // Simulate application activity and monitoring
  def simulateApplicationLoad(): Unit = {
    println("=== Simulating Application Load ===")

    val random = new scala.util.Random()

    // Simulate 100 requests
    for (i <- 1 to 100) {
      val responseTime = 50 + random.nextInt(200)  // 50-250ms
      val isError = random.nextDouble() < 0.05      // 5% error rate

      applicationMetrics.recordRequest(responseTime, isError)

      if (i % 20 == 0) {
        println(s"Processed $i requests...")
        healthService.printHealthReport()
        applicationMetrics.printMetrics()
        println()
      }

      Thread.sleep(10)  // Small delay between requests
    }

    println("=== Final Application State ===")
    healthService.printHealthReport()
    applicationMetrics.printMetrics()
  }
}

// CI/CD Pipeline configuration
object CIPipeline {

  // GitHub Actions workflow
  val githubActionsWorkflow = """
    |name: Scala CI/CD Pipeline
    |
    |on:
    |  push:
    |    branches: [ main, develop ]
    |  pull_request:
    |    branches: [ main ]
    |
    |jobs:
    |  test:
    |    runs-on: ubuntu-latest
    |    
    |    services:
    |      postgres:
    |        image: postgres:13
    |        env:
    |          POSTGRES_PASSWORD: postgres
    |        options: >-
    |          --health-cmd pg_isready
    |          --health-interval 10s
    |          --health-timeout 5s
    |          --health-retries 5
    |
    |    steps:
    |    - uses: actions/checkout@v2
    |    
    |    - name: Set up JDK 11
    |      uses: actions/setup-java@v2
    |      with:
    |        java-version: '11'
    |        distribution: 'adopt'
    |        
    |    - name: Cache SBT
    |      uses: actions/cache@v2
    |      with:
    |        path: |
    |          ~/.sbt
    |          ~/.ivy2/cache
    |          ~/.coursier
    |        key: ${{ runner.os }}-sbt-${{ hashFiles('**/build.sbt') }}
    |        
    |    - name: Run tests
    |      run: sbt test
    |      
    |    - name: Generate coverage report
    |      run: sbt coverage test coverageReport
    |      
    |    - name: Upload coverage to Codecov
    |      uses: codecov/codecov-action@v1
    |
    |  build-and-deploy:
    |    needs: test
    |    runs-on: ubuntu-latest
    |    if: github.ref == 'refs/heads/main'
    |    
    |    steps:
    |    - uses: actions/checkout@v2
    |    
    |    - name: Set up JDK 11
    |      uses: actions/setup-java@v2
    |      with:
    |        java-version: '11'
    |        distribution: 'adopt'
    |        
    |    - name: Build application
    |      run: sbt assembly
    |      
    |    - name: Build Docker image
    |      run: |
    |        docker build -t ${{ secrets.DOCKER_REGISTRY }}/scala-app:${{ github.sha }} .
    |        docker tag ${{ secrets.DOCKER_REGISTRY }}/scala-app:${{ github.sha }} ${{ secrets.DOCKER_REGISTRY }}/scala-app:latest
    |        
    |    - name: Login to Docker Registry
    |      run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login ${{ secrets.DOCKER_REGISTRY }} -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
    |      
    |    - name: Push Docker image
    |      run: |
    |        docker push ${{ secrets.DOCKER_REGISTRY }}/scala-app:${{ github.sha }}
    |        docker push ${{ secrets.DOCKER_REGISTRY }}/scala-app:latest
    |        
    |    - name: Deploy to Kubernetes
    |      run: |
    |        echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
    |        export KUBECONFIG=kubeconfig
    |        kubectl set image deployment/scala-app scala-app=${{ secrets.DOCKER_REGISTRY }}/scala-app:${{ github.sha }}
    |        kubectl rollout status deployment/scala-app
    |""".stripMargin

  // SBT release configuration
  val sbtReleaseConfig = """
    |// project/plugins.sbt
    |addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0")
    |addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.1")
    |addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.9.0")
    |
    |// build.sbt release settings
    |import ReleaseTransformations._
    |
    |releaseProcess := Seq[ReleaseStep](
    |  checkSnapshotDependencies,
    |  inquireVersions,
    |  runClean,
    |  runTest,
    |  setReleaseVersion,
    |  commitReleaseVersion,
    |  tagRelease,
    |  publishArtifacts,
    |  setNextVersion,
    |  commitNextVersion,
    |  pushChanges
    |)
    |
    |// Docker configuration
    |enablePlugins(DockerPlugin)
    |docker / dockerfile := {
    |  val jarFile = (Compile / packageBin).value
    |  val classpath = (Compile / managedClasspath).value
    |  val mainclass = (Compile / packageBin / mainClass).value.getOrElse(sys.error("Expected exactly one main class"))
    |  val jarTarget = s"/app/${jarFile.getName}"
    |
    |  new Dockerfile {
    |    from("openjdk:11-jre-slim")
    |    add(jarFile, jarTarget)
    |    entryPoint("java", "-jar", jarTarget)
    |  }
    |}
    |""".stripMargin
}

// Run production deployment demonstrations
println("=== Production Deployment Strategy Demonstration ===")

// Health monitoring simulation
HealthMonitoring.simulateApplicationLoad()

println("\n=== Docker Configuration ===")
println("Multi-stage Dockerfile for optimized builds:")
println(DockerConfiguration.multiStageDockerfile.take(500) + "...")

println("\n=== CI/CD Pipeline ===")
println("GitHub Actions workflow configured for:")
println("- Automated testing")
println("- Docker image building")
println("- Kubernetes deployment")
println("- Coverage reporting")

println("\n=== Performance Analysis Complete ===")
println("Ready for production deployment with:")
println("✓ JVM optimization")
println("✓ Memory management")
println("✓ Health monitoring")
println("✓ Container orchestration")
println("✓ CI/CD automation")

Summary

In this lesson, you've mastered production-ready Scala development:

Performance Analysis: JVM monitoring, profiling, and bottleneck identification
Memory Management: Efficient data structures, object pooling, and leak prevention
JVM Tuning: Garbage collection optimization and configuration for different scenarios
Deployment Strategies: Containerization, Kubernetes orchestration, and scaling
Monitoring: Health checks, metrics collection, and observability
CI/CD: Automated testing, building, and deployment pipelines
Production Best Practices: Configuration management and operational excellence

These skills ensure your Scala applications perform optimally in production environments and can scale to meet real-world demands.

Congratulations!

You've completed an advanced journey through Scala, from advanced types and implicits to production deployment. You now have the knowledge and tools to build high-performance, scalable Scala applications that excel in professional environments. Continue practicing these concepts and exploring the rich Scala ecosystem to become a Scala expert!