Java 性能分析、性能调优和内存分析练习

我即将使用JProfilerEclipse Tptp对Java应用程序进行研讨会分析,性能调优,内存分析,内存泄漏检测等。我需要一组练习,我可以尽可能地提供给参与者:使用该工具来分析发现问题:瓶颈,内存泄漏,次优代码等。我相信周围有很多经验和现实生活中的例子。

  • 解决问题并实施优化的代码
  • 通过执行另一个分析会话来演示解决方案
  • 理想情况下,编写演示性能提升的单元测试

问题和解决办法都不应过于复杂;应该有可能在几分钟内解决这些问题,最坏的情况是几个小时。一些有趣的运动领域:

  • 解决内存泄漏问题
  • 优化循环
  • 优化对象创建和管理
  • 优化字符串操作
  • 解决并发和并发瓶颈加剧的问题

理想情况下,练习应包括示例未优化代码和解决方案代码。


答案 1

我试图找到我在野外看到的现实生活中的例子(也许略有改变,但基本问题都非常真实)。我还尝试围绕相同的场景将它们群集起来,以便您可以轻松构建会话。

场景:您有一个耗时的函数,您希望对不同的值执行多次,但相同的值可能会再次弹出(理想情况下,在创建后不久)。一个很好的例子是您需要下载和处理的url-web页面对(对于练习,它应该是模拟的)。

循环:

  • 您要检查页面中是否弹出了一组单词中的任何一个。在循环中使用函数,但具有相同的值,伪代码:

    for (word : words) {
        checkWord(download(url))
    }
    

    一个解决方案非常简单,只需在循环之前下载页面即可。其他解决方案如下。

内存泄漏:

  • 简单的一个:你也可以用一种缓存来解决你的问题。在最简单的情况下,您可以将结果放到(静态)地图中。但是,如果不阻止它,它的大小将无限增长 - >内存泄漏。
    可能的解决方案:使用 LRU 映射。最有可能的是,性能不会降低太多,但内存泄漏应该会消失。
  • 更棘手的一个:假设你使用一个实现以前的缓存,其中键是URL(不是字符串,见后面),值是包含URL,下载页面和其他内容的类的实例。你可能会认为它应该没问题,但实际上并非如此:由于值(不是弱引用)具有对密钥(URL)的引用,因此密钥将永远没有资格清理 - >个不错的内存泄漏。
    解决方案:从值中删除 URL。WeakHashMap
  • 与以前相同,但url是暂存字符串(“如果我们碰巧再次使用相同的字符串,则节省一些内存”),值不是指此。我没有尝试过,但在我看来,它也会导致泄漏,因为实习的字符串不能被GC-ed。
    解决方案:不要实习,这也会导致你一定不要跳过的建议:不要过早地优化,因为它是所有邪恶的根源

对象创建和字符串:

  • 假设您只想显示页面的文本(~删除html标签)。编写一个逐行执行该操作的函数,并将其追加到不断增长的结果中。起初,结果应该是一个字符串,因此追加将花费大量时间和对象分配。您可以从性能角度(为什么追加速度如此之慢)和对象创建角度(为什么我们创建了这么多字符串,StringBuffers,数组等)来检测此问题。
    解决方案:使用 StringBuilder 作为结果。

并发:

  • 您希望通过并行执行下载/过滤来加快整个内容的速度。创建一些线程并使用它们运行代码,但在一个大的同步块(基于缓存)内执行所有操作,只是为了“保护缓存免受并发问题的影响”。效果应该是您有效地只使用一个线程,因为所有其他线程都在等待获取缓存上的锁定。
    解决方案:仅围绕缓存操作进行同步(例如,使用 'java.util.collections.synchronizedMap())

  • 同步所有微小的代码片段。这应该会扼杀性能,可能会阻止正常的并行执行。如果你足够幸运/聪明,你也可以想出一个死锁。这样做的寓意是:同步不应该是一个临时的事情,在“它不会伤害”的基础上,而是一个经过深思熟虑的事情。

奖金练习:

在开始时填满缓存,之后不要做太多的分配,但仍然在某个地方有一个小的泄漏。通常这种模式不太容易捕捉。您可以使用探查器的“书签”或“水印”功能,该功能应在缓存完成后立即创建。


答案 2

不要忽略此方法,因为出于这些原因,它适用于任何语言和操作系统。这里有一个例子。此外,请尝试使用具有 I/O 和显著调用深度的示例。不要只使用像曼德布洛特这样的小型CPU绑定程序。如果你拿那个不太大的C例子,用Java重新编码,这应该说明你的大部分观点。

我看看:

  • 解决内存泄漏问题。
    垃圾回收器的全部意义在于堵塞内存泄漏。但是,您仍然可以分配太多内存,对于某些对象,这显示为“新”中的大部分时间。

  • 优化循环。
    通常,循环不需要优化,除非它们内部很少完成(并且它们需要很大一部分时间)。

  • 优化对象创建和管理。
    这里的基本方法是:保持数据结构尽可能简单。特别是远离通知式的尝试,以保持数据一致性,因为这些东西会逃跑,使呼叫树变得非常茂密。这是大型软件出现性能问题的主要原因。

  • 优化字符串操作。
    使用字符串生成器,但不要使用未使用可靠百分比执行时间的代码。

  • 并发。
    并发有两个目的。
    1)性能,但这仅在允许多个硬件同时启动的程度上起作用。如果硬件不存在,则无济于事。很痛。
    2)表达式清晰,因此例如UI代码不必担心同时进行繁重的计算或网络I / O。

无论如何,它都不能被强调得足够,在你证明某件事需要很大一部分时间之前,不要做任何优化。


推荐