在 VS Code 中使用 GraalVM 和 VisualVM 进行性能和内存分析

简介: 在 VS Code 中使用 GraalVM 和 VisualVM 进行性能和内存分析

18.pngJetbrain最近的一项调查显示,大约五分之一的Java开发人员使用了visual vm,这使得它成为生态系统中使用最广泛的性能分析工具。


在最近发布的GraalVM 21.2中,我们改进了VS Code的工具支持,现在VS Code与VisualVM紧密集成。它实际上不仅仅是一个分析器,更确切地说,它是一个集Java监视和故障排除工具于一身的工具。这意味着现在从VS Code中更容易、更舒适地分析Java项目的性能和内存!


19.png


本文提供了一个使用Java的GraalVM扩展包开发和分析代码的示例,重点介绍VisualVM集成特性。使用一个非常简单的场景,您将学习如何与您的项目一起启动VisualVM,并立即使用自动生成的设置对其进行配置。您还将看到,一旦发现问题,从VisualVM中的分析结果导航到VS Code编辑器中的源代码是多么容易。

如果您想遵循本文中的步骤,您需要安装必要的工具:


VS Code

如果你还没vs code 那么就去下载一个吧!


所需的扩展

使用Extensions活动安装用于Java扩展的gralvm扩展包。通过这种方式,您将在VS Code中获得Java 8+开发所需的一切,包括一些用于GraalVM和Micronaut的很酷的东西。有关扩展的更多细节,请参阅市场入口。

请禁用或卸载您可能已经安装的用于Java开发的任何其他扩展,以确保您能够一步一步地遵循本文的要求。


额外的软件

你需要在VS Code中安装和设置一个最新的GraalVM版本。切换到Gr活动并单击下载和安装GraalVM按钮,或添加一个现有的GraalVM 21.2或更新版本的安装。当下载一个新的GraalVM实例时,您可以选择您所选择的发行版——可以是针对所有目的免费的Community Edition,也可以是针对评估和开发免费的Enterprise Edition。访问GraalVM网站graalvm.org了解更多关于GraalVM发行版和特性的信息。


一旦下载并安装了GraalVM,确保它被标记为活动的,最终使用该安装的Set active GraalVM Installation操作(“home”图标)使它处于活动状态。这设置了VS Code环境来使用特定的GraalVM(不仅仅如此!)Java开发。


到目前为止还不错,但是VisualVM在哪里呢?这难道不是一切吗?!事实上,你只有一个。VisualVM与每个GraalVM安装捆绑在一起,配置为在其上运行,并测试使用它。不需要从visualvm.github下载独立版本。不过我们还是欢迎你来参观,以便学习一些新的和有用的东西。


创建项目

让我们为实验准备一个示例项目!我们将使用一个基于Micronaut的项目——这是一个易于使用,但功能非常强大的微服务和无服务器框架,它与GraalVM非常配合。您可以在Micronaut .io了解更多关于Micronaut的信息。


生成项目

使用View | command palette…打开命令面板并输入“Micronaut”显示可用的与Micronaut相关的命令。调用Micronaut:创建Micronaut Project命令并提供以下输入:最新稳定的Micronaut版本,将Micronaut Application作为应用程序类型,将活动的GraalVM实例作为项目Java,将FibonacciDemo作为项目名称,com.example作为基础包,Java作为项目语言,没有额外的项目功能,Gradle作为项目构建工具,JUnit作为测试框架,并选择项目父文件夹位置。此时,创建了一个新的Micronaut项目,可以使用了。


或者,您可以使用Micronaut Launch服务生成项目,然后在VS Code中手动提取并打开它。


实现逻辑

现在,让我们向项目添加一些业务逻辑!我们将实现一个简单的斐波那契数生成器,它易于理解,并为我们的实验提供正确的行为。下面简要回顾一下斐波那契数列的知识。


确保_Explorer_ activity显示,并展开src、main和java节点以查看java.com.example包。右键单击示例部分并调用New from Template…操作。选择Java作为模板类型,Java Class作为要使用的模板,并输入FibonacciController作为要创建的类名。最终,一个FibonacciController.java文件会在与项目一起生成的Application.java旁边创建。


在编辑器中打开FibonacciController.java文件,输入以下实现,并保存它:

package com.example;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/")
public class FibonacciController {
    private static final StringBuilder LOG = new StringBuilder();
    @Get(uri = "/nthFibonacci/{nth}", produces = MediaType.TEXT_PLAIN)
    public String nthFibonacci(Integer nth) {
        long[] counter = new long[] {0};
        long start = System.currentTimeMillis();
        LOG.append("Fibonacci number #").append(nth).append(" is ");
        LOG.append(computeNthFibonacci(nth, counter));
        LOG.append(" (computed in ").append(System.currentTimeMillis() - start).append(" ms, ").append(counter[0]).append(" steps)\n");
        return LOG.toString();
    }
    private static long computeNthFibonacci(int nth, long[] counter) {
        counter[0]++;
        if (nth == 0 || nth == 1) return nth;
        return computeNthFibonacci(nth - 1, counter) + computeNthFibonacci(nth - 2, counter);
    }
}

如您所见,代码非常简单。nthFibonacci方法是请求的入口点,使用computeNthFibonacci方法计算实际结果,并将其存储到一个全局缓冲区中,包括计算所需的时间和步骤数。


项目分析

使VS Code中的分析变得非常容易的关键特性是Gr活动视图中的VISUALVM部分。该部分包含要分析的流程句柄,以及控制和调用最有用的VisualVM特性的操作。可以使用process:节点的Select流程操作提前选择要分析的流程,也可以调用任何需要具体流程上下文的VisualVM操作。第三种设置流程句柄的方法是让VisualVM支持在项目启动时自动选择流程。用于分析的VisualVM实例由活动的gralvm安装定义。


20.png


设置VisualVM

要使VS Code项目与VisualVM顺利集成,请切换到Run和Debug活动,单击创建启动。json文件链接,选择Java 8+环境,并添加一个特殊的启动配置,名为launch VisualVM & Java 8+ Application using the add configuration…启动按钮。json编辑器。不要忘记保存修改后的文件!运行和调试活动视图被更新,现在显示一个定义活动启动配置的运行和调试选择器。单击它并选择Launch VisualVM & Java 8+ Application配置。


21.png


性能分析

现在可以运行和分析项目了!使用“运行|开始调试”或“运行|不进行调试就运行”操作来构建和启动项目。在某个时候,您会注意到VisualVM与项目一起启动,最终显示它的GUI并在Monitor选项卡上打开项目流程。这是默认设置——你可以在Gr | VISUALVM窗格中使用More Actions…菜单。注意,在VisualVM应用程序窗格中,项目过程使用VS Code项目名称FibonacciDemo显示。


此时,Fibonacci数字生成器已经启动并运行,并准备在localhost:8080/nthFibonacci/上接受请求。要计算的第n个斐波那契数的定义是通过将该数附加到生成器地址来完成的。让我们运行这三个请求:


  • localhost:8080/nthFibonacci/35
  • localhost:8080/nthFibonacci/40
  • localhost:8080/nthFibonacci/45

最终你会看到生成的日志如下:

Fibonacci number #35 is 9227465 (computed in 61 ms, 29860703 steps)

Fibonacci number #40 is 102334155 (computed in 573 ms, 331160281 steps)

Fibonacci number #45 is 1134903170 (computed in 6367 ms, 3672623805 steps)

您可能已经注意到计算最后一个斐波那契数花费了大量的时间。同时,VisualVM Monitor显示如下CPU峰值:


22.png

这是使用分析器的正确时机!切换到VS Code Gr活动,并在VISUALVM部分展开CPU采样器节点来配置分析会话。单击“Filter:”节点的“Configure”动作,并选择“仅包括项目类作为CPU采样过滤器”。单击“配置采样速率:节点动作”,选择CPU采样速率为20ms。配置完成!


现在调用CPU采样节点的Start CPU采样动作(“Start”/“triangle”图标)。在VisualVM中启动项目进程的CPU采样会话,并在VisualVM中显示项目进程的“Sampler”选项卡。采样器的结果是空的,因为还没有执行项目代码。


让我们使用http://localhost:8080/nthFibonacci/45再次调用第45个斐波那契数的计算,现在采样器开始工作。基于项目类筛选器,它显示包含项目类的所有堆栈跟踪。通过单击列标题,按Total Time (CPU)对结果进行排序,然后右键单击工作线程default-nioEventLoopGroup-X-Y,并在上下文菜单中调用Expand / Collapse | Expand Topmost Path来扩展占用大部分时间的执行路径。正如您所看到的,几乎所有的时间都花在com. example.fibonaccicontrollor . computenthfibonacci()方法中,它似乎在重复调用自己。使用VisualVM GUI中的Sampler选项卡中的Stop按钮或VS Code中的CPU Sampler节点的Stop sampling动作停止采样会话。


23.png


此时,最好的方法是检查com. instance . fibonaccicontroller . computenthfibonacci()的源代码。在采样器结果中右键单击该方法,并调用上下文菜单中的Go to Source操作。这将VS Code窗口再次呈现在你的眼前,并在编辑器中打开类源代码,将光标放在方法定义的右边。看一下实现,很明显性能受到了影响,因为使用了效率非常低、时间复杂度呈指数级的递归算法。这段代码真可耻!


有比递归更好的方法来计算斐波那契数。事实上,递归可能是所有可能的方法中最糟糕的一种。但是为了简单起见,让我们尝试通过为已经计算的结果添加一个简单的缓存来修复递归算法的性能。将


FibonacciController.java内容更改为改进的版本并保存文件:

package com.example;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import java.util.HashMap;
import java.util.Map;
@Controller("/")
public class FibonacciController {
    private static final StringBuilder LOG = new StringBuilder();
    private static final Map<Integer, Long> CACHE = new HashMap();
    @Get(uri = "/nthFibonacci/{nth}", produces = MediaType.TEXT_PLAIN)
    public String nthFibonacci(Integer nth) {
        long[] counter = new long[] {0};
        long start = System.currentTimeMillis();
        LOG.append("Fibonacci number #").append(nth).append(" is ");
        LOG.append(computeNthFibonacci(nth, counter));
        LOG.append(" (computed in ").append(System.currentTimeMillis() - start).append(" ms, ").append(counter[0]).append(" steps)\n");
        return LOG.toString();
    }
    private static long computeNthFibonacci(int nth, long[] counter) {
        counter[0]++;
        if (nth == 0 || nth == 1) return nth;
        Long result = CACHE.get(nth);
        if (result == null) {
            result = computeNthFibonacci(nth - 1, counter) + computeNthFibonacci(nth - 2, counter);
            CACHE.put(nth, result);
        }
        return result;
    }
}


这个小小的改变会有助于提高性能吗?让我们再次使用CPU采样器来验证它!


显示VS Code Gr活动,并在VISUALVM部分中单击When started:节点的Configure动作。该节点负责配置在VS Code中使用Launch VisualVM & Java 8+ Application启动配置启动进程时发生的情况。默认值为Open进程;这就是为什么当第一次运行项目时,流程会在VisualVM中自动打开。现在选择Start CPU sampler选项,让采样会话尽快开始,而不需要任何额外的操作:


24.png


如果原始项目进程仍在运行,则使用Run | Stop Debugging操作终止它,并使用Run | start Debugging或Run | Run Without Debugging操作再次启动它。注意,这一次VisualVM在Sampler选项卡上打开了新的项目流程,并自动启动了采样会话。你可以使用sampler视图右上角的settings复选框来验证sampler设置——这个过滤器应该由VS Code集成来配置,只包含项目类com.example。*,采样频率为20ms。

再次运行这三个请求以收集可比结果:


  • localhost:8080/nthFibonacci/35
  • localhost:8080/nthFibonacci/40
  • localhost:8080/nthFibonacci/45

这一次,你应该立即得到结果,并看到一个更合理的报告,从生成器类似于:


同时,CPU采样器没有显示结果。为什么?这是因为采样的性质,它在定义的间隔内周期性地检查实际的堆栈跟踪。如果要包含在结果中的方法的执行速度比采样速度快,采样者就无法看到它们。这实际上是非常有用的一课——Sampler是发现性能瓶颈的好工具,但在优化算法细微差别方面帮助不大。它也不能用于调查调用计数——这就是为什么在应用程序代码中直接实现计算步骤的原因。


虽然仍然不如其他算法有益,但我们已经设法将递归算法的性能提高到可以接受的水平,并使用VisualVM对其进行了验证。但是如果我们通过修复引入了内存泄漏呢?性能和内存消耗通常是一种权衡,所以这种场景是完全可能的。让我们在下一节研究它。


分析内存消耗

确保您已经停止了CPU采样会话,但不要关闭VisualVM,并保持项目进程运行。再次切换到VS Code窗口,并在Gr活动视图的VISUALVM部分中为堆转储节点调用Take堆转储操作。这将生成一个.hprof内存快照,描述在堆上分配的所有类和实例,以及它们之间的引用。VisualVM在一个特殊的堆查看器组件中显示此快照,该组件允许以一种可理解的和性能好的方式浏览和分析其内容——这是一个查找内存泄漏的好工具!


加载堆转储之后,您将看到进程堆的Summary概述。单击Heap Dump工具栏中的Summary按钮,切换到Objects视图,该视图显示类及其各自实例的直方图。在视图底部找到Class Filter并提交com.example。过滤器,因为我们只对我们的类感兴趣。现在您可以看到项目中的几个类:最后两个类是直接实现的,而其他类是由Micronaut框架生成的。展开com.example. fibonaccicontroller类,右键单击单个实例com.example。FibonacciController#1,并在上下文菜单中调用“在新标签中打开”动作,以在一个新视图中打开它:


25.png


现在单击Retained列标题以计算保留的大小。保留大小是一个指标,描述具体实例及其引用的对象占用了多少内存——或者更具体地说,如果从堆中删除实例,将释放多少内存。对于大型堆,计算保留大小最初可能需要一些时间,但结果将在后续会话中重用。


计算了保留的大小之后,您就可以分析为改进算法而引入的缓存的内存占用情况了。展开节点以查看实例的内存表示,并找到静态字段CACHE。堆查看器显示它是一个java.util.HashMap实例,包含44个元素,占用大约3KB的堆。看起来绝对值得加速!估计它不会以我们应该担心的方式增长。如果你仍然想要改进一些东西,请记住,你总是可以很容易地在VS Code编辑器中使用go to Source动作返回实现,即使是从堆查看器:


26.png


让我们快速查看一下静态字段LOG,它表示结果的全局缓冲区。它实际上并不占用堆上的太多空间,但请注意,在堆查看器中发现它的内容是多么容易!存储文本的预览可直接在节点名中获得。如果您需要查看更多内容,只需单击堆转储工具栏的详细信息部分中的Preview按钮。全文显示在一个常规文本组件中,可以很容易地阅读、复制,甚至保存到文件中。这对于基于其值/内容搜索和识别具体实例非常有用:


27.png


至此,我们可以得出结论,我们成功地使用递归算法实现了斐波那契数生成器,并基于VisualVM的见解对其进行了改进,使其执行速度相当快,并从内存管理的角度验证了它的行为是否正确。


其他功能

到目前为止,我们只使用了vscode中可用的VisualVM特性的一部分:CPU采样器和堆转储。实际上,我们还启用了与项目一起启动VisualVM,配置了项目启动时的动作,并使用了从VisualVM到VS Code编辑器的Go to Source回调。没有提到的其他功能是什么?


  • 与 Heap dump 类似,线程转储可以从VS Code中调用并显示在VisualVM中。
  • 与CPU采样器类似,内存采样器会话可以在VS Code中配置和控制。
  • VS Code现在也允许启动和停止项目进程的JFR会话,并在VisualVM中转储和显示收集的事件。

要了解VS Code和VisualVM集成特性的全图,请参阅用于Java扩展的gralvm工具以及VisualVM和VS Code集成文档。


在这篇文章中,我们展示了如何使用VS Code中的最新改进来让你的代码执行得更好,让你的工作更舒适,从而交付更好的软件。


还有更多关于VS Code工具的内容:语言服务器、调试、测试支持等等。了解Visual Studio Code Extensions文档中的所有细节。


所有这些开发都是由Oracle实验室领导的GraalVM工作的一部分。如果您对关于GraalVM的更酷的东西感兴趣——比如用本机映像将Java应用程序编译成二进制,在Java应用程序中运行脚本语言,或多语言编程——请查看本博客中的GraalVM文档和其他文章。


我们应该强调,本文中描述的实际分析和概要分析是由VisualVM提供的,VisualVM是最著名的Java工具之一。VisualVM与您的gralvm发行版绑定在一起,因此您可以开箱即用。您还可以在VisualVM项目页面上了解更多信息。


相关文章
|
11天前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
27 2
|
2月前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
22天前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
11天前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
22 1
|
13天前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
|
14天前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
29天前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
50 11
|
1月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
47 11
|
28天前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
1月前
|
NoSQL 程序员 Linux
轻踩一下就崩溃吗——踩内存案例分析
踩内存问题分析成本较高,尤其是低概率问题困难更大。本文详细分析并还原了两个由于动态库全局符号介入机制(it's a feature, not a bug)触发的踩内存案例。
下一篇
无影云桌面