This is why’s 92nd original article


In the book Understanding the Java Virtual Machine, there is this code:

public class VolatileTest {



    public static volatile int race = 0;



    public static void increase() {

        race++;

    }



    private static final int THREADS_COUNT=20;



    public static void main(String[] args) {

        Thread[] threads = new Thread[THREADS_COUNT];

        for(int i = 0; i < THREADS_COUNT; i++){

           new Thread(new Runnable() {

               @Override

               public void run() {

                   for (int i = 0; i < 10000; i++) {

                       increase();

                   }

               }

           }).start();

        }



// Wait for all accumulated threads to finish

        while(Thread.activeCount()>1)

            Thread.yield();



        System.out.println(race);

    }

}

Copy the code

What was your first reaction when you saw this code?

Is the focus all on the volatile keyword?

You might even start blurting out that volatile guarantees visibility, not atomicity. And the code in race++ is not atomic, blah, blah, blah…

That’s what I am anyway:


When he sent me the code, I pasted it in idea and ran the main method, something magical happened.

This code really does not execute to the output statement, nor does it report any errors.

It just seems like an endless loop.

If you don’t believe it, you can also put it into your idea to implement it.

And so on…

Infinite loop?

Isn’t there an infinite loop in the code?

// Wait for all accumulated threads to finish

while(Thread.activeCount()>1)

    Thread.yield();

Copy the code

What can this code do? It seems so harmless.

But a programmer’s gut tells me there’s something wrong with this place.

The active thread is always greater than 1, causing the while to loop forever.

No, I don’t think so. Let’s Debug it.

I did not find this interesting until I had debugged it twice.

Because of the Debug case, the program should end normally.


What’s going on?

Analyze a wave to go up.

Why can’t it stop?

How do I analyze this problem?

I Run the program again, and the console still doesn’t output anything.

I just stared at the console and thought, what could be the reason?

That doesn’t seem like a good idea.

I’m just going to kill this while loop right now anyway, so to get rid of all the other distractions.

I’ve simplified it to something like this:

public class VolatileTest {



    public static volatile int race = 0;



    public static void main(String[] args) {

        while(Thread.activeCount()>1)

            Thread.yield();

        System.out.println("race = " + race);

    }

}

Copy the code

When it runs, it still doesn’t execute the output statement, which confirms my belief that the while loop has a problem.

The condition for the while loop is thread.Activecount ()>1

Continuing in this direction is to see how many threads are currently active.

So the program can be simplified like this:


Run it directly and see that the output is 2.


Running in Debug mode returns 1.

Comparing the results of this operation, I basically have a good idea.

Let’s take a look at what the activeCount method does:


Notice where the underline is:

The returned value is an estimate.

Estimate is what?


There you go. Another advanced word from me. That’s very good.

What is returned is an estimate.

Why is that?

Because the number of threads is changing dynamically the moment we get the value when we call this method.

This means that the value returned only represents how many threads are active at the time of the call, and maybe one of them will die immediately after the call is made.

So, this is an estimate.

At that moment, I suddenly thought of the uncertainty principle of quantum mechanics.


It is impossible for you to know both the location of a particle and its speed, just as it is impossible for you to know both the value you get when you call activeCount and the actual value of the value when you want to use it in the case of high concurrency in multiple threads.

You see, just after Learning English, I’m learning quantum mechanics.


All right, back to the program.

Although the comment says that the return value is estimate, in our program, there is no such problem.

After seeing the implementation of the activeCount method:

public static int activeCount() {

    return currentThread().getThreadGroup().activeCount();

}

Copy the code

And I thought, well, since in the case of Run, the number that the program returns is 2, what threads are there?

In fact, I originally thought to Debug, but in the case of Debug, the number returned is 1. I realized that the problem must have something to do with IDEA, and I had to use log debugging to find out why.

So, I changed the program to look like this:


If we Run, we can see that there are actually two threads.

One is the main thread, which we’re familiar with.

One is the Monitor Ctrl-break thread, which I don’t recognize.

But something interesting happens when I run Debug:


The Monitor Ctrl-break thread is missing! ?

So I asked him:


Yeah, problem solved, but why?

Why can’t Run Run and Debug Run?


What are the current threads?

Let’s start by sorting out what the current threads are.

You can get all the current threads using the following code:

public  static Thread[] findAllThread() {

    ThreadGroup currentGroup =Thread.currentThread().getThreadGroup();



    while(currentGroup.getParent()! =null){

// Returns the parent thread group of this thread group

        currentGroup=currentGroup.getParent();

    }

// The estimated number of active threads in this thread group

    int noThreads = currentGroup.activeCount();



    Thread[] lstThreads = new Thread[noThreads];

// Copies references to all active subgroups in this thread group into the specified array.

    currentGroup.enumerate(lstThreads);



    for (Thread thread : lstThreads) {

        System.out.println("Thread count:"+noThreads+"" +

                "Thread ID:" + thread.getId() + 

                "Thread name:" + thread.getName() + 

                "Thread status:" + thread.getState());

    }

    return lstThreads;

}

Copy the code

After running, you can see that there are 6 threads:


That is, in idea, if a main method runs, six threads will Run even if nothing is done.

What do these six threads do?

Let’s say it one by one.

Reference Handler thread:

The JVM creates the Reference Handler thread after the main thread is created. The Reference Handler thread has the highest priority, 10, and is mainly used to handle garbage collection of the Reference objects themselves (soft, weak, and virtual).

Finalizers thread:

This thread is also created after the main thread and has a priority of 10. It is used to call the Finalize () method of objects before garbage collection. A few things about the Finalizer thread: 1) The Finalize () method is called only when a garbage collection round is started; Therefore, not all objects will have finalize() methods executed; 2) This thread is also a daemon thread, so if there are no other non-Daemon threads in the VIRTUAL machine, the JVM will exit regardless of whether the thread completes finalize() method. 3) During garbage collection, the JVM will wrap the lost Reference object into the Finencequeue object (the implementation of Reference), which will be processed by the Finencequeue thread. Finally, the reference of the Finalizer object is set to null, which is collected by the garbage collector. 4) Why does the JVM need a separate thread to execute the Finalize () method? If the JVM garbage collection thread does it itself, there is a good chance that the GC thread will stop or go out of control due to a misoperation in finalize(), which is a disaster for the GC thread.

Attach Listener thread:

The Attach Listener thread is responsible for receiving an external command, executing the command and returning the result to the sender. Usually we ask the JVM to give us some feedback with some command. Such as Java-version, jmap, jStack, and so on. If the thread is not initialized when the JVM starts, it will be started the first time the user executes a JVM command.

Signal Dispatcher thread:

We mentioned earlier that the first Attach Listener thread is responsible for receiving external JVM commands. When the command is received successfully, it is handed over to the Signal Dispather thread to distribute the command to various modules and return the result. The Signal Dispather thread is also initialized the first time it receives an external JVM command.

The main thread:

Uh, forget about that. Everybody knows that.

Monitor Ctrl-break thread:

Just in case, the next section is devoted to this thread.

The above thread function, I am from this page to carry over, there are many other threads, you can go to see:

http://ifeve.com/jvm-thread/

I’ll go the extra mile and just give you a long screenshot of everything.

You save the picture first and look at it slowly later:


Now follow me to discover the secrets of the Monitor Ctrl-break thread.

Keep digging

The problem has been solved, but the underlying problem has not been solved:

What is Monitor Ctrl-break thread? Where did it come from?

Let’s just jStack and look at the thread stack.

In IDEA, the “camera” icon is the same function as JStack.


I restored the program to its original form and made the “camera” just a little bit lighter:


From the thread stack you can see that the Monitor Ctrl-break thread comes from this place:

com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

And this place, a look at the name, is the source of idea ah?

It’s not part of our program. What’s going on?

On second thought, I came up with a possibility, so I decided to verify it with the JPS command:


I laughed when I saw the results, and it all made sense.

Sure enough, it is – JavaAgent.

So what is JavaAgent?

Ok, to answer this question well, you have to start another article, this article will not discuss, owe first.

Just a quick mention.

When you execute a Java command on the command line, it outputs a list of things, including this:


What language is it? I don’t understand it.

Refer us to java.lang. Instrument.

Then what is it for?

The simple explanation is:

Instrument makes it easier to use bytecode enhancement techniques, which can be thought of as a JVM level cross section. A program can be enhanced or modified without any intrusion into its source code. In short, a bit of AOP flavor.

The -javaAgent command must be followed by a JAR package.

-javaAgent :

[=< options >]

The instrument mechanism requires that the JAR must have a manifest.mf file, and the manifest.mf file must have something called premain-class.

So, back in our program, take a look at the package that follows JavaAgent.

Where can I watch it?

Here it is:


You turn it on. The command is very long. But what we care about – JavaAgent is right at the beginning:


- javaagent: D: \ \ Program Files \ JetBrains \ IntelliJ IDEA 2019.3.4 \ lib \ idea_rt jar = 61960

Jar package idea_rt: jar package idea_rt: jar package idea_rt: jar package idea_rt: jar package idea_rt: jar package idea_rt


We unzip the JAR package and open its manifest.mf file:


This class is what we’re looking for:


Right now, we’re one step away from the truth.

In the corresponding package, there are three classes:


Focus on the AppMaInv2.class file:


Within this file, there is a startMonitor method:


What did I say?

Repeat after me aloud: there are no secrets under the source code.

This is where the Monitor Ctrl-break thread comes from.

And if you look at the code here, what is this thread doing?

Socket client = new Socket("127.0.0.1", portNumber);

Oh, my God, check out this cute little thing, socket programming. It’s so familiar, it’s back in college lab class.

It links to a port on 127.0.0.1 and then waits in a while(true) loop for the command to be received.

So what is this port?

Here’s 62325:


Note that this port is not fixed and changes every time you start it.

Play with it

Since it’s Socket programming, I’ll play with it.

Here’s the procedure:

public class SocketTest{



    public static void main(String[] args) throws IOException {

        ServerSocket serverSocket = new ServerSocket(12345);

        System.out.println("Waiting for the client to connect.");

        Socket socket = serverSocket.accept();

        System.out.println("A client is connected."+ socket.getInetAddress() + ":" + socket.getPort() +"");

 

        OutputStream outputStream = socket.getOutputStream();

        Scanner scanner = new Scanner(System.in);

        while (true)

        {

            System.out.println("Please enter instructions:");

            String s = scanner.nextLine();

            String message = s + "\n";

            outputStream.write(message.getBytes("US-ASCII"));

        }

    }

}

Copy the code

We specify the server port as 12345.

The port on the client side must also be specified as 12345.

Don’t try to be complicated. It’s simple.

Paste this line of log:


It should be noted that I added a for loop to the program to demonstrate the effect.

Then we change the port to 12345 here:


Save the file as start.bat and place it anywhere.

Everything is ready.

Let’s get the server up and running:


Then, execute the bat file:


In the CMD window output our log, that the program is running normally.

On the server side, the client connection is successful.

Tell us to type in instructions.

What do you type in?

Take a look at the commands supported by the client:


As you can see, the STOP command is supported.

After receiving the command, the program exits.

Let’s do a wave, go GIF:


Get things done.

This is the technical part of this article. Congratulations on knowing that the Monitor Ctrl-break thread in idea is useless.

If you have to dig deeper, dig in the -JavaAgent direction.

Arthas, a well-known Java diagnostic tool, is based on JavaAgent.

That’s interesting.

This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to see the details of the campaign