首页 > 系统相关 >Java线程Hotspot线程Linux线程源码穿透

Java线程Hotspot线程Linux线程源码穿透

时间:2022-09-20 14:24:26浏览次数:87  
标签:Java Thread thread void 源码 线程 JVM java

 原理分析

首先不妨先看一副图

​编辑

通常我们在Java开发中使用线程无非就是使用Thread类提供的一些API,比如new Thread()、Thread.start() 等等方法。那么对于Linux操作系统而言是没有线程概念的,线程的概念是在glibc(操作系统API的一部分)层抽象出来的实现,是提供给Linux系统开发者使用的。目的是控制进程资源的使用。所以这里对应的线程/进程实体在如下三层都有体现:

  1. JDK层:Java线程对象。
  2. glibc层:c语言实现的对象c/c++开发者的线程对象。
  3. 内核层:进程对象task_struct,Linux任务调度的实体,在Linux只要是一个task_struct就可以调度。

 那么我们的JVM如何完成不同层线程之间的联系呢?

首先glibc层的线程对象本来就是基于内核层的进程实体task_struct抽象出来的,不用我们关心,我们在JVM层需要关心的是如何完成glibc层的线程对象与JDK层的线程对象对应关系维护。

实现:

其实对于JDK层的Java线程对象,在JVM层也有一个c语言描述的线程对象,否则JVM又该如何控制JDK层的Java线程对象呢?当然这个也可以从JDK层有本地方法看得出来,否则本地方法又该如何关联到Java线程呢?

上图中可见JDK层和JVM层都有线程对象,两者合并完成Java线程的所有功能。那么JVM层的线程对象有了,glibc层的线程对象也有了,我们需要在jvm层做个线程映射来完成两层之间的线程关联即可。

下文主要围绕,具体源码分析展开。

Java层Thread

先看个demo

public class Demo1 {

    public static void main(String[] args) {
        //1、创建线程对象
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread start");
            }
        });
        //2、启动线程
        t.start();
    }
}

节奏我们深入Thread.java来看下

java/lang/Thread.java
public class Thread implements Runnable {
    
        //本地方法,注册本地方法
         private static native void registerNatives();
    
        //静态代码代码块,调用registerNatives注册所有的本地方法
          static {
            registerNatives();
            EMPTY_STACK_TRACE = new StackTraceElement[0];
            SUBCLASS_IMPLEMENTATION_PERMISSION = new RuntimePermission("enableContextClassLoaderOverride");
        }
    
        //new Thread调用的构造函数
        public Thread(Runnable var1) {
                   this.init((ThreadGroup)null, var1, "Thread-" + nextThreadNum(), 0L);
        }    
        private void init(ThreadGroup var1, Runnable var2, String var3, long var4) {
                   this.init(var1, var2, var3, var4, (AccessControlContext)null, true);
        }

        private void init(ThreadGroup var1, Runnable var2, String var3, long var4, AccessControlContext var6, boolean var7) {
                if (var3 == null) {
                    throw new NullPointerException("name cannot be null");
                } else {
                    
                    //此处省略若干行代码……

                    //设置线程的名字
                    this.name = var3;
                    //获取当前线程(调用new Thread 的线程)
                    Thread var8 = currentThread();
                    //获取当前线程是否守护线程并设置
                    this.daemon = var8.isDaemon();
                    //获取当前线程的优先级
                    this.priority = var8.getPriority();
                   //这里的var2代表的既是线程的执行体,可以看到被保存在java线程对象的target中
                    this.target = var2;
                    //设置线程的优先级
                    this.setPriority(this.priority);
                }
        }
    
        //开始执行线程
        public synchronized void start() {
        if (this.threadStatus != 0) {
            throw new IllegalThreadStateException();
        } else {
            this.group.add(this);
            boolean var1 = false;

            try {
                //调用start0方法开始执行线程体
                this.start0();
                var1 = true;
            } finally {
                try {
                    if (!var1) {
                        this.group.threadStartFailed(this);
                    }
                } catch (Throwable var8) {
                }
            }
        }
    }
    //执行线程体
    private native void start0();
}

由以上代码可知:

1、Thread类在被加载的时候,首先会执行静态代码块,调用本地方法registerNatives去注册所有的本地方法。

2、在new Thread的过程中会执行如下核心逻辑:

​        1)设置线程的名字。

​        2)获取调用线程是否是守护线程及优先级并设置到当前线程。 

​        3)设置线程的执行体到线程对象中。

​        4)最后重新设置优先级

3、然后执行start方法执行线程体,调用本地方法start0执行线程体。

综上所述,我们可以清楚知道,本地线程的创建和java线程与本地线程的映射的逻辑是在native层完成的。据此我们主要分析两个native方法的执行逻辑:

​        1)registerNatives :所有本地方法的注册。

​        2)start0 :创建本地线程并执行java线程体。

JVM层Thread

线程接口的注册

据此我们需要翻越jdk的源码,我用的openjdk1.8源码。

那么在java层线程对应的实现在 Thread.java,同样在JVM层叫Thread.c

openjdk/jdk/src/share/native/java/lang/Thread.c


#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define STR "Ljava/lang/String;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

static JNINativeMethod methods[] = {
    {"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},
};

#undef THD
#undef OBJ
#undef STE
#undef STR

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env,  cls, methods, ARRAY_LENGTH(methods));
}

 

启动线程的入口JVM_StartThread


由以上代码可以看到所有Thread.java中的本地方法都在这里注册。好,接下来我们看start0的本地实现JVM_StartThread

openjdk/hotspot/src/share/vm/prims/jvm.cpp


JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
{
    //此处省略若干代码 ……
    
    //创建本地线程,用与包装java线程与内线线程
      native_thread = new JavaThread(&thread_entry, sz);
    
    //此处省略若干代码 ……
   
  }

//启动本地线程
  Thread::start(native_thread);

JVM_END

JVM层的JavaThread实体

继续追进 new JavaThread(&thread_entry, sz);

openjdk/hotspot/src/share/vm/runtime/thread.cpp


JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
  Thread()

{
       //此处省略若干代码 ……
      
  //此时调用和os平台相关的线程创建实现
  os::create_thread(this, thr_type, stack_sz);
      
       //此处省略若干代码 ……

}

JVM映射JavaThread与glibc的Thread

继续追追进去 os::create_thread(this, thr_type, stack_sz);  我们选择linux的线程实现

openjdk/hotspot/src/os/linux/vm/os_linux.cpp


bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {


     //此处省略若干代码 ……
    
  // Allocate the OSThread object 创建 os线程对象
  OSThread* osthread = new OSThread(NULL, NULL);
  if (osthread == NULL) {
    return false;
  }

     //此处省略若干代码 ……
    
  // set the correct thread state
  osthread->set_thread_type(thr_type);

     //此处省略若干代码 ……
    
  // Initial state is ALLOCATED but not INITIALIZED
  osthread->set_state(ALLOCATED);

     //此处省略若干代码 ……
    
  thread->set_osthread(osthread);

     //此处省略若干代码 ……
    
  // 初始化线程创建参数
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

     //此处省略若干代码 ……
    
    pthread_t tid;
    //到这里我们可以看到熟悉的glibc线程创函数pthread_create,到此真正的native线程就创建出来了,javastart是线程的执行入口函数
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);

     //此处省略若干代码 ……
    
    pthread_attr_destroy(&attr);

    // 设置native线程与osthread设置关联
    osthread->set_pthread_id(tid);
    
     //此处省略若干代码 ……

}

启动JavaThread

继续追进 java_start

openjdk/hotspot/src/os/linux/vm/os_linux.cpp


static void *java_start(Thread *thread) {
  {
       //此处省略若干代码 ……
      
    // 设置线程状态为INITIALIZED
    osthread->set_state(INITIALIZED);
    sync->notify_all();

       //此处省略若干代码 ……
      
    // 注意这里由于创建线程的时候设置了状态为INITIALIZED,此处会一直阻塞,这也就是java线程创建完以后的状态,并不是立马执行
    while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }
  }

     //此处省略若干代码 ……
    
  // 执行线程,此处会执行java线程体的内容也就是Runnable的实现
  thread->run();
  return 0;
}

何时解除sync->wait

好了,到这里java线程的创建与执行基本已经分析完了,但是还差一点,那就是上面的代码的阻塞何时解除?因为解除阻塞以后才能执行thread->run,进而执行Runnable的的实现,真正执行我们的java代码。要回答这个问题我们需要往前看,还记得本地线程创建的入口吗?

openjdk/hotspot/src/share/vm/prims/jvm.cpp


JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;
{
    //此处省略若干代码 ……
    
    //创建本地线程,用与包装java线程与内线线程
      native_thread = new JavaThread(&thread_entry, sz);
    
    //此处省略若干代码 ……
   
  }

//启动本地线程
  Thread::start(native_thread);

JVM_END

这里的  Thread::start(native_thread);  我们追进去看一下

openjdk/hotspot/src/share/vm/runtime/thread.cpp


void Thread::start(Thread* thread) {  
trace("start", thread);  
if (!DisableStartThread) {   
 if (thread->is_Java_thread()) {
     //设置线程的状态为RUNNABLE解除阻塞执行run
     java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(), 
                                      java_lang_Thread::RUNNABLE);   
         }    
 os::start_thread(thread); 
  }    
}

这里的Thread::start会解除线程的阻塞进而执行run方法。

结语

由于此篇文章仅对从Java线程到本地线程作讨论叙述,不涉及线程的其他方面知识,所有代码均来自openjdk,在此只是作简要删减,目的是为了让读者更容易理解,不喜勿喷,欢迎点赞收藏~~~。

标签:Java,Thread,thread,void,源码,线程,JVM,java
From: https://www.cnblogs.com/suse123/p/16710855.html

相关文章

  • java 取上月份最后一天日期8位
    1/**2*获取上个月的最后一天23点59分59秒的时间3*/4privateStringgetBeforeLastMonthdate()throwsException{5SimpleDateForma......
  • JavaScript 声明提升
    函数及变量的声明都将被提升到函数的最顶部。变量可以在使用后声明,也就是变量可以先使用再声明。声明提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最......
  • 避免Java异常栈被吞掉
    ThecompilerintheserverVMnowprovidescorrectstackbacktracesforall"cold"built-inexceptions.Forperformancepurposes,whensuchanexceptionisth......
  • js精度计算问题解决,Jsutil库源码
    为什么会存在浮点数计算精度丢失问题,这个原因不再过多赘述; JS中如何解决精度计算问题,大不部分人都知道升幂再降幂的解决方案; 但是如果直接升幂也会出现精度问题,且需......
  • JavaScript 错误 - throw、try 和 catch
    try 语句测试代码块的错误。catch 语句处理错误。throw 语句创建自定义错误。finally 语句在try和catch语句之后,无论是否有触发异常,该语句都会执行。语法tr......
  • JavaScript:David Flanagan 的权威指南
    JavaScript:DavidFlanagan的权威指南JavaScript:DavidFlanagan的权威指南[JavaScript:权威指南-Twos表达式是可以被评估以产生值的短语。语句是以…结尾的完整句子......
  • java蓝途之接口
    接口接口:关键字interface接口的特点:1:接口不能直接创建对象,但是可以创建数组2:接口通常是用来被类实现的,使用关键字implements,实现之后,需要重写接口中的所有抽......
  • JavaScript 正则表达式
    正则表达式:      -admin@atguigu.com      -正则表达式用于定义一些字符串的规则,        计算机可以根据正则表达式,来检查一......
  • javascript中的控制语句
      1、forin用来遍历对象的,可以在属性未知的情况下遍历对象  2、forof:遍历数组和其他迭代对象,如:Map,Set等  3、trycatchfinally,捕获异常,无论异常是否发......
  • java中IO流字符的读入与写出操作
    importjava.io.*;importorg.junit.Test;publicclassFileReaderWriterTest{//写@TestpublicvoidtestWriter(){FileWriterfw=null......