4
头图

Hello, I am crooked.

I ran into an inexplicable mentality issue last week that wasted hours.

I'm so mad, isn't it fragrant to spend these hours knocking (touching) on behalf of (touching) the code (fish)?

The main reason is that the solution to the last problem also left me speechless. The more I thought about it, the more angry I became, so I wrote an article to complain about it.

Let’s start with the conclusion, that is, the title:

When starting the project locally in Debug mode, never breakpoints on methods! Please do not!

First of all, what is a method breakpoint?

For example, hit a breakpoint on the line of the method name:

You click on the icon below in IDEA, View Breakpoints, and it will pop up a box for you.

This pop-up box shows all the breakpoints in the current project. There is a checkbox, Java Method Breakpoints, which is all the "method breakpoints" in the current project:

So what the hell is wrong with this thing?

When the project starts in Debug mode, it slows down the startup very, very seriously.

Show you two screenshots.

The following is a very simple project in my local area. When there is no method breakpoint, it only takes 1.753 seconds to start and complete:

But when I added a method breakpoint, the startup time went straight to 35.035 seconds:

From 1.7 seconds to 35 seconds, the startup time increased by 2000%.

Are you saying you can stand it?

Can't stand it, right?

So how did I step on this pit?

A colleague said that he encountered a bizarre bug in his project and wanted me to help with it.

So I pulled the project down first, then briefly looked at the code, ready to run the project locally and debug it.

However, half an hour has passed, and the project has not yet started. I asked him: How did this project take so long to start locally?

He answered: Normally, it should start up in half a minute.

Then he gave me a demonstration, and it did start successfully in more than 30 seconds on his side.

Obviously, for the same code, one place starts slowly and another place starts quickly. First, environmental problems are suspected.

So I am going to follow the process below.

Check settings -> clear cache -> change workspace -> restart -> change computer -> resign

I checked all the configuration, startup items, network connections, etc. to make sure it was exactly the same as his local environment.

After this set of operations, almost an hour has passed, and I have not found any clues.

But at that time, I didn't panic at all, and I still had the ultimate trick: reboot.

After all, my computer hasn't been shut down for months, and a restart is fine.

Sure enough, after restarting the computer, nothing changed.

When I was in a daze, a colleague came over and asked me what my progress was.

What can I say?

All I can say is: it should be solved in terms of time, but in fact I haven't even started the project successfully.

Hearing this, he sat at my desk, ready to help me take a look.

After half a minute, a magical scene appeared, and he started the project directly on my computer.

After questioning, he did not start in Debug mode, but ran it directly.

If you think about it with your toes, you must know that the Debug mode is doing things.

Then based on the principles of browser-oriented programming, I now have a few key words: IDEA debug starts slowly.

Then I found that many people have encountered similar problems. The solution is to cancel the "method breakpoint" in the project when starting.

But, sadly, not most articles say it's good to do. But doesn't tell me why it's doing this just fine.

I really want to know why there is this pit, because I still use a lot of method breakpoints. The key is that I didn't notice this pit in the process of using it before.

"Method breakpoints" are still very useful, such as my random example.

When I wrote a transaction-related article before, I mentioned such a method:

java.sql.Connection#setAutoCommit

There are several implementation classes for the setAutoCommit method, and I don't know which one will go:

Therefore, when debugging, you can put a breakpoint on the following interface:

Then restart the program, IDEA will automatically help you determine which implementation class to use:

But it should be noted that not all method breakpoints will cause slow startup problems. At least that's what it looks like locally.

When I add method breakpoints to Mapper's interface, I can reproduce this problem stably:

When adding method breakpoints to other methods of the project, it is not mandatory, and this problem occurs occasionally.

In addition, in fact, when you start in Debug mode with method breakpoints, IDEA will pop up this reminder, telling you that method breakpoints will cause Debug slowness:

But, real man, never read reminders. Anyway, I just ignored it and didn't care about the content of the pop-up window at all.

As for why the method breakpoint is set on the interface of Mapper?

It's all my fault, okay?

why exactly

In the process of looking for an answer, I found a link to the official community of this idea:

https://intellij-support.jetbrains.com/hc/en-us/articles/206544799-java-slow-performance-or-hangups-when-starting-debugger-and-stepping

This post, posted by the JetBrains Team, is about slow performance that may be caused by the Debug feature.

In this thread, the first performance point, is Method breakpoints.

How does the official explain this problem?

I will translate it for you.

Method breakpoints will slow down debugger a lot because of the JVM design, they are expensive to evaluate.

They say that due to the design of the JVM, method breakpoints can significantly slow down the debugger because the "evaluate" cost of this thing is high.

evaluate, the fourth-level word, remember it well, the exam will be tested:

Probably means that you want to use the function of the method breakpoint, and during the startup process, it involves a cost of "evaluating" the breakpoint. The cost is a slow start.

How to solve the cost of this "assessment"?

The official solution is very simple and rude:

Don't use method breakpoints, no cost?

So, Remove, it's done:

Remove method breakpoints and consider using the regular line breakpoints.

Remove method breakpoints and consider using regular line breakpoints.

The official is still very considerate, I'm afraid you don't know how Remove also added a sentence:

To verify that you don't have any method breakpoints open .idea/workspace.xml file in the project root directory (or <project>.iws file if you are using the old project format) and look for any breakpoints inside the method_breakpoints node .

You can use the following method to verify that you have enabled method breakpoints.

Just go to the .idea/workspace.xml file, find the method_breakpoints Node, and remove if there is one.

Then I looked at the corresponding files in my project and didn't find the method_breakpoints keyword, but found the following.

It should be that the document has changed, the problem is not big, it is a meaning anyway,

In fact, the method given by the official, although the force is slightly higher, but the operation I gave earlier is simpler:

For the "why" question.

Here, the official answer is particularly vague: because of the JVM design.

Don't ask, because the JVM is designed that way.

I don't think this is the answer I was looking for, but luckily I found a reply from a "good guy" below this thread:

This good person is called Gabi Laotie. When I saw the first sentence of his reply, "I made some research", I knew that this wave was stable, and I found the right place. The answer must be hidden in this one attached by him. link inside.

Gabi old iron said: Brothers, I have studied the reason why the breakpoint of this method is slow. The research report is here:

http://www.smartik.net/2017/11/method-breakpoints-are-evil.html

He even came with a synopsis: To make the long story short.

He was so sweet, I cried to death.

He starts by pointing out the root cause of the problem:

it seems that the root issue is that Method Breakpoints are implemented by using JDPA's Method Entry & Method Exit feature.

The underlying problem is that method breakpoints are implemented using JDPA's Method Entry & Method Exit features.

Some students will ask, what is JDPA?

is a baby:

https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/index.html

JPDA, the full name of Java Platform Debugger Architecture.

The various Debug functions in IDEA are based on this thing.

It doesn't matter if you don't understand, this thing is not tested in the interview, just know that you have this technology here.

Then, he used four any to complete the jump sentence and four bets:

This implementation requires the JVM to fire an event each time any thread enters any method and when any thread exits any method.

This implementation requires the JVM to fire an event every time any (any) thread enters any (any) method, and any (any) thread exits any (any) method.

Guys, isn't this an AOP?

Having said that, I can see why the performance of method breakpoints is so poor. Shouldn't it take so much time to fire so many events entering and exiting methods?

The specific details are clearly written in the research report he mentioned earlier. If you are interested in the details, you can consult and read his report.

The name of his report is also quite bluffing: Method Breakpoints are Evil.

I show you two key places.

The first one is about Method Entry & Method Exit:

  • IDE adds breakpoint to its internal method breakpoint list
  • The IDE tells the front end to enable Method Entry & Method Exit events
  • The frontend (debugger) passes the request to the VM through a proxy
  • On each Method Entry & Method Exit event, forward the notification to the IDE through the entire chain
  • The IDE checks whether its method breakpoint list contains the current method.
  • If it is found to be included, indicating that there is a method breakpoint on this method, the IDE will send a SetBreakpoint request to the VM to set the breakpoint. Otherwise, the VM's thread will be released and nothing will happen

Here is a slightly more specific operation similar to AOP that I mentioned earlier.

The core meaning is just one sentence: too many events are triggered, resulting in severe performance degradation.

The second key place is this:

The article concludes with five conclusions:

  • Method breakpoints IDE feature, not JPDA feature
  • Method breakpoints are really evil, a ratio of evil
  • Method breakpoints will greatly affect the debugger
  • Use them only when you really need them
  • If you must use a method as a breakpoint, consider turning off the method exit event

The first four points have nothing to say.

One last point: consider closing method exit events.

This point is very simple to verify. Right-click on the method breakpoint to see this option. Method Entry & Method Exit are checked by default:

So I verified it with a random project locally.

Open Method Exit event, startup time: 113.244 seconds.

Close Method Exit event, startup time: 46.754 seconds.

Don't say it, it's really useful.

Now I probably know why method breakpoints are so slow.

This is not really a bug, but a feature.

As for the problem of method breakpoints, I searched the community by the way, and the earliest I dated back to 2008:

The dude said that he debugged web applications so slowly that it was unusable. His project only has one-line breakpoints enabled, no method breakpoints.

Ask the boss to help him take a look.

Then the big guy helped him with an analysis and couldn't find the reason.

He himself was very puzzled and said:

I didn't move, it was weird. Sometimes it works, sometimes it doesn't.

Like a classic line:

But the problem was finally solved. How to solve it?

He himself said:

There is indeed a method breakpoint, and he doesn't know how to hit this breakpoint. Like me, his hands are shaking.

windfall

At the bottom of the official post that appeared earlier, there are two links like this:

It points to this place:

https://www.jetbrains.com/help/idea/debugging-code.html

I opened this part of the link and read it again. After identification, this is really a good thing.

This is the official hands-on teaching, teaching you how to use the Debug mode.

Some of the articles I have read about debugging tips before are translated from the official website.

I will give two examples here as a guide. It is strongly recommended that those students who only know the basic operations such as the non-stop next step and skipping the current breakpoint when debugging the program should read it carefully and practice it. .

First is this:

Debugging of Streams streams for Java.

The official gave a debug code example, I made a little fine-tuning, you can stick it and run:

 class PrimeFinder {

    public static void main(String[] args) {
        IntStream.iterate(1, n -> n + 1)
                .limit(100)
                .filter(PrimeTest::isPrime)
                .filter(value -> value > 50)
                .forEach(System.out::println);
    }
}

class PrimeTest {
    static boolean isPrime(int candidate) {
        return candidate == 91 ||
                IntStream.rangeClosed(2, (int) Math.sqrt(candidate))
                        .noneMatch(n -> (candidate % n == 0));
    }
}

The code logic is very simple, that is, to find prime numbers within 100 and greater than 50.

Obviously, the non-prime number 91 is treated specially in the isPrime method, so that the program will eventually output 91, which is a BUG.

Although this bug is obvious at a glance, don't laugh, hold back, and pretend you don't know why.

Now we have to find the bug by debugging.

The breakpoint is hit here:

When running in Debug mode, there is an icon like this:

After clicking, a pop-up window like this will appear:

The box above is the sequence of each method call corresponding to the program, and what is the output after the call is completed.

The "Flat Mode" framed below is clicked like this:

The far right, that is, the result output after the filter.

It contains the number 91:

Click this "91" and find that after the first filter, the data of 91 is still there.

Indicates that there is a problem with this place.

And this place is the isPrime method that has done special treatment to "91" mentioned earlier.

In this way, the method can be analyzed in a targeted manner and the scope of problem exclusion can be narrowed down.

How to say this function, anyway my comment is:

In conclusion, the above is a simple example of IDEA's debugging of Streams.

Then demonstrate a concurrency related:

The official gave such an example:

 public class ConcurrencyTest {
    static final List a = Collections.synchronizedList(new ArrayList());

    public static void main(String[] args) {
        Thread t = new Thread(() -> addIfAbsent(17));
        t.start();
        addIfAbsent(17);
        t.join();
        System.out.println(a);
    }

    private static void addIfAbsent(int x) {
        if (!a.contains(x)) {
            a.add(x);
        }
    }
}

The code creates a thread-safe list collection, and then the main thread and an asynchronous thread insert the same data into the list respectively.

According to the meaning of the addIfAbsent method, if the element to be added exists in the list, it will not be added.

Are you saying this program is thread safe?

Certainly not.

You think about it, judge first, then add, the classic non-atomic operation.

But if you take this program and run it directly, it is not easy to run out of thread-unsafe scenarios:

How to do?

Debug is here to help you do this.

Hit a breakpoint here, then right-click the breakpoint and select "Thread":

In this way, when the program runs, both the main thread and the asynchronous thread will stop here:

You can select Debug main thread or asynchronous thread through the drop-down box in "Frames".

Since both threads have executed the add method, the final output looks like this:

Isn't this thread unsafe?

Even if you know the place is thread-unsafe, it's difficult to verify with program output without Debug to help.

After all, the multi-threading problem is not a problem that can occur every time in most cases.

After locating the problem, the official also gave the correct code snippet:

Okay, it's a guide, it's all basic operations. Again, if you're interested, go through it yourself and follow the case.

Even if you see someone playing with the Debug source code, it is nothing more than a combination of such basic operations.

look back

Let's go back to the official "About the slow performance that the Debug function may cause" this thread:

When I saw the "Collections classes" and "toString()" methods inside the box, my tears were about to fall.

When I first started writing articles, I was miserable by this thing.

Three years ago, in 2019, I wrote this article "This Java basic question is really pitted! I didn't expect a sequel either. 》

At that time, I encountered a problem when Debugging ArrayList, I thought I was disturbed by protons:

A summary in one sentence is that in the case of a single thread, the result of the program running directly is different from the result output by Debug.

At the time I was baffled.

Until 8 months later, I wrote "Memory overflow caused by JDK's BUG! Anyway, I didn't expect to have a sequel" until I accidentally found the answer to the question.

The root cause is that in Debug mode, IDEA will automatically trigger the toString method of the collection class. In the toString method of some collection classes, there will be logic such as modifying the head node, causing the program running result to not match the expected one.

It corresponds to this sentence:

The translation is: old iron, please note that if the code in the toString method changes the state of the program, these methods can also change the result of running the application when running in the debug state.

The final solution is to close these two configurations of IDEA:

At the same time, I also found the explanation of these two configurations in the official documentation:

https://www.jetbrains.com/help/idea/customizing-views.html#renderers

The main purpose is to display the collection class in a more friendly form during Debug.

What do you mean?

Show you an example.

This is what the map collection looks like in Debug mode when the configuration mentioned above is not checked:

This is what the map collection looks like in Debug mode after checking it:

Obviously, after checking it, it looks more friendly.

Finally, my articles are first published on my official account. Welcome to follow:


why技术
2.2k 声望6.8k 粉丝