According to a recent Jetbrain survey, about one in five Java developers use Visual VM, making it the most widely used performance analysis tool in the ecosystem.

In the recent release of GraalVM 21.2, we have improved VS Code’s tooling support and NOW VS Code is tightly integrated with VisualVM. It’s really more than just a profiler; rather, it’s a Java monitoring and troubleshooting tool all in one. This means that it is now easier and more comfortable to analyze the performance and memory of Java projects from VS Code!

This article provides an example of developing and analyzing code using Java’s GraalVM extension pack, focusing on the VisualVM integration features. Using a very simple scenario, you will learn how to launch VisualVM with your project and immediately configure it with automatically generated Settings. You’ll also see how easy it is to navigate from the analysis results in VisualVM to the source Code in the VS Code editor once a problem is found. If you want to follow the steps in this article, you will need to install the necessary tools:

VS Code

If you don’t already have VS Code, download one!

Required extensions

Install the GralVM extension pack for Java Extensions using the Extensions activity. This way, you’ll get everything you need for Java 8+ development in VS Code, including some cool stuff for GraalVM and Micronaut. For more details on the extension, see Market Entry. Disable or uninstall any other extensions you may have installed for Java development to ensure that you follow the step-by-step instructions of this article.

Additional software

You will need to install and set up an up-to-date version of GraalVM in VS Code. Switch to the Gr activity and click the Download and Install GraalVM button, or add an existing installation of GraalVM 21.2 or later. When downloading a new GraalVM instance, you can choose the distribution of your choice — either Community Edition, which is free for all purposes, or Enterprise Edition, which is free for evaluation and development. Visit GraalVM’s website at graalvm.org to learn more about GraalVM distributions and features.

Once GraalVM is downloaded and installed, make sure it is marked as active, and finally make it active using the Set Active GraalVM Installation action for that Installation (the “home” icon). This sets up the VS Code environment to use a specific GraalVM(not only that!). Java development.

So far so good, but where is VisualVM? Isn’t that everything? ! Actually, you only have one. VisualVM is bundled with each GraalVM installation, configured to run on it, and tested using it. There is no need to download a standalone version from Visualvm.github. But you are welcome to visit and learn something new and useful.

Create a project

Let’s prepare a sample project for the experiment! We will be using a project based on Micronaut — an easy to use but very powerful microservices and serverless framework that works very well with GraalVM. You can learn more about Micronaut at Micronaut. IO.

Generating project

Use the View | command palette… Open the command panel and type ‘Micronaut’ to show available micronaut-related commands. Calling Micronaut: Create the Micronaut Project command and provide the following inputs: latest stable version of Micronaut, Micronaut Application as Application type, active GraalVM instance as Project Java, Use FibonacciDemo as the project name, com.example as the base package, Java as the project language, no additional project functionality, Gradle as the project build tool, JUnit as the test framework, and select the project parent folder location. At this point, a new Micronaut project has been created and is ready to use.

Alternatively, you can generate the project using the Micronaut Launch service and then manually extract and open it in VS Code.

Implementation logic

Now, let’s add some business logic to the project! We will implement a simple Fibonacci number generator that is easy to understand and provides the correct behavior for our experiments. Here is a brief review of the Fibonacci sequence.

Make sure _Explorer_ activity is displayed, and expand the SRC, main, and Java nodes to see the java.com.example package. Right-click the sample section and call New From Template… Operation. Select Java as the template type, Java Class as the template to use, and enter FibonacciController as the name of the Class to create. Finally, a fibonaccicontroller.java file is created next to the application.java generated with the project.

Open the fibonaccicontroller.java file in an editor, enter the following implementation, and save it:

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); }}Copy the code

As you can see, the code is very simple. The nthFibonacci method is the entry point to the request, using the computeNthFibonacci method to compute the actual result and store it in a global buffer, including the time and number of steps required for the computation.

Project analysis

The key feature that makes analysis in VS Code very easy is the VISUALVM section in the Gr activity view. This section contains the process handles to be analyzed, as well as the operations that control and invoke the most useful VisualVM features. You can use the Process: node’s Select process operation to pre-select the process to be analyzed, or you can invoke any VisualVM operation that requires a specific process context. A third way to set process handles is to have VisualVM support automatic process selection at project startup. The VisualVM instance for analysis is defined by the active GralVM installation.

Set the VisualVM

For smooth integration of the VS Code project with VisualVM, switch to the Run and Debug activities and click Create Launch. Json file link, select the Java 8+ environment, and add a special launch configuration called Launch VisualVM & Java 8+ Application using the Add Configuration… Start button. Json editor. Don’t forget to save the modified file! The Run and debug activity view has been updated to now show a run and debug selector that defines the activity launch configuration. Click on it and select Launch VisualVM & Java 8+ Application configuration.

Performance analysis

Now you can run and analyze the project! Using the “run | start debugging” or “run | run without debug” operation to build and start the project. At some point, you’ll notice that VisualVM starts up with the project, finally displaying its GUI and opening the project flow on the Monitor TAB. This is the default setting, you can be in Gr | VISUALVM pane using More Actions… The menu. Notice that in the VisualVM application pane, the project process is displayed using the VS Code project name FibonacciDemo.

At this point, the Fibonacci number generator is up and running, and preparing the localhost: 8080 / nthFibonacci/accept the request. The definition of the NTH Fibonacci number to be computed is done by appending that number to the generator address. Let’s run these three requests:

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

You should eventually see the generated log as follows: 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) You may have noticed that calculating the last Fibonacci number took a lot of time. Meanwhile, VisualVM Monitor shows the following CPU peaks:

This is the right time to use profilers! Switch to the VS Code Gr activity and expand the CPU sampler node in the VISUALVM section to configure the analysis session. Click the Configure action of the Filter: node and select Include Item classes as CPU sampling filters only. Click set sampling rate: node action and set the CPU sampling rate to 20ms. Configuration complete!

Now call the Start CPU sampling action of the CPU sampling node (” Start “/” Triangle “icon). Start the CPU sampling session for the project process in VisualVM and display the Project process’s “Sampler” TAB in VisualVM. The result of the sampler is empty because the project code has not been executed.

Let us use http://localhost:8080/nthFibonacci/45 called again 45 a Fibonacci number calculation, sampler start work now. Based on the item class filter, it displays all stack traces that contain the item class. Sort the results by Total Time (CPU) by clicking on the column header, then right-click on the worker thread default-NioEventLoopGroup -x-y, And calls to Expand/Collapse in the context menu | Expand Topmost Path to Expand to take up most of the time paths of execution. As you can see, almost all of the time is spent on com. Example. Fibonaccicontrollor. Computenthfibonacci () method, it seems to be repeated calls to himself. Stop the sampling session using the Stop button in the Sampler TAB in VisualVM GUI or the Stop Sampling action of the CPU Sampler node in VS Code.

At this point, the best way is to check the source code for com.instance.fibonaccicontroller.computenthFibonacci (). Right-click the method in the sampler result and invoke the Go to Source operation in the context menu. This brings the VS Code window back to you and opens the class source in the editor with the cursor to the right of the method definition. When you look at the implementation, it is clear that performance suffers because of the use of a very inefficient and exponentially time-complex recursive algorithm. Shame on this code!

There are better ways to calculate Fibonacci numbers than recursion. In fact, recursion may be the worst of all possible approaches. But for the sake of simplicity, let’s try to fix the performance of the recursive algorithm by adding a simple cache for the results already computed. will

Fibonaccicontroller.java content change to improved version and save file:


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);
        }
        returnresult; }}Copy the code

Will this small change help improve performance? Let’s use the CPU sampler again to verify it!

Display VS Code Gr activity and click When Started: Configure action for the node in the VISUALVM section. This node is responsible for configuring what happens when the process is started using Launch VisualVM & Java 8+ Application Launch configuration in VS Code. The default is Open process; This is why the process opens automatically in VisualVM when the project is run for the first time. Now select the Start CPU Sampler option to Start the sampling session as soon as possible without any additional operations:

If the original project process is still running, then using the Run | Stop was Debugging operations terminate it, and using the Run | start was Debugging or Run | Run Without was Debugging operation to start it again. Notice that this time VisualVM opens the new project flow on the Sampler TAB and automatically starts the sampling session. You can verify the Sampler Settings by using the Settings check box in the upper right corner of the Sampler view — this filter should be configured by the VS Code integration and contain only the project class com.example. *, the sampling frequency is 20ms. Run the three requests again to gather comparable results:

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

This time you should get the results immediately and see a more reasonable report from the generator something like:

Meanwhile, the CPU sampler does not display the results. Why is that? This is because of the nature of sampling, which periodically checks the actual stack trace at defined intervals. If the methods to be included in the result execute faster than the sampling speed, the sampler will not see them. This is actually a very useful lesson — Sampler is a good tool for finding performance bottlenecks, but not very helpful for optimizing algorithm nuances. It also cannot be used to investigate call counts — which is why the calculation steps are implemented directly in the application code.

While still not as beneficial as other algorithms, we have managed to improve the performance of the recursive algorithm to an acceptable level and verified it using VisualVM. But what if we introduce a memory leak by fixing it? Performance and memory consumption are usually a trade-off, so this scenario is entirely possible. Let’s look at it in the next section.

Analyzing memory consumption

Make sure you have stopped the CPU sampling session, but do not shut down VisualVM and keep the project process running. Switch to the VS Code window again and invoke the Take heap dump operation for the heap dump node in the VISUALVM section of the Gr Active view. This will generate a.hprof memory snapshot describing all the classes and instances allocated on the heap and the references between them. VisualVM displays this snapshot in a special heap viewer component that allows you to browse and analyze its contents in an understandable and high-performance way — a great tool for finding memory leaks!

After the heap dump is loaded, you will see a Summary overview of the process heap. Click the Summary button in the Heap Dump toolbar to switch to the Objects view, which displays a histogram of the class and its respective instances. Find the Class Filter at the bottom of the view and submit com.example. Filters, because we’re only interested in our class. Now you can see several classes in the project: the last two classes were implemented directly, while the others were generated by the Micronaut framework. Expand the com.example. fibonaccicontroller class and right-click ona single instance of com.example. FibonacciController#1 and invoke the “open in new TAB” action in the context menu to open it in a new view:

Now click across column headings to calculate the Retained size. The reserved size is an indicator of how much memory is occupied by a specific instance and the objects it references — or, more specifically, how much memory would be freed if the instance were removed from the heap. For large heaps, calculating the retention size may take some time initially, but the results will be reused in subsequent sessions.

After calculating the size of the reservation, you can analyze the memory footprint of the cache introduced to improve the algorithm. Expand the node to see the memory representation of the instance and find the static field CACHE. The heap viewer shows that it is an instance of java.util.HashMap that contains 44 elements and occupies about 3KB of the heap. Looks like it’s definitely worth speeding up! It’s probably not going to grow in the way we should be worried about. If you still want to improve something, keep in mind that you can always easily implement the go to Source action back in the VS Code editor, even from the heap viewer:

Let’s take a quick look at the static field LOG, which represents the global buffer of results. It doesn’t actually take up much space on the heap, but notice how easy it is to find its contents in the heap viewer! A preview of the stored text is available directly in the node name. If you need to see more, just click the Preview button in the Details section of the heap dump toolbar. The full text is displayed in a regular text component and can be easily read, copied, or even saved to a file. This is useful for searching and identifying specific instances based on their value/content:

At this point, we can conclude that we have successfully implemented the Fibonacci number generator using a recursive algorithm, improved it based on VisualVM’s insights to make it fairly fast, and verified that it behaves correctly from a memory management perspective.

Other features

So far, we’ve only used a few of the VisualVM features available in vscode: the CPU sampler and the heap dump. In fact, we also enabled VisualVM to start with the project, configured the actions when the project starts, and used the Go to Source callback from VisualVM to the VS Code editor. What other features were not mentioned?

  • Similar to Heap dump, thread dump can be called from VS Code and displayed in VisualVM.
  • Like CPU samplers, memory sampler sessions can be configured and controlled in VS Code.
  • VS Code now also allows you to start and stop JFR sessions for project processes and dump and display collected events in VisualVM.

For a complete picture of VS Code and VisualVM integration features, see the GralVM tool for Java Extensions and the VisualVM and VS Code integration documentation.

In this article, we show how to use the latest improvements in VS Code to make your Code perform better, make your work more comfortable, and deliver better software.

There’s more to learn about VS Code’s tools: language servers, debugging, testing support, and more. Learn all the details in the Visual Studio Code Extensions document.

All of this development is part of GraalVM’s efforts, led by Oracle LABS. If you’re interested in something cooler about GraalVM — like compiling Java applications to binary with native images, running scripting languages in Java applications, or programming in multiple languages — check out the GraalVM documentation and other articles in this blog.

We should emphasize that the actual analysis and profiling described in this article is provided by VisualVM, one of the most well-known Java tools. VisualVM is bundled with your GralVM distribution so you can use it right out of the box. You can also learn more on the VisualVM project page.

Translated from: medium.com/graalvm/per…

More often pay attention to the public number [programmer Shi Lei]