当前位置: 首页 > 科技观察

从Hotspot虚拟机角度分析Java线程启动

时间:2023-03-12 18:24:13 科技观察

基本概念Java线程实际上是映射到操作系统的内核线程,所以Java线程基本上是由操作系统管理的。在Linux系统中,线程和进程是用同一个结构体来描述的,但是进程有自己独立的地址空间,同一个进程的多个线程共享资源。简要说明:本文基于openjdk1.8进行各线程状态的切换条件及调用方式如下图所示:线程有以下几种状态。Java线程状态在Thread.State枚举中定义。代码如下publicenumState{//新建,未启动NEW,//运行在jvm中,也可能正在等待操作系统的其他资源RUNNABLE,//阻塞,并等待监视器锁BLOCKED,//线程中waiting状态,等待另一个线程执行特定的操作WAITING,//有限的等待时间,可以设置最大等待时间TIMED_WAITING,//结束TERMINATED;}线程创建继承Thread类,代码如下:classPrimeThreadextendsThread{longminPrime;PrimeThread(longminPrime){this.minPrime=minPrime;}publicvoidrun(){//computeprimeslargerthanminPrime...}}//启动线程PrimeThreadp=newPrimeThread(143);p.start();实现Runable接口,代码如下(通常推荐这种方式):线程PrimeRunp=newPrimeRun(143);newThread(p).start();热点源码JNI机制JNI是JavaNativeInterface,它提供了几个API来实现Java与其他语言(主要是C和C++)的通信。JNI的适用场景当我们有一些老的库,已经用C语言写的,移植到Java上是浪费时间,而JNI可以支持Java程序与C语言写的库交互,所以没有必要移植。或者与硬件和操作系统进行交互,提高程序性能等,都可以使用JNI。需要注意的一件事是,您需要确保本机代码可以在任何Java虚拟机环境中工作。JNI的副作用一旦使用JNI,Java程序将失去Java平台的两个优势:程序不再跨平台。要跨平台,程序必须在不同的系统环境中以本地语言部分进行编译和配置。程序不再是绝对安全的,原生代码使用不当可能会导致整个程序崩溃。一个普遍的规则是对本地方法的调用应该集中在少数几个类中,这样可以降低Java与其他语言之间的耦合度。例如,这方面的操作很多。可以参考以下资料https://www.runoob.com/w3cnote/jni-getting-started-tutorials.html。通过start方法启动线程并通知执行。在start方法内部,本地方法start0()被调用。从这个方法我们可以分析出JVMforThread的底层实现。publicsynchronizedvoidstart(){//判断线程状态if(threadStatus!=0)thrownewIllegalThreadStateException();//加入groupgroup.add(this);booleanstarted=false;try{//启动线程start0();started=真;}finally{try{if(!started){group.threadStartFailed(this);}}catch(Throwableignore){/*donothing.Ifstart0threwaThrowablethenitwillbepassedupthecallstack*/}}}privatenativevoidstart0();start0()是本地方法,我们遵循JNI具体规范可以在hotspot的虚拟源码中找到函数java_lang_Thread_start0。定义如下:/**Class:java_lang_Thread*Method:start0*Signature:()V*/JNIEXPORTvoidJNICALLJava_java_lang_Thread_start0(JNIEnv*,jobject);通过注解Method:start0,可以猜测到jvm内部可能也存在start0方法,于是再次查找这个方法,找到了Thread.c文件。可以看到里面有一个Java_java_lang_Thread_registerNatives()方法,用来初始化Thread.java和其他方法的绑定,并且在Threa.java的第一个静态块中调用这个方法,保证这个方法在类Loading是第一个调用的方法。此本机方法的目的是向JVM注册其他本机方法。代码如下所示:staticJNINativeMethodmethods[]={{"start0","()V",(void*)&JVM_StartThread},{"stop0","("OBJ")V",(void*)&JVM_StopThread},{"isAlive","()Z",(void*)&JVM_IsThreadAlive},{"suspend0","()V",(void*)&JVM_SuspendThread},{"resume0","()V",(void*)&JVM_ResumeThread},{"setPriority0","(I)V",(void*)&JVM_SetThreadPriority},{"yield","()V",(void*)&JVM_Yield},{"sleep","(J)V",(void*)&JVM_Sleep},{"currentThread","()"THD,(void*)&JVM_CurrentThread},{"countStackFrames","()I",(void*)&JVM_CountStackFrames},{"interrupt0","()V",(void*)&JVM_Interrupt},{"isInterrupted","(Z)Z",(void*)&JVM_IsInterrupted},{"holdsLock","("OBJ")Z",(void*)&JVM_HoldsLock},{"getThreads","()["THD,(void*)&JVM_GetAllThreads},{"dumpThreads","(["THD")[["STE,(void*)&JVM_DumpThreads},{"setNativeName","("STR")V",(void*)&JVM_SetNativeThreadName},};#undefTHD#undefOBJ#undefSTE#undefSTRJNIEXPORTvoidJNICALLJava_java_lang_Thread_registerNatives(JNIEnv*env,jclasscls){(*env)->RegisterNatives(env,cls,方法,ARRAY_LENGTH(方法));}回到我们的start0方法,这个时候我们会在文件/hotspot/src/share/vm/prims/jvm.cpp中寻找JVM_StartThread方法:JVM_ENTRY(void,JVM_StartThread(JNIEnv*env,jobjectjthread))JVMWrapper("JVM_StartThread");JavaThread*native_thread=NULL;//当我们抛出异常时,我们不能阻止Threads_lock,//由于排序问题。示例:我们可能需要在构建异常时获取//堆锁。boolthrow_illegal_thread_state=false;//我们必须释放{//Threads_lockbeforewecanpost/inThread/inThread事件:确保:++线程和操作系统Threadststructuresaren'tfreedbefore//weoperate.MutexLockermu(Threads_lock);//1.判断Java线程是否启动,如果已经启动,则抛出异常if(java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread))!=NULL){throw_illegal_thread_state=true;}else{//2。如果不创建,就会创建一个线程jlong??size=java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));size_tsz=size>0?(size_t)size:0;//虚拟机创建一个JavaThread,它创建一个操作系统线程内部,然后关联Java线程}}}if(throw_illegal_thread_state){THROW(vmSymbols::java_lang_IllegalThreadStateException());}assert(native_thread!=NULL,"Startingnullthread?");if(native_thread->osthread()==NULL){//Nooneshouldholdarereferencetothe'native_thread'.deletenative_thread;if(JvmtiExport::should_post_resource_exhausted()){JvmtiExport::post_resource_exhausted(JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR|JVMTI_RESOURCE_EXHAUSTED_THREADS,"unabletocreatenewnativethread");}THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),"unabletocreatenewnativethread");}//设置线程状态为RunnableThread::start(native_thread);我们来看看JVM_ENDJavaThread类的构造方法,他是使用os::create_thread函数创建JavaThread::JavaThread(ThreadFunctionentry_point,size_tstack_sz):Thread(){if(TraceThreadEvents){tty->print_cr("creatingthread%p",this);}initialize();_jni_attach_state=_not_attaching_via_jni;set_entry_point(entry_point);os::ThreadTypethr_type=os::java_thread;thr_type=entry_point==&compiler_thread_entry?os::compiler_thread:os::java_thread;//创建Java线程对应的内核行os::create_thread(this,thr_type,stack_sz);_safepoint_visible=false;}os:create_thread其实主要用于支持跨平台线程创建,以Linux为例(hotspot/src/os/linux/vm/os_linux.cpp):boolos::create_thread(Thread*thread,ThreadTypethr_type,size_tstack_size){//...//创建OSThread内核线程对象OSThread*osthread=newOSThread(NULL,NULL);//绑定线程->set_osthread(osthread);pthread_ttid;//pthread_create用于为linuxapi创建线程intret=pthread_create(&tid,&attr,(void*(*)(void*))java_start,thread);//...returntrue;}我们可以通过ubantu控制台manpthread_create查询接口信息通过From查询文档查看文档,我们可以了解到,当执行pthread_create函数,创建线程时,会调用第三个参数传递的回调函数intret=pthread_create(&tid,&attr,(void*()(void))java_start,线);这里是java_start函数//Threadstartroutineforallnewlycreatedthreadsstaticvoid*java_start(Thread*thread){//主要是调用Thread的run方法thread->run();return0;}在thread.cpp中,JavaThread::run方法最后调用thread_main_inner方法://ThefirstroutinecalledbyanewJavathreadvoidJavaThread::run(){//Wecallanotherfunctiontodotherestsowearesurethatthestackaddressesused//fromtherewillbelowerthanthestackbasejustcomputedthread_main_inner();//Note,threadisnolongervalidatthispoint!}在thread_main_inner方法内,在调用咱们之前创建JavaThread对象的时候传递进来的entry_point方法:voidJavaThread::thread_main_inner(){if(!this->has_pending_exception()&&!java_lang_Thread::is_stillborn(this->threadObj())){{ResourceMarkrm(this);this->set_native_thread_name(this->get_thread_name());}HandleMarkhm(this);//调用entry_point方法this->entry_point()(this,this);}DTRACE_THREAD_PROBE(stop,this);this->exit(false);deletethis;}通过上面的代码我们可以看到,首先创建了一个JavaThread对象,然后传入thread_entry方法。//JVM_StartThread创建一个操作系统线程,并执行thread_entry函数staticvoidthread_entry(JavaThread*thread,TRAPS){HandleMarkhm(THREAD);Handleobj(THREAD,thread->threadObj());JavaValueresult(T_VOID);//Thrad.start()调用java.lang.Thread的run方法classJavaCalls::call_virtual(&result,obj,KlassHandle(THREAD,SystemDictionary::Thread_klass()),vmSymbols::run_method_name(),vmSymbols::void_method_signature(),THREAD);}我们来看看Java中线程类publicvoidrun(){if(target!=null){//Thread.run()调用Runnable.run()target.run();}}参考https://www.jb51.net/article/216231.htmhttps://blog.csdn.net/u013928208/article/details/108051796https://www.cnblogs.com/whhjava/p/9916626.htmlhttps://www.runoob.com/w3cnote/jni-getting-started-tutorials.htmlhttps://developer.51cto.com/art/202011/632936.htmhttps://blog.csdn.net/weixin_34384681/article/details/90660510https://blog.csdn.net/weixin_30267697/article/details/95994035https://zhuanlan.zhihu.com/p/33830504