VisualVM分析与HelloWorld、springBoot项目 - metabolism - 博客园

标签: | 发表时间:2019-12-08 07:46 | 作者:
出处:https://www.cnblogs.com

VisualVM分析与HelloWorld、springBoot项目

自从1995年第一个JDK版本JDKBeta发布,至今已经快25年,这些年来Java的框架日新月异,从最开始的Servlet阶段,到SSH,SSI,SSM,springboot等,还有一些其他方向的框架微服务SpringCloud、响应式编程Spring Reactor。零零总总 的框架,我们都需要去熟悉,那么怎么去快速熟悉呢,我觉得可以看源码,可以看博客,也可以根据内存分配去完善理解。

那么问题来了,一个Java项目在咱们平时启动项目的时候,究竟发生了什么,创建几个简单的项目,用VisualVM来分析一下~

Main

简单的项目,应该没有比HelloWorld更简单的了吧,按照老规矩,咱们就从HelloWorld开始分析!那么简单的项目大家都能闭着眼睛敲出来,是不是没分析的必要啊,别着急,写好HelloWorld咱们开始分析:

      System.out.println("HelloWorld start");
// 这里让线程睡一会,方便分析
Thread.sleep(100000);
System.out.println("HelloWorld end");

运行main方法,打开VisualVM,发现事情并不简单哦,这个简单的项目有十六个线程维护,其中守护线程有十五个。

其中几大线程的内存分配情况如下:

这些线程都是干什么用的?写了那么多年HelloWorld没想到还有这种知识盲区:

  1. RMI TCP Connection(2)-10.128.227.33

    10.128.227.33是我本地的ip地址。正确而愚蠢的原因是因为开了VisualVM(JMX客户端),JVM需要把他的数据传递给这个客户端,就是使用的TCP传递,相同作用的线程还有 JMX server connection timeout:MAIN方法跑完了,JMX连接的心跳断开。 RMI TCP Connection(idle):用来在RMI连接池中创建线程。 *** Profiler Agent Communication Thread:Profiler代理通信线程。 RMI TCP Accept-0:进行JMX进行JMX监测。

  2. Attach Listener

    Attach Listener线程是负责接收到外部的命令,对该命令进行执行并把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。

  3. main

    main线程,就是我们代码所写得代码对应线程

  4. Monitor Ctr-Break

    这应该是 IDEA 通过反射的方式,伴随你的程序一起启动的对你程序的监控线程。这也是一个默认全局线程

  5. Signal Dispatcher

    前面提到的Attach Listener线程职责是接收外部jvm命令,当命令接收成功后,就会交给signal dispather线程分发到各个不同的模块处理,并且返回处理结果。signal dispather线程是在第一次接收外部jvm命令时,才进行初始化工作。

  6. Finalizer

    这个线程是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;关于Finalizer线程的几点:

    1. 只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;

    2. 该线程是守护线程,因此如果虚拟机中没有其他非守护线程的线程,不管该线程有没有执行完finalize()方法,JVM也会退出;
    3. JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;
    4. JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难,所以单独创建了一个守护线程。

  7. Reference Handler

    VM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。

经过上面的分析可以看出来main本身程序的线程有:main线程,Reference Handler线程,Finalizer线程,Attach Listener线程,Signal Dispatcher线程。

java代码想要实现也很简单,如下即可:

      // 获取java线程管理器MXBean,dumpAllThreads参数:
//                                  lockedMonitors参数表示是否获取同步的monitor信息
//                                  lockedSynchronizers表示是否获取同步的synchronizer
ThreadInfo[] threadInfos = ManagementFactory.getThreadMXBean().dumpAllThreads(true, false);
for (ThreadInfo threadInfo : threadInfos) {
    System.out.println(threadInfo.getThreadId() + " : " + threadInfo.getThreadName());
}

得到的打印结果为:

也就是说,写了那么多年的HelloWorld居然有五个线程来支撑,而我却一直被蒙在鼓里??谁能随时去关注项目有多少个线程啊,VIsualVM可以= =,虽然我觉得他一直起线程进行通信很蠢,但是项目结构大了就有必要了。

Spring-Boot

那么一个啥都没有的springBoot项目启动了之后,会有哪些线程呢?先看看他的pom文件:

      <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.visual.vm.performance</groupId>
    <artifactId>mock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mock</name>
  
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

只引入了spring-boot-starter-web的依赖,其他的什么都没有,启动着试一下。共有27个线程,守护线程有23个。

不同的颜色对应着不同的状态,详情看右下角。这些线程很多都是熟悉的,Main方法分析过的,通过VisualVM工具进行JMX监视(RMI TCP...)开了些线程;IDEA(Monitor Ctrl-Break)开了些线程;垃圾回收(Finalizer,Reference Handler)开了些线程。着重讲一下没见过的线程。

  1. DestroyJavaVM

    所有 POJO应用程序都通过调用该 main方法开始。正常情况下,main 完成后,将告知JVM的DestroyJavaVM`线程来关闭JVM,该线程等待所有非守护进程线程完成后再进行工作。这是为了确保创建的所有非守护程序线程都可以在JVM拆除之前运行完毕。

    但是,带有GUI的应用程序通常以多个线程运行。用于监视系统事件,例如键盘或鼠标事件。JVM仍然会创建 DestroyJavaVM线程,且需要等待所有创建的线程完成,然后再拆除VM,然而应用并不会停止,所以DestoryJavaVM线程就会一直处于等待,直到应用运行完成。

    任何创建线程并仅依赖其功能的应用程序都会有一个 DestroyJavaVM线程,等待应用程序完成并关闭JVM。由于它等待所有其他线程执行完毕( join),因此它不会消耗任何资源。

  2. Http-nio-8080-AcceptorHttp-nio-8080-ClientPollerHttp-nio-8080-BlockPollerhttp-nio-8080-exec-1...10

    这些线程都有个特点,http-nio-8080开头。8080就是这个应用的端口,显然这是给容器使用的。项目引入的是spring-boot-starter-web依赖,也就是默认使用springBoot的内置tomcat容器启动,我们的maven下面也会有这样的几个包: tomcat-embed-coretomcat-embed-eltomcat-embed-websocket,我们所看到的线程都是由这几个包产生的。那么这些线程是干什么用的?

    解决这个问题之前,先看一下tomcat的总体架构:

    Tomcat由Connector和Container两个核心组件构成,Connector组件负责网络请求接入,目前支持BIO、NIO、APR三种模式,Tomcat5之后就支持了NIO,看我们的线程名也就是用的NIO;Container组件负责管理servlet容器。service服务将Container和Connector又包装了一层,使得外部可以直接获取。多个service服务运行在tomcat的Server服务器上,Server上有所有的service实例,并实现了LifeCycle接口来控制所有service的生命周期。

    而NIO对应线程主要是实现在Connector组件中,他负责接受浏览器发过来的tcp请求,创建一个Reuqest和Response对象用来请求和响应,然后产生一个线程,将Request和Response分发给他们对应处理的线程。

    终于看到了线程名中包含的Acceptor、Poller。他们都在Connector组件下的Http11NioProtocol下。着重介绍一下Http11NioProtocol下面的几个组件

    1. Acceptor:接受socket线程,接受的方法比较传统:serverSocket.accept(),得到SocketChannel对象并封装到NioChannel对象中。然后NioChannel对象封装在PollerEvent对象中,并放到events queue中。使用队列(生产者-消费者)和Poller组件交互,Acceptor是生产者,Poller是消费者,通过events queue通信。

                    package org.apache.tomcat.util.net;
      
      public class Acceptor<U> implements Runnable {
            ...
          public void run() {
              byte errorDelay = 0;
              while(this.endpoint.isRunning()) {
                            ....
                  try {
                      this.endpoint.countUpOrAwaitConnection();
                      if (!this.endpoint.isPaused()) {
                          Object socket = null;
                          try {
                            // 这句会调用NioEndPoint类,底层是serverSock.accept()
                              socket = this.endpoint.serverSocketAccept();
                          } catch (Exception var6) {
                              ...
                          }
                                            ...
                      }
                  } catch (Throwable var7) {
                      ...
                  }
              }
      
              this.state = Acceptor.AcceptorState.ENDED;
          }
      }
    2. Poller:NIO选择器Selector用于检查一个或多个NIO Channel(通道)的状态是否可读、可写。如此可以实现单线程管理多个channels也就是可以管理多个网络线程。Poller是NIO实现的主要线程,首先从events queue队列中消费得到PollerEvent对象,再将此对象中的Channel以OP_READ事件注册到主Selector中,Selector执行select操作,遍历出可以读数据的socket,并从Worker线程池中拿到可用的Workrer线程,将可用的socket传递给Worker线程。

                    package org.apache.tomcat.util.net;
      public class Poller implements Runnable {
           ...
           public void run() {
               while(true) {
                   boolean hasEvents = false;
                      label59: {
                          try {
                              if (!this.close) {
                                  hasEvents = this.events();
                                  if (this.wakeupCounter.getAndSet(-1L) > 0L) {
                                      this.keyCount = this.selector.selectNow();
                                  } else {
                                    // selector.select方法,接受acceptor的socket
                                      this.keyCount = this.selector.select(NioEndpoint.this.selectorTimeout);
                                  }
      
                                  this.wakeupCounter.set(0L);
                              }
      
                              if (!this.close) {
                                  break label59;
                              }
      
                              this.events();
                              this.timeout(0, false);
      
                              try {
                                  this.selector.close();
                              } catch (IOException var5) {
                                  NioEndpoint.log.error(AbstractEndpoint.sm.getString("endpoint.nio.selectorCloseFail"), var5);
                              }
                          } catch (Throwable var6) {
                              ExceptionUtils.handleThrowable(var6);
                              NioEndpoint.log.error(AbstractEndpoint.sm.getString("endpoint.nio.selectorLoopError"), var6);
                              continue;
                          }
      
                          NioEndpoint.this.getStopLatch().countDown();
                          return;
                      }
      
                      if (this.keyCount == 0) {
                          hasEvents |= this.events();
                      }
      
                      Iterator iterator = this.keyCount > 0 ? this.selector.selectedKeys().iterator() : null;
      
                      while(iterator != null && iterator.hasNext()) {
                          SelectionKey sk = (SelectionKey)iterator.next();
                          NioEndpoint.NioSocketWrapper socketWrapper = (NioEndpoint.NioSocketWrapper)sk.attachment();
                          if (socketWrapper == null) {
                              iterator.remove();
                          } else {
                              iterator.remove();
                            // 然后调用processKey方法,将socket传给worker线程进行处理
                              this.processKey(sk, socketWrapper);
                          }
                      }
      
                      this.timeout(this.keyCount, hasEvents);
                  }
              }
          }
    3. Worker:Worker线程从Poller传过来的socket后,将socket封装在SocketProcessor对象中,然后从Http11ConnectionHandler获取Http11NioProcessor对象,从Http11NioProcessor中调用CoyoteAdapter的逻辑(这就出了Http11NioProtocol组件,可以看上上图)。在Worker线程中,会完成从socket中读取http request,解析成HttpervletRequest对象,分派到相应的servlet并完成逻辑,然而将response通过socket发回client。

                    package org.apache.tomcat.util.net;
      protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
              public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
                  super(socketWrapper, event);
              }
      
              protected void doRun() {
                // 这一句从Poller拿到socket,然后进行tomcat主线程处理流程
                  NioChannel socket = (NioChannel)this.socketWrapper.getSocket();
                  SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector());
                  NioEndpoint.Poller poller = NioEndpoint.this.poller;
                  if (poller == null) {
                      this.socketWrapper.close();
                  } else {
                      try {
                          int handshake = -1;
      
                          try {
                              if (key != null) {
                                  if (socket.isHandshakeComplete()) {
                                      handshake = 0;
                                  } else if (this.event != SocketEvent.STOP && this.event != SocketEvent.DISCONNECT && this.event != SocketEvent.ERROR) {
                                      handshake = socket.handshake(key.isReadable(), key.isWritable());
                                      this.event = SocketEvent.OPEN_READ;
                                  } else {
                                      handshake = -1;
                                  }
                              }
                          } catch (IOException var13) {
                              handshake = -1;
                              if (NioEndpoint.log.isDebugEnabled()) {
                                  NioEndpoint.log.debug("Error during SSL handshake", var13);
                              }
                          } catch (CancelledKeyException var14) {
                              handshake = -1;
                          }
      
                          if (handshake == 0) {
                              SocketState state = SocketState.OPEN;
                              if (this.event == null) {
                                  state = NioEndpoint.this.getHandler().process(this.socketWrapper, SocketEvent.OPEN_READ);
                              } else {
                                  state = NioEndpoint.this.getHandler().process(this.socketWrapper, this.event);
                              }
      
                              if (state == SocketState.CLOSED) {
                                  poller.cancelledKey(key, this.socketWrapper);
                              }
                          } else if (handshake == -1) {
                              NioEndpoint.this.getHandler().process(this.socketWrapper, SocketEvent.CONNECT_FAIL);
                              poller.cancelledKey(key, this.socketWrapper);
                          } else if (handshake == 1) {
                              this.socketWrapper.registerReadInterest();
                          } else if (handshake == 4) {
                              this.socketWrapper.registerWriteInterest();
                          }
                      } catch (CancelledKeyException var15) {
                          ...
                      } finally {
                          ...
      
                      }
      
                  }
              }
          }
    4. NioSelectorPool:NioEndPoint对象维护了一个NioSelectorPool对象,这个NioSelectorPool中又维护了一个BlockPoller线程(基于Selector进行NIO逻辑)。

总结

平时看起来很熟悉的代码,HelloWorld和SpringBoot初始化的项目。没想到背地里有那么多线程来支撑。装了VisualVM插件并不是让你蹭的就变强,但是可以给你提供一些进步的思路,引导你去思考去进步。下面还会继续带着分析更复杂的项目,不知道会不会有更多常见又未知的知识点等待我们去发现~

欢迎访问我的个人博客

相关 [visualvm 分析 helloworld] 推荐:

VisualVM分析与HelloWorld、springBoot项目 - metabolism - 博客园

- -
VisualVM分析与HelloWorld、springBoot项目. 自从1995年第一个JDK版本JDKBeta发布,至今已经快25年,这些年来Java的框架日新月异,从最开始的Servlet阶段,到SSH,SSI,SSM,springboot等,还有一些其他方向的框架微服务SpringCloud、响应式编程Spring Reactor.

使用 VisualVM 进行性能分析及调优

- - Java - 编程语言 - ITeye博客
使用 VisualVM 进行性能分析及调优. VisualVM 是一款免费的\集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优. 这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作.

VisualVM 使用实例

- - 开源软件 - ITeye博客
VisualVM 是一款免费的性能分析工具. 监控程序运行的实时数据,从而进行动态的性能分析. 同时,它能自动选择更快更轻量级的技术尽量减少性能分析对应用程序造成的影响,提高性能分析的精度. 转储:性能分析工具从内存中获得当前状态数据并存储到文件用于静态的性能分析. 1     系统转储:JVM 生成的本地系统的转储,又称作核心转储.

JVM性能调优监控工具专题三:VisualVM基本篇之快照分析、监控GC、Eclipse集成

- - 行业应用 - ITeye博客
上一个专题专门举例说明了使用VisualVM进行远程监控以及对Tomcat的远程监控,如果有兴趣,可以查看:. 该专题将讲解如何使用VisualVM生成快照、以及如何对JVM的GC进行监控,最后举例说明如何将VisualVM和eclipse进行集成. 我们可以使用 VisualVM 的快照功能生成任意个性能分析快照并保存到本地来辅助我们进行性能分析.

springbatch简介与helloworld

- - CSDN博客推荐文章
一、SpringBatch简介. Spring Batch是一个轻量级的批处理框架, 可以用于企业级海量数据处理, 它提供以下技术解决方案:. 二、SpringBatch结构. Spring Batch由应用层、核心层、基础架构层等组成:. 应用层: 包含所有的批处理作业,  使用spring框架管理程序员自定义的代码.

百度地图API--HelloWorld

- - CSDN博客推荐文章
百度地图API--Hello World.           这里引用一个经典的单词"Hello World",这个词是程序界所有人都很熟悉的,我在开始学习Java的时候就是从这开始的,什么编写一个Hello World程序,甚至有的面试题中有“写一个输出Hello World的程序”来测试面试者的面向对象的思维.

【PHP框架CodeIgniter学习】Helloworld

- - CSDN博客推荐文章
在想做API的时候 ,在搜索发现大家都钟爱推荐 CodeIgniter 这个轻量级开发框架,于是乎就搜索了一番. 原来CodeIgniter 简称CI,开源框架,好像很多的CMS系统都是基于它进行二次开发的. 自己之前使用过的PHP框架有 ThinkPHP,PHPWind等,感觉有点复杂(可能是自己不大熟悉PHP的原因).

Hadoop HelloWorld Examples - 单表连接

- - CSDN博客云计算推荐文章
  应该是那本"Hadoop 实战"的第4个demo了,单表连接. 给出一对对的children和parents的名字,然后输出所有的grandchildren和grandparents对.   输入数据(第一列child,第二列 parent).   输出数据(第一列grandchild,第二列grandparents).

使用Java VisualVM监控远程JVM

- - 互联网 - ITeye博客
我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其是内存的消耗是非常庞大,JDK1.6开始自带的VisualVM就是不错的监控工具. 这个工具就在JAVA_HOME\bin\目录下的jvisualvm.exe, 双击这个文件就能看到一个比较直观的界面.

使用Java VisualVM监控远程JVM

- - Java - 编程语言 - ITeye博客
我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其是内存的消耗是非常庞大,JDK1.6开始自带的VisualVM就是不错的监控工具. 这个工具就在JAVA_HOME\bin\目录下的jvisualvm.exe, 双击这个文件就能看到一个比较直观的界面.