从头开始学习->JVM(六):线程和JVM的关系

在这里插入图片描述

前言

在前面关于类加载器的文章中,我们可以看到,类加载器通过java类的字节码文件,创建出了Class对象,并且通过加载-验证-准备-解析-初始化等一系列流程,将java类加载到JVM中,直到这个时候,JVM才会将主导权移交给java应用程序。

这个时候,我们的java程序就可以跑动起来了。

当java程序开始跑动起来的时候,线程和对象也纷纷产生,随着程序的跑完,也纷纷消亡。对象的产生和消亡,无疑是和JVM中的运行时数据区相关的,但是今天我们先不去深究这方面,我们先对线程来进行深一层次的了解和解析。

正文

JVM,是一种虚拟机,也就是说,它必然是要运行在一个操作系统上,不管是linux系统还是windows系统。

而操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经调用过start()方法且还未结束的java.lang.Thread类的实例就代表着一个线程。

我们注意到Thread类与大部分的Java类库API有着显著差别,它的所有关键方法都被声明为Native。在 Java类库API中,一个Native方法往往就意味着这个方法没有使用或无法使用平台无关的手段来实现。

Thread类的native方法源码如下:

public
class Thread implements Runnable {
    //确保调用registerNatives方法是初始化的时候要做的第一件事。
    private static native void registerNatives();
    
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

    //这个方法,是Thread.Start()的时候会调用的方法
    private native void start0();
    
    //这个方法是测试这个线程是否已经被中断了
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    //测试此线程是否活动。
    public final native boolean isAlive();
    
    //对该线程中的堆栈帧数进行计数。(线程必须挂起。)
    @Deprecated
    public native int countStackFrames();
    
    //断言当前线程是否已经持有指定的锁:
    public static native boolean holdsLock(Object obj);
    
    private native static StackTraceElement[][] dumpThreads(Thread[] threads);
    
    private native static Thread[] getThreads();
    
    private native void setPriority0(int newPriority);
    
    //这是Thread.stop()方法会内部调用的方法
    private native void stop0(Object o);
    
    private native void suspend0();
    
    private native void resume0();
    
    private native void interrupt0();
    
    private native void setNativeName(String name);
}

从这些代码中,我们可以看出,java的线程,不是由java程序本身创建,而是由底层的操作系统创建。而java程序对线程的操作调用,也不是java程序直接操作调用,而是间接的通过调用操作系统提供的接口来调用。

而这一切,和我们的java虚拟机没有什么关联。

当然,这句话的意思是指我们的Java虚拟机当然是提供了支持操作系统调度的模型和方案的,但是java虚拟机也只是做了这个事情而已,等于是做了一个转接的操作,把java程序关于线程的创建和操作的动作,转到操作系统那里。

操作系统的线程实现方式

而底层的操作系统,实现线程主要有三种方式:使用内核线程实现(1:1实现),使用用户线程实现(1:N实现), 使用用户线程加轻量级进程混合实现(N:M实现)。详细如下:

1.内核线程

使用内核线程实现的方式也被称为1:1实现。内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。

每个内核线程可以视 为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就称为多线程内核 (Multi-Threads Kernel)。

程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1 的关系称为一对一的线程模型。

2.用户线程:

使用用户线程实现的方式被称为1:N实现。

广义上来讲,一个线程只要不是内核线程,都可以认为是用户线程(User Thread,UT)的一种,因此从这个定义上看,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,因此效率会受到限制,并不具备通常意义上的用户线程的优点。

狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到用户线程的存在及如何实现的。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。

用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要由用户程序自己去处理。

java之前支持过用户线程,但是后面又放弃了它。

3. 混合实现:

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。

在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,这大大降低了整个进程被完全阻塞的风险。

JVM的线程模型

从JDK 1.3起,“主流”平台上的“主流”商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生线程模型来实现,即采用1:1的线程模型(内核线程)。

也就是说,JVM 中的 Java 线程与原生操作系统线程有直接的映射关系

当java线程本地存储、缓冲区分配、同步对象、栈、程序计数器等准备好以后,就会创建一个操作系统原生线程。操作系统负责调度所有线程,并把它们分配到任何可用的 CPU 上。当原生线程初始化完毕,就会调用 Java 线程的 run() 方法。当线程结束时,会释放原生线程和 Java 线程的所有资源。

那么,java程序的运行,都涉及了哪些线程呢,如何细分这些线程呢?

首先,JVM会找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出(一个java程序的启动,会生成一个JVM进程)。除了main方法生成的主线程外,这个java程序也生成了其他的线程,这也就是我们在编程过程中使用的所谓的多线程编程生成的线程。

当然,除了这些线程外,如果你使用jconsole或者是任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用 public static void main (String []) 的main线程以及所有这个main线程自己创建的线程。

这些主要的后台系统线程在Hotspot JVM里主要是以下几个:

  1. 虚拟机的线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括“stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
  2. 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。
  3. GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。
  4. 编译线程:这种线程在运行时会将字节码编译成到本地代码。
  5. 信号调度线程:这种线程接收信号井发送给JVM, 在它内部通过调用适当的方法进行处理。

当JVM里面的堆满足一下两点的时候,就是JVM的安全点:

1.堆内存的变化是受控制的,最好所有的线程全部停止。

2.堆中的对象是已知的,不存在不再使用的对象很难找到或者找不到即堆中的对象状态都是可知的。

而这些所有线程的组合,就是整个java程序和JVM组成的一个生态了。

总结

很难去形容线程在JVM中的处境,因为本身这个点也只是在说明一下,线程其实和JVM的关系是非常紧密相连的。

但是从本文来看,线程本身,和我们的JVM是不相连的,相连的是线程在运行过程中对象的产生和销毁的这个过程,才和我们的JVM息息相关。

因此,JVM和线程的这种关系,我们也确实要去了解一下,去探索一下。当然,本文对它们之间的关系还是了解的比较浅薄的,希望后面有时间再去搞清楚明白点。

就到这里吧。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值