📜  原始线程和非原始线程之间的区别

📅  最后修改于: 2021-09-16 10:27:40             🧑  作者: Mango

当操作系统启动一个新进程时,只有一个线程。这是将进入应用程序本机 main函数的线程,然后它可能会产生其他线程。 “原始线程”是第一个单线程;它不是一个正式的名词,而是一个应该从上下文中推断出其含义的词。
可以理解,新建立的线程可能比程序开始时已经在使用的线程更容易更改。
当一个进程形成时,操作系统内核会创建一个原始线程作为第一个线程。
在Java SE 6 之前,用户程序对原始线程属性几乎没有或根本没有控制权,这些属性在线程建立后无法更改。
在任何程序/库代码有机会运行之前,内核会创建一个原始线程。它的堆栈大小和位置可能与程序启动的其他线程的堆栈大小和位置有很大不同。

从原始线程创建 JVM,然后在该线程上运行Java代码会导致一系列问题:

  1. 可执行文件的 PE 标头控制 Windows 上原始线程堆栈的大小。用户没有动态调整它的机制。因此 -Xss 不适用于原始线程。
  2. Solaris/Linux 的原始线程堆栈的大小由 ulimit -s 控制,它通常很大 (8M)。作为补偿,我们在堆栈中央放置了一个保护页,以人为地降低堆栈大小。但是,这可能会导致本地程序出现问题。
  3. 为原始线程设置保护页面是有风险的。与其他线程不同,原始线程堆栈可以按需增加。 getrlimit() 将 ulimit 值通知 VM,这是最高限制,但不是实际堆栈大小。可能发生的情况是 VM 将保护置于理论限制,但由于应用程序不需要那么多堆栈,操作系统会出于其他原因重用未使用的空间(例如 malloc)(其他线程不会发生这种情况) .在堆栈和它的保护页之间,我们最终得到了一个 C 堆。
  4. 为了检查堆栈溢出,Linux VM 敲击低于当前 SP 的堆栈地址。由于内核中内置的安全机制,如果它们出现在原始线程中,这将导致 SEGV。在 Linux VM 上通过手动增加堆栈解决了该问题。但是,当 VM 扩展堆栈时,可用堆栈容量会在短时间内减少到只有一页。如果在该窗口内发出信号,VM 可能没有足够的空间来处理它。
  5. 对于堆栈着色和执行屏蔽,某些 Linux 内核会随机分配每个线程的起始堆栈地址,但它们不会通知应用程序。在原始线程中,这使得很难准确识别堆栈位置和大小。 VM 需要这些信息才能正确管理堆栈溢出。我们确实有一些缓冲,这通常是足够的,但是从错误报告中可以看出,有些确实因此而导致崩溃。
  6. 在 Linux 上,没有等效的 main() 可以确定当前线程是否是原始线程,这使得编写特定代码来处理原始线程变得更加困难。

应该注意的是,某些数字,例如 Xss 最大值,受到操作系统、shell 的限制以及系统当前可用的物理和虚拟内存的严重限制。尽管物理/虚拟内存可用,但由于加载的共享对象或进程地址空间中的 dll 导致碎片化,Xmx 可能无法完全利用您的所有可用内存。由于与Web浏览器和它的dll加载进程地址空间相关的开销,-Xmx1.6M可能无法与Java插件的Windows函数。

如果你想要一个巨大的堆,应该积极考虑 64 位平台。
使用自定义启动器的 JNI 应用程序可能遵循与此基本示例相同的方法:

C++
#include 
#include 
#include 
#include 
  
#include 
  
YourJVM\* jvm;
  
JNIEnv\* create_vm()
{
    JNIEnv\* env;
    YourJVMInitArgs args;
    YourJVMOption options[1];
  
    args.version = JNI_VERSION_1_4;
    args.nOptions = 1;
    options[0].optionString = "-Djava.class.path=.";
    args.options = options;
    args.ignoreUnrecognized = JNI_FALSE;
  
    JNI_CreateYourJVM(&jvm, (void \*\*)&env, &args);
    return env;
}
  
void invoke_class(JNIEnv\* env)
{
    jclass helloWorldClass;
    jmethodID mainMethod;
    jobjectArray applicationArgs;
    jstring applicationArg0;
    char buf[128];
  
    sprintf(buf, "%d", getpid());
  
    helloWorldClass
        = (\* env)->FindClass(env, "HelloWorld");
  
    mainMethod = (\* env)->GetStaticMethodID(
        env, helloWorldClass, "main",
        "([Ljava/lang/String;)V");
  
    applicationArgs = (\* env)->NewObjectArray(
        env, 1,
        (\* env)->FindClass(env, "java/lang/String"), NULL);
    applicationArg0 = (\* env)->NewStringUTF(env, buf);
  
    (\* env)->SetObjectArrayElement(env, applicationArgs, 0,
                                    applicationArg0);
    (\* env)->CallStaticVoidMethod(
        env, helloWorldClass, mainMethod, applicationArgs);
}
  
// VM Worker Thread
void\* dowork(void\* args)
{
    JNIEnv\* env = create_vm();
    invoke_class(env);
    // Unload the VM
    if (jvm == NULL)
        exit(-1);
  
    int retval = (\* jvm)->DetachCurrentThread(jvm);
    if (retval != 0)
        exit(2);
  
    retval = (\* jvm)->DestroyYourJVM(jvm);
    if (retval != 0)
        exit(3);
}
  
int main(int argc, char \*\* argv)
{
    pthread_t tid;
    void\* status;
  
    // Create a new thread and launch the vm in that thread
    pthread_create(&tid, NULL, dowork, NULL);
  
    // Make the primordial wait until the VM worker thread
    // exits
    pthread_join(tid, &status);
    exit(0);
}


所有这些都计入差异。