什么是守护线程?
守护线程就是在所有用户线程结束之后,所有守护线程的终止,进而虚拟机进程就会退出。
呃, 这里为什么把用户线程加粗呢? 想一个问题,如果用户线程代表 除了守护线程之后的所有线程包括(我们自己创建的以及jvm里面c++代码创建的),守护线程会自己终止吗?
先不说代码(毕竟jvm 层的c++代码可太难了),就说一般的idea如果让你来设计 如何实现?
方式1: 所有守护线程都会循环有个全局的标志位判断,比如一个静态变量,在最后一个用户线程退出的时候 把找个标志位复制为true, 这个时候守护线程就执行return。
这种方式可取吗? 那也太麻烦了, 用户要么写守护线程的时候自己来判断这个标记位,或者使用动态代理的方式为runnable 织入横切逻辑 ,这也坑定不可取的
方式2: 线程是由操作系统提供的创建函数的(我想称为函数对于c 面向过程的语言, 像c++的类的函数,我想称为行为或者方法)。那么操作系统也应该会提供停止正在运行的线程的函数,比如我们只要把第一次调操作系统创建线程得到的tid线程id,传给操作系统的停止线程的函数就可以。
那要实现方式二的关键点在哪里: 要执行的关闭线程集合从哪里来? 谁来关闭这些线程集合? 基于这两个点可以考虑把 守护线程和用户线程的线程集合存在一个公共的存储能力比如 数组、链表等, 这里推荐链表, 好的, 那接着 谁来执行这个关系的行为呢? ok 那就可以来一个 专门的用户线程来最后执行 对守护线程的线程集合的关闭操作。
那么接着 问题又来了? 这个专门清楚 守护线程的用户线程我们称之为DestroyJavaVM 吧, 那这个线程的创建时机是什么时候?
我们知道 java程序 也就是一个class文件 一般如果要可执行都得又一个 mian方法。这个main方法是jvm在 底层内部启动一个线程 通过jni的方式来回调 java的main方法, 所以一般jvm中一个java程序运行都会有一个线程来执行main方法,所以这个main方法执行结束之后 可以创建一个DestroyJavaVM 线程来执行 销毁守护线程的逻辑。但是问题又来了,
如果这时候还有别的我们自己定义的用户线程呢?所以DestroyJavaVM 在执行销毁逻辑之前得对 用户线程集合进行判断,如果大于1(他自己和别人)就进行wait, 如果另一个用户线程退出的时候判断用户线程数为1那么就notify把DestroyJavaVM唤醒执行销毁逻辑进行。
动手验证一下:
通过idea的debug查询DestroyJavaVM线程
🆗 在想一下, 这个时候 执行main方法的mian线程还没有结束,这个时候创建DestroyJavaVM 是没必要的,浪费资源,所以jvm就把DestroyJavaVM线程的创建留在了 main线程退出之后, 那么怎么验证呢? 我们需要new 一个thread.
🆗 现在不就出现了。 哈哈哈。 但是注意没有 是 RUNNING状态, 按道理来说应该 是wait呀,🆗,继续验证,我们别使用debug , 使用sleep和打印看看。
public class DaemonThreads {
/**
*
* @author: puhg_sinosoft
* @date: 2022/6/25 1:17
* @param
*/
public static void printDestoryVMState(){
try {
TimeUnit.SECONDS.sleep(3);
Thread thread = Thread.currentThread();
ThreadGroup threadGroup = thread.getThreadGroup();
int i = threadGroup.activeCount();
Thread[] threads = new Thread[i];
threadGroup.enumerate(threads);
Arrays.stream(threads).filter(x-> x.getName().equals("DestroyJavaVM")).forEach(x-> System.out.println(x.getState()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
printDestoryVMState();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(6);
printDestoryVMState();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println("mian线程结束");
}
}
呃。。。。说实话还是 打印出 RUNABLE, 不应该是 WAIT吗。
好吧 看 大概 看一个,盲猜一下 。c++ 代码。
在这里插入图片描述
呃 jvm代码注释 白嫖大佬,得, 别管我看得看不懂就是 猜, 这里是 jvm c程序的 main方法入口。
呃如果要看如果调用起来的请看 java.cass 文件的main方法如何调用起来
最后回调完java的mian方法之后,线程结束mian方法退出,就要启动DestroyJavaVM线程了。
int JNICALL
JavaMain(void * _args) //这个方法使用操作系统的线程, 用来回调java的main线程
{
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;
RegisterThread();
/* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) { //初始化虚拟机,内部创建虚拟机并开启main线程
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
if (showSettings != NULL) {
ShowSettings(env, showSettings);
CHECK_EXCEPTION_LEAVE(1);
}
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
/* If the user specified neither a class name nor a JAR file */
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}
FreeKnownVMs(); /* after last possible PrintUsage() */
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}
/* At this stage, argc/argv have the application's arguments */
if (JLI_IsTraceLauncher()){
int i;
printf("%s is '%s'\n", launchModeNames[mode], what);
printf("App's argc is %d\n", argc);
for (i=0; i < argc; i++) {
printf(" argv[%2d] = '%s'\n", i, argv[i]);
}
}
ret = 1;
/*
* Get the application's main class.
*
* See bugid 5030265. The Main-Class name has already been parsed
* from the manifest, but not parsed properly for UTF-8 support.
* Hence the code here ignores the value previously extracted and
* uses the pre-existing code to reextract the value. This is
* possibly an end of release cycle expedient. However, it has
* also been discovered that passing some character sets through
* the environment has "strange" behavior on some variants of
* Windows. Hence, maybe the manifest parsing code local to the
* launcher should never be enhanced.
*
* Hence, future work should either:
* 1) Correct the local parsing code and verify that the
* Main-Class attribute gets properly passed through
* all environments,
* 2) Remove the vestages of maintaining main_class through
* the environment (and remove these comments).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/
mainClass = LoadMainClass(env, mode, what); //加载main函数所在的类文件
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* In some cases when launching an application that needs a helper, e.g., a
* JavaFX application with no main method, the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);//JavaFX没有MainClass而是通过ApplicationClass启动的,这里获取ApplicationClass
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*
* PostJVMInit uses the class name as the application name for GUI purposes,
* for example, on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
PostJVMInit(env, appClass, vm);//将ApplicationClass作为应用名传给JavaFX本身,比如作为主菜单
/*
* The LoadMainClass not only loads the main class, it will also ensure
* that the main method's signature is correct, therefore further checking
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");//获取main方法的方法ID
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc); //解析main方法的参数
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */
(*env)->CallStaticVoidMe thod(env, mainClass, mainID, mainArgs); //执行main方法
/*
* The launcher's exit code (in the absence of calls to
* System.exit) will be non-zero if main threw an exception.
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();//main方法执行完毕,JVM退出,包含两步:(*vm)->DetachCurrentThread,让当前Main线程同启动线程断联,然后创建一个新的名为DestroyJavaVM的线程,让该线程等待所有的非后台进程退出,并在最后执行(*vm)->DestroyJavaVM方法。
}
bool Threads::destroy_vm() {
JavaThread* thread = JavaThread::current();
#ifdef ASSERT
_vm_complete = false;
#endif
// Wait until we are the last non-daemon thread to execute 等待自己是最后一个非守护线程条件
{ MutexLocker nu(Threads_lock);
while (Threads::number_of_non_daemon_threads() > 1 ) //非守护线程大于1,则一直等待(等到等于1的时候往下执行)
// This wait should make safepoint checks, wait without a timeout,
// and wait as a suspend-equivalent condition.
//
// Note: If the FlatProfiler is running and this thread is waiting
// for another non-daemon thread to finish, then the FlatProfiler
// is waiting for the external suspend request on this thread to
// complete. wait_for_ext_suspend_completion() will eventually
// timeout, but that takes time. Making this wait a suspend-
// equivalent condition solves that timeout problem.
//
Threads_lock->wait(!Mutex::_no_safepoint_check_flag, 0,
Mutex::_as_suspend_equivalent_flag); //这里阻塞的线程会被remove方法里的notify唤醒
}
// Hang forever on exit if we are reporting an error.
if (ShowMessageBoxOnError && is_error_reported()) {
os::infinite_sleep();
}
os::wait_for_keypress_at_exit();
if (JDK_Version::is_jdk12x_version()) {
// We are the last thread running, so check if finalizers should be run.
// For 1.3 or later this is done in thread->invoke_shutdown_hooks()
HandleMark rm(thread);
Universe::run_finalizers_on_exit();
} else {
// run Java level shutdown hooks
thread->invoke_shutdown_hooks();//钩子代码是在这里触发的(设置时间长点,守护线程可以再存活一会儿)
}
before_exit(thread);
thread->exit(true);//destroyJavaVM线程退出,至此java中不能执行任何代码
// Stop VM thread.
{
// 4945125 The vm thread comes to a safepoint during exit.
// GC vm_operations can get caught at the safepoint, and the
// heap is unparseable if they are caught. Grab the Heap_lock
// to prevent this. The GC vm_operations will not be able to
// queue until after the vm thread is dead.
// After this point, we'll never emerge out of the safepoint before
// the VM exits, so concurrent GC threads do not need to be explicitly
// stopped; they remain inactive until the process exits.
// Note: some concurrent G1 threads may be running during a safepoint,
// but these will not be accessing the heap, just some G1-specific side
// data structures that are not accessed by any other threads but them
// after this point in a terminal safepoint.
MutexLocker ml(Heap_lock);
VMThread::wait_for_vm_thread_exit();
assert(SafepointSynchronize::is_at_safepoint(), "VM thread should exit at Safepoint");
VMThread::destroy();
}
线程退出的执行:
void Threads::remove(JavaThread* p) {//移除线程
// Extra scope needed for Thread_lock, so we can check
// that we do not remove thread without safepoint code notice
{ MutexLocker ml(Threads_lock);
assert(includes(p), "p must be present");
JavaThread* current = _thread_list;
JavaThread* prev = NULL;
while (current != p) { //线程以链表形式存在,判断当前线程是否要删除的线程,如果不是,后移,直到找到为止
prev = current;
current = current->next();
}
if (prev) {//被删除线程是否有前驱节点,如果有,则直接摘除,让前驱节点指向被删除节点的next节点(跨过被删除节点)
prev->set_next(current->next());
} else { //如果没有前驱节点,说明被删除节点就是第一个节点,直接_thread_list指向它的next节点
_thread_list = p->next();
}
_number_of_threads--; //线程数量减一
oop threadObj = p->threadObj();
bool daemon = true;
if (threadObj == NULL || !java_lang_Thread::is_daemon(threadObj)) {
_number_of_non_daemon_threads--; //非守护线程数量减1
daemon = false;
// Only one thread left, do a notify on the Threads_lock so a thread waiting
// on destroy_vm will wake up.
if (number_of_non_daemon_threads() == 1) //当非守护线程数量为1时,唤醒在destroy_vm方法等待的线程(其实最后那个非守护线程就是销毁线程)
Threads_lock->notify_all();
}
ThreadService::remove_thread(p, daemon); //移除掉线程
// Make sure that safepoint code disregard this thread. This is needed since
// the thread might mess around with locks after this point. This can cause it
// to do callbacks into the safepoint code. However, the safepoint code is not aware
// of this thread since it is removed from the queue.
p->set_terminated_value();
// Now, this thread is not visible to safepoint
p->set_safepoint_visible(false);
// once the thread becomes safepoint invisible, we can not use its per-thread
// recorder. And Threads::do_threads() no longer walks this thread, so we have
// to release its per-thread recorder here.
MemTracker::thread_exiting(p);
} // unlock Threads_lock
// Since Events::log uses a lock, we grab it outside the Threads_lock
Events::log(p, "Thread exited: " INTPTR_FORMAT, p);
}