从JVM的退出机制分析Java程序的优雅关闭退出

前言

Java程序启动从main函数开始启动,是程序入口和主线程,但程序会在什么时候结束?为什么有的Java程序在启动后很快就结束了,比如HelloWorld程序,有的程序却能一直在运行,比如Tomcat启动后就一直保持进程不关闭。使用kill -15 PID关闭JVM进程究竟有没有问题?为了搞清楚这些问题,本文来详细分析一下JVM退出的机制。

JVM的退出介绍

JVM的退出可以分正常退出、异常退出和强制退出,每种退出方法的不同会产生不过的情况,汇总如下:

在这里插入图片描述

Linux操作系统关闭不完全支持优雅退出,原因是Linux关闭时先会向进程发送SIGTERM信号,等待一段时间进程还没退出时就会强制关闭进程,所以Linux只会给一定时间让进程关闭退出。

JVM可通过以下几种方式正常退出

  • 最后一个非守护线程结束。
  • JVM被中断(通过ctrl + c或发送SIGINT信号)。
  • JVM被终止(通过发送SIGTERM信号,即kill -15 PIDkill PID)。
  • 某个线程调用System.exit()Runtime.exit()

System.exit(int)被调用时,会通过security manager进行检查,是否允许以给定的状态退出,当允许时会调用Shutdown.exit()

当向JVM发送中断信号(SIGINT)或终止信号(SIGTERM),不经过security manager检查,直接调用Shutdown.exit()

Shutdown类的exit()会运行Shutdown Hook,通过一个锁防止这些Hook执行两次,在最后会调用halt(int)真正的去关闭JVM。

为什么SIGKILL(kill -9)无法实现应用的优雅关闭

SIGKILL(使用 kill -9 命令发送)无法实现应用的优雅关闭,因为它是一种无条件的终止信号,会立即终止目标进程,而不给进程执行任何清理或收尾工作的机会。这包括关闭文件、释放资源、保存状态等。简而言之,SIGKILL不会让进程有机会进行任何“优雅”的关闭操作。

相反,常规的进程终止信号 SIGTERM 允许进程执行清理工作。当你发送 SIGTERM 信号时,进程会收到这个信号并可以自行决定如何处理它,比如关闭文件、释放资源、保存状态等,然后正常退出。这种方式更为优雅,因为它给了应用程序执行关闭过程的机会。

ShutdownHook

在具体分析JVM的每种退出方式之前先来了解一下与退出机制息息相关的概念:ShutdownHook(关闭钩子)。

ShutdownHook(关闭钩子)是一个已经初始化但尚未启动的线程。当虚拟机开始其关闭步骤时,它会以某种未指定的顺序启动所有已注册的关闭钩子,并让它们并发运行。当所有钩子都完成后,如果启用了退出时的清理(finalization-on-exit),那么它会运行所有尚未调用的终结器。最后,虚拟机将停止。关闭钩子应该尽快完成它们的工作。当一个程序调用exit时,期望是虚拟机会迅速关闭并退出。当虚拟机因外部因素(如用户中断或系统事件)而终止时,关闭钩子提供了一个机会来执行一些清理工作或保存状态,但同样应该尽快完成。

以下是一个简单的Shutdown Hook栗子:

public class SimpleShutdownHookTest {public static void main(String[] args) {MyHook myHook1 = new MyHook("hook-1");MyHook myHook2 = new MyHook("hook-2");Runtime.getRuntime().addShutdownHook(myHook1);Runtime.getRuntime().addShutdownHook(myHook2);System.exit(0);  //系统退出,会启动Shutdown Hook线程}static class MyHook extends Thread {public MyHook(String name) {super.setName(name);}public void run() {try {System.out.println("do shutdown " + Thread.currentThread().getName());Thread.sleep(10 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}}}

ShutdownHook是实现程序优雅退出的关键,提供了一种方让开发者回收资源、关闭句柄、结束任务等工作。

Java程序优雅退出触发的场景和处理ShutdownHook的过程归纳起来如下图:

在这里插入图片描述


System.exit(int)处理过程

Java虚拟机规范有描述到JVM的退出:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.7

5.7. Java Virtual Machine ExitThe Java Virtual Machine exits when some thread invokes the exit method of class Runtime or class System, or the halt method of class Runtime, and the exit or halt operation is permitted by the security manager.In addition, the JNI (Java Native Interface) Specification describes termination of the Java Virtual Machine when the JNI Invocation API is used to load and unload the Java Virtual Machine.

这段话翻译为中文:

JVM在某个线程调用Runtime类或System类的exit方法,或者调用Runtime类的halt方法,并且这些exit方法或halt方法操作被安全管理器允许时,JVM退出。
此外,JNI(Java Native Interface)规范描述了在使用JNI调用API加载和卸载Java虚拟机时,Java虚拟机的终止情况。

后半段JNI这段话的意思是使用JNI调用本地库的方法时,这个方法里面包含有加载或退出虚拟机的逻辑。

总结一下就是调用以下三个方法之一会使JVM退出:

  • System.exit(int)
  • Runtime.exit(int)
  • Runtime.halt(int)

其中System.exit(int)*调用的是Runtime.exit(int),这两个是同样的效果

public final class System {//省略代码...public static void exit(int status) {Runtime.getRuntime().exit(status);}
}

Runtime.exit(int):

public class Runtime {/*** Terminates the currently running Java virtual machine by initiating its* shutdown sequence.  This method never returns normally.  The argument* serves as a status code; by convention, a nonzero status code indicates* abnormal termination.** <p> The virtual machine's shutdown sequence consists of two phases.  In* the first phase all registered {@link #addShutdownHook shutdown hooks},* if any, are started in some unspecified order and allowed to run* concurrently until they finish.  In the second phase all uninvoked* finalizers are run if {@link #runFinalizersOnExit finalization-on-exit}* has been enabled.  Once this is done the virtual machine {@link #halt* halts}.** <p> If this method is invoked after the virtual machine has begun its* shutdown sequence then if shutdown hooks are being run this method will* block indefinitely.  If shutdown hooks have already been run and on-exit* finalization has been enabled then this method halts the virtual machine* with the given status code if the status is nonzero; otherwise, it* blocks indefinitely.** <p> The <tt>{@link System#exit(int) System.exit}</tt> method is the* conventional and convenient means of invoking this method. <p>** @param  status*         Termination status.  By convention, a nonzero status code*         indicates abnormal termination.** @throws SecurityException*         If a security manager is present and its <tt>{@link*         SecurityManager#checkExit checkExit}</tt> method does not permit*         exiting with the specified status** @see java.lang.SecurityException* @see java.lang.SecurityManager#checkExit(int)* @see #addShutdownHook* @see #removeShutdownHook* @see #runFinalizersOnExit* @see #halt(int)*/public void exit(int status) {SecurityManager security = System.getSecurityManager();if (security != null) {security.checkExit(status);}Shutdown.exit(status);}
}

上面exit方法的源码上的注释:

  • 传入参数status为0时,是正常退出
  • 传入参数status不0时,为异常退出

JVM的关闭步骤包含两个步骤:

  1. 运行已经注册的ShutdownHook,它们被无序的执行直到完成。
  2. 如果用setRunFinalizersOnExit设置为true,在关闭之前将会继续调用所有未被调用的 finalizers 方法。
class Shutdown {static void exit(int status) {boolean runMoreFinalizers = false;synchronized (lock) {if (status != 0) runFinalizersOnExit = false;switch (state) {case RUNNING:       /* Initiate shutdown */state = HOOKS;break;case HOOKS:         /* Stall and halt */break;case FINALIZERS:if (status != 0) {/* Halt immediately on nonzero status */halt(status);} else {/* Compatibility with old behavior:* Run more finalizers and then halt*/runMoreFinalizers = runFinalizersOnExit;}break;}}if (runMoreFinalizers) {runAllFinalizers();halt(status);}synchronized (Shutdown.class) {/* Synchronize on the class object, causing any other thread* that attempts to initiate shutdown to stall indefinitely*///开始序列sequence();//强制终止当前正在运行的Java虚拟机。//这个方法接受一个整数参数作为退出状态码,表示程序的退出状态。halt(status);}}private static void sequence() {synchronized (lock) {DestroyJavaVM initiates the shutdown sequence//防在止DestroyJavaVM开始关闭序列步骤后,另一个线程调用exit造成两次运行if (state != HOOKS) return;}runHooks();boolean rfoe;synchronized (lock) {state = FINALIZERS;rfoe = runFinalizersOnExit;}if (rfoe) runAllFinalizers();}private static void runHooks() {for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {try {Runnable hook;synchronized (lock) {// acquire the lock to make sure the hook registered during// shutdown is visible here.currentRunningHook = i;hook = hooks[i];}if (hook != null) hook.run();} catch(Throwable t) {if (t instanceof ThreadDeath) {ThreadDeath td = (ThreadDeath)t;throw td;}}}}
}    class ApplicationShutdownHooks {private static IdentityHashMap<Thread, Thread> hooks;static {try {//Shutdown.add(1 /* shutdown hook invocation order */,false /* not registered if shutdown in progress */,new Runnable() {public void run() {runHooks();}});hooks = new IdentityHashMap<>();} catch (IllegalStateException e) {// application shutdown hooks cannot be added if// shutdown is in progress.hooks = null;}}static synchronized void add(Thread hook) {if(hooks == null)throw new IllegalStateException("Shutdown in progress");if (hook.isAlive())throw new IllegalArgumentException("Hook already running");//防止同一个钩子多次注册if (hooks.containsKey(hook))throw new IllegalArgumentException("Hook previously registered");//增加钩子hooks.put(hook, hook);}static void runHooks() {Collection<Thread> threads;synchronized(ApplicationShutdownHooks.class) {threads = hooks.keySet();hooks = null;}for (Thread hook : threads) {hook.start();}for (Thread hook : threads) {try {hook.join(); //等待钩子线程执行完成} catch (InterruptedException x) { }}}}

System.exit(int)对于关闭钩子的处理时序如下图:

在这里插入图片描述


非守护线程运行完成退出JVM

Shutdown.shutdown()的JavaDoc提到当最后一个非守护线程完成,本地DestroyJavaVM程序会调用Shutdown.shutdown();与Shutdown.exit(int)不同的是Shutdown.shutdown()不会真正去终止JVM,而是由DestroyJavaVM程序终止。

    /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon* thread has finished.  Unlike the exit method, this method does not* actually halt the VM.*/static void shutdown() {synchronized (lock) {switch (state) {case RUNNING:       /* Initiate shutdown */state = HOOKS;break;case HOOKS:         /* Stall and then return */case FINALIZERS:break;}}synchronized (Shutdown.class) {sequence();   //开始序列}}

来测试一下这个说法,把调试断点放在Shutdown.shutdown()第一行,运行一个最简单的main函数,在main函数这个唯一的非守护线程结束后,断点会运行到Shutdown.shutdown(),验证了这个说法。

在这里插入图片描述

类似的说法在线程类ThreadsetDaemon(..)方法JavaDoc也有提到:当JVM只有守护线程时,JVM会退出。

public class Thread implements Runnable {/*** Marks this thread as either a {@linkplain #isDaemon daemon} thread* or a user thread. The Java Virtual Machine exits when the only* threads running are all daemon threads.*/public final void setDaemon(boolean on) {checkAccess();if (isAlive()) {throw new IllegalThreadStateException();}daemon = on;}}

最后一个非守护线程结束后JVM关闭的流程图:

在这里插入图片描述

下面介绍一下用户线程和守护线程

Java线程分为两类:

  • 1、用户线程(非守护线程)
  • 2、守护线程(后台线程)

守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程都是守护线程。与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了,所以结束所有用户线程的运行,就可以使JVM关闭限出

可以使用jstack -l PID查看线程有没有daemon修饰判断是用户线程还是守护线程。

在这里插入图片描述

来盘点一下让用户线程退出的方法:

1、调用线程的stop()方法(已废弃)

直接退出线程,因为太暴力会产生不可知的结果该方法已废弃。

2、调用线程的interrupt()方法

需要注意的是线程的interrupt()方法不会直接停止线程的运行,需要在interrupt方法后出现的情况在程序自行通过编码结束。当调用线程的interrupt()方法根据以下两种情况出现不同结果:

  • 2.1 当使用interrupt()方法去打断处于阻塞状态的线程时,会抛出InterruptedException异常,而不会更新打断标记,因此,虽然被打断,但是打断标记依然为false。

Thread#isInterrupted()方法可返回打断标记

线程阻塞的情况有以下这些:

 * @see     java.lang.Object#wait()* @see     java.lang.Object#wait(long)* @see     java.lang.Object#wait(long, int)* @see     java.lang.Thread#sleep(long)* @see     java.lang.Thread#sleep(long)* @see     java.util.concurrent.locks.Condition.await
  • 2.2 当使用interrupt()方法去打断正在运行线程时,被打断的线程会继续运行,但是该线程的打断标记会更新,更新为true,因此可以根据打断标记来作为判断条件使得线程停止。线程是否打断的方法为isInterrupted()

须注意的是调用线程的interrupt()方法并不会停止和关闭线程,程序自行根据打断标记或InterruptedException异常自行结束线程的运行

下面是一个interrupt非守护线程后通过判断线程中断状态结束程序运行的例子:

public class ThreadExitTest {public static void main(String[] args) {Thread t  = new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 100; i++) {System.out.println("Task " + i);if (Thread.currentThread().isInterrupted()) {//如果线程状态为中断,退出循环System.out.println("Thread interrupted! Exiting loop.");return;}Thread.sleep(1000); // 模拟执行任务的耗时}} catch (InterruptedException e) {System.out.println("Thread interrupted! Exiting thread.");// 设置线程的中断状态,以确保线程可以正确退出// 如果捕获异常后其它事情可做,也可以直接在此处returnThread.currentThread().interrupt();}}});t.setDaemon(false);t.start();// 让主线程等待一段时间后中断子线程try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}t.interrupt();}}

再来看看最简单的HelloWorld程序:

public class HelloWorld {public static void main(String[] args) {System.out.println("hello world!");}
}

这是main函数只有一行打印输出控制台的代码,根据上面的理论,就很容易解析为什么HelloWorld程序在打印完hello world!后进程就会退出。

首先当HelloWorld程序启动后,JVM只有一个用户线程main线程,当执行打印的代码后,main线程的任务已经运行完毕,紧接下来的是main线程的结束。当main线程结束后,JVM已经没有用户线程,JVM随之退出。

下面再来看看另一个栗子,起一个子线程,子线程睡眠60秒。

import java.util.concurrent.TimeUnit;public class JvmExistWhenNonDaemonThreadRunning {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(TimeUnit.SECONDS.toMillis(60));} catch (InterruptedException e) {e.printStackTrace();}}});t.setDaemon(false);t.start();System.out.println("non daemon thread has started...");}
}

启动后用以下命令来查询JVM的非守护线程

$ jstack -l 46596 | awk '/tid/ && !/daemon/ {print $0; for(i=1;i<=10;i++) {getline; print}}'

在这里插入图片描述

咦!怎么跟上面讲的不一样,除了子线程"Thread-0"外,还有"DestroyJavaVM"、“VM Thread”、“GC task thread#0 (ParallelGC)”、"VM Periodic Task Thread"等非守护线程。当子线程"Thread-0"运行完毕后,还有这几个非守护线程,这样是不是导致JVM没法退出?

这里引申出另外一个知识点,在main函数结束后,JVM会自动启动一个DestroyJavaVM线程,该线程会等待所用户线程结束后退出(即只剩下daemon 线程、DestroyJavaVM线程自己、VM Thread、VM Periodic Task Thread、GC线程等系统非守护线程,整个虚拟机就退出,此时守护线程被终止)。由此可知这些系统非守护线程并不会影响所有非守护线程结束后JVM的关闭。

系统非守护线程说明
DestroyJavaVM在JVM中所有其他非守护线程全部结束后负责销毁虚拟机。DestroyJavaVM线程在JVM的生命周期中扮演着非常重要的角色,确保资源得到正确的清理和释放。
VM Thread这个线程等待在 JVM 到达安全点进行操作时出现,该线程执行的操作包括“stop the world”的垃圾收集、线程堆栈dump、线程挂起和偏向锁。
VM Periodic Task ThreadVM Periodic Task Thread是JVM中的一个特殊线程,主要负责执行一些周期性的后台任务,包括垃圾回收、性能监控、统计信息收集等。
GC task thread#0 (ParallelGC)并行垃圾回收器, 使用java启动参数-XX:+UseParNewGC时使用这个垃圾回收器。

思考一下问题,上面那个子线程睡眠的例子在运行时正常关闭JVM会出现问题吗

正常关闭的所包含场景可以回看本文第一张配图

答案是会出现问题的。因为JVM退出的所有的清理和关闭钩子都没有对这个睡眠线程作处理,这个线程其实没有得到优雅的退出处理的,最后会让JVM强制关闭退出,线程由此不可控的退出。这种粗暴的退出线程处理在一些对数据保存的场景是不可接受的,比如先将数据保存到数据库,然后更新缓存这两个步骤,如果在第一步保存数据到数据库完成后就线程就被强制退出了,导致数据库和缓存的不一致。

非守护线程的优雅关闭

JVM所有优雅退出的情况都会在退出的时候调用关闭钩子,所以可以用上面介绍到的关闭钩子去实现,以下是一个通过关闭钩子中断任务线程的栗子。每次开始下一次任务时任务线程会根据线程状态是否中断来进行继续下一个任务或结束线程的运行,当JVM退出运行关闭钩子时,中断线程,任务线程的状态设置为中断,任务线程结束运行。

public class JvmThreadElegantExit {public static void main(String[] args) {//任务线程Thread taskThread = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {try {if (Thread.currentThread().isInterrupted()) {System.out.println("task thread is interrupt, exit now...");break;}System.out.println("task " + i + "has done..."); //模拟一次任务处理Thread.sleep(1000);} catch (InterruptedException e) {Thread.currentThread().interrupt(); //将线程状态设为中断}}}});taskThread.start();//关闭钩子Thread shutdownHook = new Thread(new Runnable() {@Overridepublic void run() {//中断任务线程taskThread.interrupt();}});Runtime.getRuntime().addShutdownHook(shutdownHook);//让主线程等待一段时间后关闭JVMtry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.exit(0);  //这行也可去掉,改为用kill -15 PID退出JVM}
}
线程池ThreadPoolExecutor的关闭

线程池的工作线程默认为非守护线程,其中的核心线程(corePoolSize)空闲时默认不会关闭退出,提交一个任务创建工作线程后不对线程池作操作的话,工作线程会一直保持存活,我们的预期是工作完成后JVM自动退出的,但实际情况是和预期不一致。

这个是一个线程池工作完成后JVM进程一直存活不会退出的栗子:

public class ThreadPoolExecutorKeepAliveExample {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque());threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 10; i++) {System.out.println("sub job " + i + " had done");Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("all job had done...");}}););}}

上面是一个线程池里循环执行N个子任务的栗子,在程序启动后对JVM发送SIGTERM信号可以使工作线程关闭,但在Shutdown Hook线程运行完毕后就强制关闭JVM,没有给工作线程优雅关闭的时机,工作线程在工作中时被强制关闭可能导致任务执行不完整。

如果在JVM退出的时候优雅的关闭?

可以在Shutdown Hook里调用线程池的shutdown()方法并使用awaitTermination(..)等待工作线程完成工作。

public class ThreadPoolExecutorElegantShutdown {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println("job begin...");try {for (int i = 0; i < 10; i++) {System.out.println("job processing " + ((i + 1) * 10) + "%");Thread.sleep(2000);}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("job had done...");}});//关闭钩子线程Thread hookThread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("shutdown hook begin...");//关闭线程池threadPoolExecutor.shutdown();try {//等待30秒使工作线程完成threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS);System.out.println("shutdown hook end...");} catch (InterruptedException e) {e.printStackTrace();}}});//注册关闭钩子Runtime.getRuntime().addShutdownHook(hookThread);}
}

JVM启动后,可以用以下命令发送SIGTERM信号:

jps -l | grep ThreadPoolExecutorElegantShutdown | awk '{print $1}' | xargs kill

程序控制台的输出:

job begin...
job processing 10%
job processing 20%
job processing 30%
shutdown hook begin...
job processing 40%
job processing 50%
job processing 60%
job processing 70%
job processing 80%
job processing 90%
job processing 100%
job had done...
shutdown hook end...

可以看出JVM进程在kill命令后工作线程的任务还是继续工作直至完成。

这里用threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)使Shutdown Hook线程等待30秒,在上面Systen.exit(int)方法介绍到DestroyVM线程在Shutdown Hook线程启动后会调用Shutdown Hook线程的join()方法等待Shutdown Hook线程完成,如果这里只等待5秒而线程池工作线程还没有结束的话,Shutdown Hook线程结束后DestroyVM就会关闭JVM,也会导致线程池工作线程中断,换句话来说就是只会等待timeout时间让工作线程完成工作。要解决在等待timeout时间后工作线程还没结束的问题,可以把等待的timeout时间设置更长一点,如果线程池工作线程结束的快,不会多浪费时间等待在设定的timeout,在线程池所有工作线程完成后线程池状态变为TERMINATED便会唤醒等待。

计划线程池ScheduledThreadPoolExecutor的关闭

计划线程池ScheduledThreadPoolExecutor和ThreadPoolExecutor一样使用shutdown()关闭,但区别就是计划线程池可以根据业务需要设置参数决定shutdown()后是否要继续运行任务:

  • executeExistingDelayedTasksAfterShutdown:是否在shutdown后继续运行延迟任务
  • continueExistingPeriodicTasksAfterShutdown:是否在shutdown后继续运行周期性任务

参考:

https://juejin.cn/post/7274046488752586811?from=search-suggest

https://stackoverflow.com/questions/32315589/what-happens-when-the-jvm-is-terminated

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/752272.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

odoo17开发教程(8):设置界面UI的字段属性

目录 添加字段 给字段设置只读和不可拷贝 给字段添加默认值 保留字段 本节目标&#xff1a;在本文末尾&#xff0c;售价(selling price)应为只读值&#xff0c;卧室数量(bedrooms)和可用日期(availability date)应为默认值。此外&#xff0c;在复制记录时&#xff0c;售价和…

【解读】保障软件供应链安全:SBOM推荐实践指南(含指南获取链接)

2023年11底&#xff0c;美国NSA&#xff08;National Security Agency&#xff09;、CISA&#xff08;Cybersecurity and Infrastructure Security Agency&#xff09;等多个政府机构部门组成的ESF&#xff08;Enduring Security Framework&#xff0c;持久安全框架&#xff09…

OGRE Pittfals Design proposal for Ogre 2.0

OGRE Pittfals & Design proposal for Ogre 2.0

Python的网络爬虫介绍与实战

Python的网络爬虫基础介绍与实战 定义流程包和函数静动态网页爬虫实战红牛分公司&#xff1f;二手房数据&#xff08;静态网页&#xff09;豆瓣读书&#xff08;动态网页&#xff09; 定义 网络爬虫是按照一定的规则&#xff0c;自动地抓取万维网&#xff08;www&#xff09;信…

rust引用本地crate

我们可以动态引用crate&#xff0c;build时从crate.io下载&#xff0c;但可能因无法下载导致build失败。首次正常引用三方crate&#xff0c;build时自动下载的crate源码&#xff0c;我们将其拷贝到固定目录中&#xff1b; build后可在RustRover中按住Ctrl键&#xff0c;在crat…

图解Kafka架构学习笔记(一)

本文参考尚硅谷大数据技术之Kafka。 消息队列 &#xff08;1&#xff09;点对点模式&#xff08;一对一&#xff0c;消费者主动拉取数据&#xff0c;消息收到后消息清除&#xff09; 点对点模型通常是一个基于拉取或者轮询的消息传送模型&#xff0c;这种模型从队列中请求信息…

基于Spark的气象数据处理与分析

文章目录 一、实验环境二、实验数据介绍三、数据获取1.观察数据获取方式2.数据爬取3.数据存储4.数据读取5.数据结构6.爬虫过程截图 四、数据分析1.计算各个城市过去24小时累积雨量2.计算各个城市当日平均气温3.计算各个城市当日平均湿度4.计算各个城市当日平均风速 五、数据可视…

ARM_基础之RAS

Reliability, Availability, and Serviceability (RAS), for A-profile architecture 源自 https://developer.arm.com/documentation/102105/latest/ 1 Introduction to RAS 1.1 Faults,Errors,and failures 三个概念的区分&#xff1a; • A failure is the event of devia…

保研|资讯|夏令营|3.31截止!香港城市大学市场营销学系首届学术暑期夏令营

香港城市大学市场营销学系首届学术暑期夏令营 1 项目简介 我们的博士项目致力为未来营销科学和工商管理学界培养一流学者和行业领袖。博士项目一般为期四到六年&#xff0c;允许本科生直接申请。课程包括实证分析模型&#xff0c;消费者行为研究&#xff0c;博弈微观模型&…

从零开始学习数据结构与算法:Python实现【第139篇—Python实现】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 从零开始学习数据结构与算法&#xff1a;Python实现 数据结构与算法是计算机科学中至关重要…

Redis7学习记录(1)

Redis入门概述 Redis介绍 Redis&#xff1a;REmote Dictionary Server&#xff08;远程字典服务器&#xff09; 官网&#xff1a;Redis 中文官网&#xff1a;Redis中文网 Redis的功能及应用 redis是基于内存的KV键值对内存数据库。 redis是key-value数据库&#xff08;NO…

[VCTF2024纳新赛]-PWN:ezhp_code解析

查看保护 查看ida 简单来说就是创建堆块和删除堆块而已&#xff0c;创建堆块的函数附带有写入函数。 但这里要注意一个程序里面的特殊的地方 在我们创建堆块时&#xff0c;程序会先创建一个0xa0大小堆块&#xff0c;并且这个地方还有个特殊的check_handle函数&#xff0c;如果…

测试人员Bug书写规范

&#x1f4cb; 个人简介 作者简介&#xff1a;大家好&#xff0c;我是凝小飞&#xff0c;软件测试领域作者支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 在测试人员日常工作中&#xff0c;关于bug的编写和定义是一个比较经常的工作&#xff0c;如果bug编写描…

深度学习1650ti在win10安装pytorch复盘

深度学习1650ti在win10安装pytorch复盘 前言1. 安装anaconda2. 检查更新显卡驱动3. 根据pytorch选择CUDA版本4. 安装CUDA5. 安装cuDNN6. conda安装pytorch结语 前言 建议有条件的&#xff0c;可以在安装过程中&#xff0c;开启梯子。例如cuDNN安装时登录 or 注册&#xff0c;会…

十 超级数据查看器   讲解稿    详情5  隐藏功能

十 超级数据查看器 讲解稿 详情5 隐藏功能 app下载地址 百度手机助手 下载地址4 ​ 讲解稿全文&#xff1a; 第5讲 界面的隐藏功能 设置这些功能是为了方便用户操作 首先是编辑栏。长按一可以在一栏和二栏之间做切换&#xff0c;这个一、二、左箭头、右箭头&#xf…

(官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell

前言 查了很多资料都不靠谱&#xff0c;在安装过程中遇到很多的坑&#xff0c;mangoDB 服务重视起不来&#xff1b;出现了很多难以解决的报错&#xff0c;现在把安装过程中遇到的问题&#xff0c;和如何闭坑说一下&#xff0c;很多时候都是准备工作不足导致的&#xff1b;很多方…

利用生成式人工智能进行功能管理测试

就 DevOps 而言&#xff0c;生成式 AI与功能管理测试的新兴集成标志着一次重大演变。我们将认真研究这项技术如何彻底改变我们创建测试环境的方式。 使用人工智能生成测试使我们能够模拟大量的用户场景和环境&#xff0c;这意味着我们可以开发和部署不仅好而且很棒的功能&…

Ubuntu系统下C语言开发环境搭建与使用教程

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

graylog API 弱密码

graylog web 页面密码设置 输入密码&#xff1a;获取sha256加密后密码 echo -n "Enter Password: " && head -1 </dev/stdin | tr -d \n | sha256sum | cut -d" " -f1vi /etc/graylog/server/server.conf #修改以下配置 root_usernameroot ro…

C#,人工智能,机器学习,聚类算法,训练数据集生成算法、软件与源代码

摘要:本文简述了人工智能的重要分支——机器学习的核心算法之一——聚类算法,并用C#实现了一套完全交互式的、可由用户自由发挥的,适用于聚类算法的训练数据集生成软件——Clustering。用户使用鼠标左键(拖动)即可生成任意形状,任意维度,任意簇数及各种数据范围的训练数…