没有强大的理论支撑的代码,都是自娱自乐,架构师带你学习多线程的原理到实战项目中的高并发解决方案,闲暇之余,分享技术干货,和喜爱技术的coder们一起交流,互相学习进步
本次主要和大家分享Thread的源码,分析Thread创建都做了什么事情,通过介绍JVM内存分布了解Thread在内存中的分布情况,从而了解Thread的原理
Thread构造介绍
默认线程名词构造
如果构造函数中没有指定线程名称,线程默认线程名称的规则是什么呢?,查看JDK源码
从源码中,我们可以看到,如果没有显示的指定线程名称,线程会使用 "Thread-"作为前缀,与nextThreadNum进行组合,生成当前线程名称,nextThreadNum是static修饰的,所以,在整个JVM都是不断自增的
显示指定线程名称构造
如果线程构造中显示的指定了线程名称,线程初始化将使用指定的线程名称,查看JDK源码:
在实际开发过程中,强烈建议显示的指定业务相关的线程名称,方便后期的问题排查以及线程跟踪
线程名称是否可以修改?答案当然是可以的,可以通过setName(String name)方法进行修改,需要注意的是线程名称只能在线程启动之前修改,线程一旦启动,线程名称不能再修改
是谁来创建线程的呢?
带着疑问我们看一下创建线程的部分源码
新的线程的创建是由父线程创建的,main函数所在的线程是JVM创建
新的线程如果没有显示的指定线程组,默认会分配到父线程的线程组,同时拥有和父线程同样的优先级
默认情况下,子线程继承了父线程的守护线程属性、优先级、线程组等特性(后续详细介绍线程优先级)
Thread与JVM内存分布
多线程和虚拟机栈有直接联系,了解JVM内存分布,有助于我们更好的理解多线程的创建和销毁,在内存中到底做了哪些事情
程序计数器
程序计数器存放当前线程将要执行的字节码指令、分支、循环、跳转、异常处理等信息
程序计数器是线程私有的,各个线程之间互不影响,目的是为了在CPU时间片轮转切换上下文后能回到正确的执行位置
Java虚拟机栈
Java虚拟机与线程的生命周期相同,创建线程的时候,JVM会为当前线程创建虚拟机栈在线程里方法的执行会创建一个栈帧,主要存放局部变量表、操作栈、动态链接、方法出口等信息,调用方法其实对应栈帧在虚拟机栈中压栈和弹栈的过程
通过-xss设置虚拟机栈的大小
同等的虚拟机栈,局部变量表等占用的内存越小,则可被压入的栈帧越多,反之则被压入的栈帧越少,栈帧的大小成为虚拟机栈的宽度,栈帧的数量成为虚拟机栈的深度
线程数量计算:
Java进程的内存大小 = 堆内存 + 线程数量 * 栈内存 (粗略计算)
线程数量 = (最大地址空间 - JVM堆内存 - 系统保留内存ReservedOsMemory)/ThreadStackSize (比较精确)
32位操作系统最大元空间是2G
Linux影响线程数量的参数:
/proc/sys/kernel/threads-max、/proc/sys/kernel/pid-max、/proc/sys/vm/max_map_count
Java虚拟机栈是线程私有的
本地方法栈
本地方法栈提供了java可调用的C++方法的实现的接口,比如有我们看看到的调用JNI方法Thread类中start方法调用的start0
本地方法栈是线程私有的
堆内存
堆内存存放的几乎是java运行期间创建的所有对象,是垃圾回收的重点关照区域,又称为"GC堆"
堆内存是线程共享的
方法区
方法区主要存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,java虚拟机规范中,方法区被分为堆内存的逻辑分区,从垃圾回收的角度又称为非堆、持久代
方法区是线程共享的
Java8元空间
元空间是堆内存的一部分,JDK8中持久代内存被彻底删除,被元空间代替,JVM为每个类加载器分配一块内存块列表,进行线性分配,块的大小取决于类加载器的类型,以前是单独回收某个类,现在是发现某个类加载器具备回收条件后,将类加载器相关的元空间全部回收,可以减少内存碎片同时节省GC扫描和压缩时间
守护线程
设置线程为守护线程的方式,线程启动之前调用Thread类中的setDaemon方法,线程结束后调用会报错
IllegalThreadStateException
父线程如果是正常的线程,则子线程也是正常的线程,父线程如果是守护线程,子线程也是守护线程
JVM进程中没有一个非守护线程,JVM自动退出,守护线程具备自动结束生命周期的特点,例如垃圾回收就是守护线程,守护线程一般用来执行后台任务