Java 线程是如何映射到操作系统线程中的呢?
Follow
Dec 30, 2017 · 3 min read
JVM thread maps to OS thread is a commonly read statement. But what does it really mean?. We create Thread object in java and call its start method to start new thread. How does it start OS thread? and how the run method of the Thread object is attached to the OS thread that is executed?
Java虚拟机线程映射到操作系统线程是一个通常大家都熟知的说法。但是它真正的意义是什么呢?我们在Java环境中创建一个线程的对象,并且调用它的start()方法去启动一个新的线程。它是如何启动操作系统线程的呢?在线程对象中的run方法执行是如何依赖于操作系统线程的呢?
To explain what happens inside JVM, I have created a very small example at
https://github.com/unmeshjoshi/jvmthreads
Assume that we have to implement java.lang.Thread class ourselves. What we will need to do is as following.
我有一个非常小的例子去解释在java 虚拟机内部发生了什么,地址是:
https://github.com/unmeshjoshi/jvmthreads
假设我们必须自己实现Java.lang.thread类。我们需要做如下操作:
Its simulates Java’s Thread class. It has only two methods, start() and run(). To create a new Thread, we just need to create a new Thread object and call its start method.
它模拟了java的线程类,仅仅有两个方法,start()方法和run()方法。2️⃣去创建一个新的线程,我们只需要去创建一个线程对象然后调用它的start方法。
例子: new Thread().start()
The magic happens inside the start method, which invokes a start0 method which is declared as a native method. The ‘native’ marker tells JVM that this is a platform specific native method (written in C/C++) which needs to be invoked through java native interface. JNI is a native method interface specification for Java and it details out how native code can integrate with the JVM and vice versa. (https://docs.oracle.com/javase/9/docs/specs/jni/design.html#jni-interface-functions-and-pointers)
在调用被声明成原生方法的start()方法中发生了神奇的事情。这样本地的标记告诉JVM(java虚拟机),这是一个特定的平台的本地方法(用C语音或者C++语言编写的),需要通过Java本地的接口才能调用。JNI就是一个本地的方法接口规范,它详细的指出了本地代码是如何与JVM进行集成的,反之亦然。
Generating a header file for native method declaration.
为本地方法声明一个头文件
JDK has a tool called javah which generates a header file for classes declaring native method, which then can be used for native implementation. Try following commands
A header file for Thread class can be generated as follows.
cd src/main/cpp
javah -classpath ../../../target/scala-2.12/classes -jni com.threading.Thread
The native method looks like following
JDK 有一个被叫做javah的工具,这个就是为本地方法生成一个头文件,然后就可以被用作本地实现。尝试如下命令。
cd src/main/cpp
javah -classpath ../../../target/scala-2.12/classes -jni com.threading.Thread
这个本地方法就像下边所示:
The first argument is the JNI interface pointer as explained here. The second object jobject is handle to the Java object on which the native method is called. In this case it’s pointer to the Thread object.
第一个参数在这里的解释是JNI接口的指针。第二个对象jobject就是当本地方法被调用时候去处理的那个Java对象
在这个例子中它是这个线程对象的指针。
Native C/C++ implementation.
本地c/c++实现
We need to implement function Java_com_threading_Thread_start0. This is implemented in threading.cpp
我们需要实现函数Java_com_threading_Thread_start0。这个在threading.cpp中实现的。
Creating linux thread with pthreads.
For creating threads on linux, we need to use pthread interface. Pthread is part of POSIX standard which defines C language interface for creating and managing threads. Linux provides implementation of pthreads. The thread is created by calling pthread_create function.
对于在Linux系统中创建线程,我们需要去使用pthreads接口。pthreads是POSIX标准的一部分,定义了C语言的接口去创建和管理线程。Linux系统提供了pthreads的实现。这个线程在被pthread_create函数所调用创建。
tid is the ID of the newly created thread.
attr is set of thread attributes we need to set
thread_entry_point is pointer to function which will be called from new thread
arg_to_entrypoint is the argument to be passed to thread_entry_point
The entry point function we pass to pthread is where we should be invoking Thread object’s run method.
For accessing Thread java object from a separate linux thread, the entry point function needs to have access to JVM JNI object and global JNI reference to Java Thread object.
We do that by creating a wrapper object called JavaThreadWrapper.
tid是新创建的线程的ID。
attr是一个我们需要设置的线程属性集合
thread_entry_point是将被在新线程中调用的函数的指针
arg_to_entrypoint 是要传递给thread_entry_point的参数。
我们传递给pthread函数的输入点,那是线程对象的run方法将会被调用的地方。
从一个单独的Linux线程中访问线程Java对象,切入点的函数需要去访问JVM JNI对象,全局的JNI 引用到的Java线程对象。
我们通过创建一个名字是JavaThreadWrapper的包装对象来实现它。
The constructor of JavaThreadWrapper gets access to JVM reference and creates a global reference to java thread object.
(To know more on local vs global references have a look at
https://docs.oracle.com/javase/9/docs/specs/jni/design.html)
这个JavaThreadWrapper的构造函数访问JVM的引用,并且创建一个全局的Java线程对象的引用。
JavaThreadWrapper::JavaThreadWrapper(JNIEnv *env, jobject javaThreadObjectRef) { | |
---|---|
env->GetJavaVM(&(this->jvm)); | |
this->threadObjectRef = env->NewGlobalRef(javaThreadObjectRef); | |
} |
Calling Java Thread object’s run method.
调用Java线程对象的run方法
The entry point function when invoked in a separate thread now need to invoke java Thread object’s run method.
The code looks like following
在一个单独的线程中去调用这个输入点的函数时,就需要去调用Java线程对象中的run方法。
代码如下:
The run method is invoked through JNI as following
这个run方法通过JNI调用如下所示:
As you can see, we literally created a linux thread for every thread object created. JVM does something exactly similar.
For reference have a look at following source files in the JVM codebase. You can checkout JVM codebase from http://hg.openjdk.java.net/jdk9/jdk9.
The code for creating and managing threads is in following files.
jdk9/hotspot/src/share/vm/prims/jvm.cpp
jdk9/hotspot/src/share/vm/runtime/thread.cpp
jdk9/hotspot/src/os/linux/vm/os_linux.cpp
如你所见,我们明确的为每个Linux线程都创建了一个线程对象。JVM也是做了相似的某些操作。
参考一下jvm代码库中的一下源文件,你可以从http://hg.openjdk.java.net/jdk9/jdk9](http://hg.openjdk.java.net/jdk9/jdk9).中切出JVM 代码库。
创建和管理线程的代码在如下所列的文件中
Running sample code
运行示例代码。
Refer to https://github.com/unmeshjoshi/jvmthreads which has instructions to run the code.
请参阅https://github.com/unmeshjoshi/jvmthreads ,这里面包含了代码运行说明。