有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java线程在javaagent阻止退出中启动

在javagent中,我启动了一个HttpServer:

public static void premain(String agentArgs, Instrumentation inst) throws InstantiationException, IOException {

        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);

        server.createContext("/report", new ReportHandler());
        server.createContext("/data", new DataHandler());
        server.createContext("/stack", new StackHandler());
        ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
            int count = 0;
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setDaemon(true);
                t.setName("JDBCLD-HTTP-SERVER" + count++);
                return t;
            }
            
        });
        server.setExecutor(es);
        server.start();

        // how to properly close ?
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                server.stop(5);
                log.info("internal httpserver has been closed.");
                es.shutdown();
                try {
                    if (!es.awaitTermination(60, TimeUnit.SECONDS)) {
                        log.warn("executor service of internal httpserver not closing in 60 seconds");
                        es.shutdownNow();
                        if (!es.awaitTermination(60, TimeUnit.SECONDS))
                            log.error("executor service of internal httpserver not closing in 120 seconds, give up");
                    }else {
                        log.info("executor service of internal httpserver closed.");
                    }
                } catch (InterruptedException ie) {
                    log.warn("thread interrupted, shutdown executor service of internal httpserver");
                    es.shutdownNow();
                    Thread.currentThread().interrupt();
                }
            }
        });

    // other instrumention code ignored ...
}

测试计划:

public class AgentTest {

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

        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:oracle:thin:@172.31.27.182:1521/pas");
        config.setUsername("pas");
        config.setPassword("pas");

        HikariDataSource ds = new HikariDataSource(config);
        Connection c = ds.getConnection();
        Connection c1 = ds.getConnection();
        
        c.getMetaData();
        
        try {
            Thread.sleep(1000 * 60 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
            c.close();
            c1.close();
            ds.close();
        }
        
        c.close();
        c1.close();
        
        ds.close();
        
        
    }

}

当目标jvm退出时,我希望停止HttpServer。但当我的测试java程序完成时,主线程停止,但整个jvm进程不会终止,上面代码中的shutdown hook不会执行。如果我在eclipse IDE中单击“终止”按钮,eclipse将显示一个错误: enter image description here 但至少jvm会退出,我的关闭钩子会被调用

根据java.lang.Runtime的java文档:

The Java virtual machine shuts down in response to two kinds of events:

The program exits normally, when the last non-daemon thread exits or when the exit (equivalently, System.exit) method is invoked, or The virtual machine is terminated in response to a user interrupt, such as typing ^C, or a system-wide event, such as user logoff or system shutdown.

com.sun.net.httpserver.HttpServer将启动一个非守护进程调度程序线程,该线程将在调用HttpServer#stop时退出,因此我面临死锁

non-daemon thread not finish -> shutdown hook not triggered -> can't stop server -> non-daemon thread not finish

有什么好主意吗?请注意,我无法修改目标应用程序的代码

应用kriegaex答案后的更新

I added some logging to watch dog thread, and here is outputs:

2021-09-22 17:30:00.967 INFO  - Connnection@1594791957 acquired by 40A4F128987F8BD9C0EE6749895D1237
2021-09-22 17:30:00.968 DEBUG - Stack@40A4F128987F8BD9C0EE6749895D1237: 
java.lang.Throwable: 
    at com.zaxxer.hikari.pool.ProxyConnection.<init>(ProxyConnection.java:102)
    at com.zaxxer.hikari.pool.HikariProxyConnection.<init>(HikariProxyConnection.java)
    at com.zaxxer.hikari.pool.ProxyFactory.getProxyConnection(ProxyFactory.java)
    at com.zaxxer.hikari.pool.PoolEntry.createProxyConnection(PoolEntry.java:97)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:192)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
    at agenttest.AgentTest.main(AgentTest.java:19)
2021-09-22 17:30:00.969 INFO  - Connnection@686560878 acquired by 464555C270688B747CA211DE489B7730
2021-09-22 17:30:00.969 DEBUG - Stack@464555C270688B747CA211DE489B7730: 
java.lang.Throwable: 
    at com.zaxxer.hikari.pool.ProxyConnection.<init>(ProxyConnection.java:102)
    at com.zaxxer.hikari.pool.HikariProxyConnection.<init>(HikariProxyConnection.java)
    at com.zaxxer.hikari.pool.ProxyFactory.getProxyConnection(ProxyFactory.java)
    at com.zaxxer.hikari.pool.PoolEntry.createProxyConnection(PoolEntry.java:97)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:192)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:162)
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:100)
    at agenttest.AgentTest.main(AgentTest.java:20)
2021-09-22 17:30:00.971 DEBUG - Connnection@1594791957 used by getMetaData
2021-09-22 17:30:01.956 DEBUG - there is still 12 active threads, keep wathcing
2021-09-22 17:30:01.956 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true HikariPool-1 connection adder#true 
2021-09-22 17:30:02.956 DEBUG - there is still 12 active threads, keep wathcing
2021-09-22 17:30:02.956 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true HikariPool-1 connection adder#true 
2021-09-22 17:30:03.957 DEBUG - there is still 12 active threads, keep wathcing
2021-09-22 17:30:03.957 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true HikariPool-1 connection adder#true 
2021-09-22 17:30:04.959 DEBUG - there is still 12 active threads, keep wathcing
2021-09-22 17:30:04.959 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true HikariPool-1 connection adder#true 
2021-09-22 17:30:05.959 DEBUG - there is still 12 active threads, keep wathcing
2021-09-22 17:30:05.960 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true HikariPool-1 connection adder#true 
2021-09-22 17:30:06.960 DEBUG - there is still 11 active threads, keep wathcing
2021-09-22 17:30:06.960 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true 
2021-09-22 17:30:07.961 DEBUG - there is still 11 active threads, keep wathcing
2021-09-22 17:30:07.961 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true 
2021-09-22 17:30:08.961 DEBUG - there is still 11 active threads, keep wathcing
2021-09-22 17:30:08.961 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true 
2021-09-22 17:30:09.962 DEBUG - there is still 11 active threads, keep wathcing
2021-09-22 17:30:09.962 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true 
2021-09-22 17:30:10.962 DEBUG - there is still 11 active threads, keep wathcing
2021-09-22 17:30:10.963 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true main#false server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true HikariPool-1 housekeeper#true 
2021-09-22 17:30:10.976 INFO  - Connnection@1594791957 released
2021-09-22 17:30:10.976 DEBUG - set connection count to 0 by stack hash 40A4F128987F8BD9C0EE6749895D1237
2021-09-22 17:30:10.976 INFO  - Connnection@686560878 released
2021-09-22 17:30:10.976 DEBUG - set connection count to 0 by stack hash 464555C270688B747CA211DE489B7730
2021-09-22 17:30:11.963 DEBUG - there is still 10 active threads, keep wathcing
2021-09-22 17:30:11.963 DEBUG - Reference Handler#true Finalizer#true Signal Dispatcher#true server-timer#true Thread-2#false jdbcld-watch-dog#false Timer-0#true oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser#true InterruptTimer#true DestroyJavaVM#false 
2021-09-22 17:30:12.964 DEBUG - there is still 10 active threads, keep wathcing

更新

我想支持各种java应用程序,包括使用servlet容器运行的web应用程序和标准的javase应用程序


共 (1) 个答案

  1. # 1 楼答案

    这里有一点MCVE说明了Ewramer的想法。我使用小的byte-buddy-agent帮助程序库动态附加一个代理,以使我的示例自包含,从主方法开始启动Java代理。我省略了运行这个示例所需的3个普通无操作虚拟处理程序类

    package org.acme.agent;
    
    import com.sun.net.httpserver.HttpServer;
    import net.bytebuddy.agent.ByteBuddyAgent;
    
    import java.io.IOException;
    import java.lang.instrument.Instrumentation;
    import java.net.InetSocketAddress;
    import java.util.Random;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.TimeUnit;
    
    public class Agent {
    
      public static void premain(String agentArgs, Instrumentation inst) throws IOException {
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(8000), 0);
        ExecutorService executorService = getExecutorService(httpServer);
        Runtime.getRuntime().addShutdownHook(getShutdownHook(httpServer, executorService));
        // other instrumention code ignored ...
        startWatchDog();
      }
    
      private static ExecutorService getExecutorService(HttpServer server) {
        server.createContext("/report", new ReportHandler());
        server.createContext("/data", new DataHandler());
        server.createContext("/stack", new StackHandler());
        ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
          int count = 0;
    
          @Override
          public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName("JDBCLD-HTTP-SERVER" + count++);
            return t;
          }
    
        });
        server.setExecutor(executorService);
        server.start();
        return executorService;
      }
    
      private static Thread getShutdownHook(HttpServer httpServer, ExecutorService executorService) {
        return new Thread(() -> {
          httpServer.stop(5);
          System.out.println("Internal HTTP server has been stopped");
          executorService.shutdown();
          try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
              System.out.println("Executor service of internal HTTP server not closing in 60 seconds");
              executorService.shutdownNow();
              if (!executorService.awaitTermination(60, TimeUnit.SECONDS))
                System.out.println("Executor service of internal HTTP server not closing in 120 seconds, giving up");
            }
            else {
              System.out.println("Executor service of internal HTTP server closed");
            }
          }
          catch (InterruptedException ie) {
            System.out.println("Thread interrupted, shutting down executor service of internal HTTP server");
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
          }
        });
      }
    
      private static void startWatchDog() {
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        while (threadGroup.getParent() != null)
          threadGroup = threadGroup.getParent();
        final ThreadGroup topLevelThreadGroup = threadGroup;
        // Plus 1, because of the monitoring thread we are going to start right below
        final int activeCount = topLevelThreadGroup.activeCount() + 1;
    
        new Thread(() -> {
          do {
            try {
              Thread.sleep(1000);
            }
            catch (InterruptedException ignored) {}
          } while (topLevelThreadGroup.activeCount() > activeCount);
          System.exit(0);
        }).start();
      }
    
      public static void main(String[] args) throws IOException {
        premain(null, ByteBuddyAgent.install());
        Random random = new Random();
        for (int i = 0; i < 5; i++) {
          new Thread(() -> {
            int threadDurationSeconds = 1 + random.nextInt(10);
            System.out.println("Starting thread with duration " + threadDurationSeconds + " s");
            try {
              Thread.sleep(threadDurationSeconds * 1000);
              System.out.println("Finishing thread after " + threadDurationSeconds + " s");
            }
            catch (InterruptedException ignored) {}
          }).start();
        }
      }
    }
    

    正如您所看到的,这基本上是您的示例代码,为了可读性,它被重构为几个helper方法,再加上新的watchdog方法。这很简单

    这将生成如下控制台日志:

    Starting thread with duration 6 s
    Starting thread with duration 6 s
    Starting thread with duration 8 s
    Starting thread with duration 7 s
    Starting thread with duration 5 s
    Finishing thread after 5 s
    Finishing thread after 6 s
    Finishing thread after 6 s
    Finishing thread after 7 s
    Finishing thread after 8 s
    internal httpserver has been closed.
    executor service of internal httpserver closed.