【问题标题】:Java Profiling, Performance Tuning and Memory Profiling exercisesJava 分析、性能调优和内存分析练习
【发布时间】:2011-03-25 07:01:06
【问题描述】:

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

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

问题或解决方案不应过于复杂;最好在几分钟内解决它们,最坏的情况下在几小时内解决它们。 一些有趣的锻炼领域:

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

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

【问题讨论】:

  • 所以您要的是课程资料?
  • 练习更精确。我想在研讨会中使用它们,但我会说任何调整和分析 Java 应用程序的人都会对这些感兴趣。
  • 您可能需要考虑将 VisualVM 与其他两个一起显示。或者如果时间是一个问题,则排除 JProfiler。 JProfiler 非常漂亮,但要让人们按原样进行分析而不让他们也为该工具付费就很难了。而且,坦率地说,另外两个通常足以找到瓶颈和死锁。
  • 谢谢,我去看看 VisualVM。
  • VisualVM 是 Sun JDK 的一部分。非常有用。

标签: java optimization memory-leaks profiling performance


【解决方案1】:

我试图找到我在野外看到的真实生活示例(可能略有改动,但基本问题都是非常真实的)。我还尝试将它们聚集在相同的场景中,这样您就可以轻松地建立会话。

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

循环:

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

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

    一个解决方案很简单,只需下载循环之前的页面。 其他解决方案如下。

内存泄漏:

  • 简单的一个:你也可以用一种缓存来解决你的问题。在最简单的情况下,您可以将结果放入(静态)地图。但是如果你不阻止它,它的大小会无限增长 -> 内存泄漏。
    可能的解决方案:使用 LRU 映射。很可能性能不会降低太多,但内存泄漏应该会消失。
  • 更棘手的一个:假设您使用WeakHashMap 实现先前的缓存,其中键是 URL(不是字符串,见后文),值是包含 URL、下载页面和其他内容的类的实例.您可能认为它应该没问题,但实际上并非如此:由于值(不是弱引用)具有对键(URL)的引用,因此该键将永远无法清理 -> 内存泄漏很好.
    解决方案:从值中删除 URL。
  • 和以前一样,但是 url 是内部字符串(“如果我们碰巧再次有相同的字符串,可以节省一些内存”),值不引用这个。我没有尝试过,但在我看来它也会导致泄漏,因为实习字符串不能被 GC-ed。
    解决方案:不要实习,这也会导致你千万不要跳过的建议:不要做premature optimization, as it is the root of all evil

对象创建和字符串:

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

并发:

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

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

奖励练习:

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

【讨论】:

    【解决方案2】:

    不要忽略this method,因为它适用于任何语言和操作系统,对于these reasons。一个例子是here。此外,尝试使用具有 I/O 和显着调用深度的示例。不要只使用像 Mandelbrot 这样的小型 CPU 绑定程序。如果你拿那个不太大的 C 例子,然后用 Java 重新编码,那应该能说明你的大部分观点。

    让我们看看:

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

    • 优化循环。
      一般来说,循环不需要优化,除非它们内部几乎没有做任何事情(而且它们需要相当多的时间)。

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

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

    • 并发。
      并发有两个目的。
      1) 性能,但这在允许多个硬件同时启动的范围内起作用。如果硬件不存在,它就无济于事。很痛。
      2) 表达清晰,例如 UI 代码不必担心同时进行大量计算或网络 I/O。

    无论如何,强调都不过分,在证明某件事需要花费大量时间之前,不要进行任何优化。

    【讨论】:

    • @Zwei:你是对的。这种方法不能解决 100% 的问题。可能存在不属于性能问题的内存增长问题。对于这些,您需要一个工具来追踪剩余对象的来源以及未释放它们的原因。
    • Mike,在我使用 jdk 6 的 linux 机器上,javap 表明使用 '+' 连接的字符串在内部经过优化以使用 StringBuilder 类。在提出这个建议之前,值得看看生成的字节码。
    • “在你证明某件事需要相当多的时间之前,不要进行任何优化。”一般来说,这是非常正确的,但在这里我认为你错过了 OP 的意图 - 创建明显表现不佳的练习,以使用优化工具来发现性能问题。
    【解决方案3】:

    我已经使用 JProfiler 来分析我们的应用程序。但它并没有太大帮助。然后我使用了 JHat。使用 JHat 你无法实时看到堆。你必须进行堆转储然后分析它。使用OQL(Object Query Language) 是查找堆泄漏的好方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多