os 4 线程

线程类型

  • 内核线程

    创建 / 撤消由内核内部需求决定,不需和用户进程联系起来

    共享内核全局数据,具有自己的内核堆栈

    能单独被调度且用标准的内核同步机制,可被单独分配到一个处理器上运行

    调度不需要经过态的转换并进行地址空间的重新映射,内核线程间上下文切换比在进程间做上下文切换快得多

  • 轻量级进程 LWP

    内核支持的用户线程,在一个单独的进程中提供多线程控制

    这些轻量级进程被单独的调度,可以在多个处理器上运行,每一个轻量级进程都被绑定在一个内核线程上

    独立调度并且共享地址空间和进程中的其它资源

    但每个 LWP 都应该有自己的程序计数器、寄存器集合、核心栈和用户栈

  • 用户线程

    如通过线程库实现,提供同步和调度的方法

    可在没有内核参与下创建、释放和管理,不消耗内核资源,省去大量系统开销

    用户线程的上下文可以在没有内核干预的情况下保存和恢复

    每个用户线程都可以有自己的用户堆栈,一块用来保存用户级寄存器上下文以及如信号屏蔽等状态信息的内存区

    库通过保存当前线程的堆栈和寄存器内容载入新调度线程的那些内容来实现用户线程之间的调度和上下文切换

    内核仍然负责进程的切换,因为只有内核具有修改内存管理寄存器的权力

    用户线程不是真正的调度实体,内核对它们一无所知,而只是调度用户线程下的进程或者轻量级进程,这些进程再通过线程库函数来调度它们的线程

    当一个进程被抢占时,它的所有用户线程都被抢占,当一个用户线程被阻塞时,它会阻塞下面的轻量级进程,如果进程只有一个轻量级进程,则它的所有用户线程都会被阻塞

pthread 实现

  • Linux2.4 前:LinuxThread

  • Linux2.5 后:NPTL


fork && clone

  • 函数: fork,拷贝父进程而创建一个新进程
  • 系统调用:clone,为进程创建提供

fork & pthread_create

fork & pthread_create 都调用的 clone

clone系统调用 ,参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等,指定了克隆时需要拷贝的东西

  • fork

    调用 clone 时不设置 CLONE_VM

    内核看来就是产生了两个拥有不同内存空间的进程

  • pthread_create

    调用 clone 时设置了 CLONE_VM

    内核看来就是产生了两个拥有相同内存空间的进程

通过 clone 的参数共享进程资源,创建出的就是 LWP

所以用户态创建一个新线程,内核态就对应生成一个新进程

经典图片

抽象

abstract

毕加索的牛抽象过程,一步一步隐藏细节

jvm-1

虚拟机

Java 程序以虚拟机为中介,运行于各平台上,实现跨平台的特性

  • Sun Classic JVM,纯解释型 JVM 实现

  • Exact VM

    现代高性能虚拟机的雏形

  1. 两级即时编译器
  2. 编译器和解释器混合工作模式
  3. 准确式内存(虚拟机可以知道内存位置的数据具体类型,reference还是value,GC时准确判断可达 )
  • Sun 收购 Longview Technologies的 HostSpot JVM,成 JDK1.3 及之后的默认虚拟机

    热点代码探测能力

  • BEA JRockit

  • IBM J9

    AOT编译

    class data sharing(一些核心类在每个JVM间共享)

  • oracle jdk8 的 HostSpot

    以前的HotSpot VM与JRockit VM的合并版

    JRockit 一些有价值的功能在HotSpot重新实现一遍。移除PermGen、Java Flight Recorder、jcmd等都属于合并项目的一部分

  • Zing VM

    gc C4: The Continuously Concurrent Compacting Collector

    利用之前收集到的profile数据,引导JVM在启动后快速达到稳定的高性能水平,减少启动后从解释执行到JIT编译的等待时间

    找出代码热点到对象分配监控、锁竞争监控等

jvm-2 GC

手动GC 易产生内存泄漏,直到内存溢出

垃圾回收算法

引用计数
  • 无法处理循环引用
  • 每次引用产生和消除,都伴随一次加或减法运算,有一定性能影响
标记清除

现代垃圾回收算法的思想基础

回收后的空间是不连续的,之后在不连续的内存空间中分配的效率要低于连续的空间

经典标记复制

解决标记清除算法内存回收后不连续的问题而衍生

  • 可用内存折半
  • 存活对象较多时,要复制大量对象,对效率也有影响
jvm

JVM 很多垃圾回收器都用到标记复制思想,为解决经典标记复制算法中内存折半问题,JVM 将内存划分为 3 个区

  • eden
  • from
  • to

from 和 to 大小相同,地位相等,且可角色互换

from 和 to 也被称为 survivor 空间,即幸存者空间,用于存放未被回收的对象

GC 时,从根节点出发,标记 eden 和 from 区所有存活对象,然后将这些对象复制到 to 区。而下次进行垃圾回收时,to 和 from 区角色互换,将 to 区 和 eden 区的幸存对象复制到 from 区

通过这种方法,解决经典标记复制算法中内存折半问题

通过调整 eden 区,from 区和 to 区的比例,来控制内存的利用率

标记压缩

新生代: 复制算法的高效性建立在存活对象少、垃圾对象多的前提下

老年代: 大部分对象都是存活对象,再用复制算法, 复制成本高

和标记清除一样, 可达标记,但之后, 不只清理未标记的对象, 而将所有的存活对象压缩到内存的一端,之后, 清理边界外所有的空间

既避免了碎片的产生, 又不需要两块相同的内存空间,性价比高

所以标记复制算法和标记压缩算法分别代表了空间换时间和时间换空间思想,并分别活跃在各自擅长的场景中

jvm-3 锁

基本作用是保护临界区资源不会被多个线程同时访问而受到破坏

通过锁, 可以让多个线程排队, 一个一个地进入临界区访问目标对象, 使目标对象的状态总是保持一致

synchronized 方法和 synchronized 代码块:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Test {

    public void synchronizedBlock() {
        synchronized (this) {
            System.out.println("synchronizedMethod");
        }
    }

    public synchronized void synchronizedMethod() {
        System.out.println("synchronizedMethod");
    }
}

从字节码中,编译器会将同步代码块用 monitorentermonitorexit 包裹

同步方法,通过函数的的 access flags 进行标识,当执行有 synchronized 标识的函数时,最后也会调用 monitorentermonitorexit 基本一样的逻辑

jvm-4 class

Class 文件结构

类似于 C 结构体描述,统一用无符号整数作为基本数据类型

u1、u2、u4、u8 表示无符号单字节、2 、4 、8 字节,字符串用 u1 数组进行表示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

os 2- 内存管理

一般单个虚拟地址空间就比物理内存要大,另每个进程都有自身的虚拟地址空间

内核和 CPU 必须考虑如何将实际可用的物理内存映射到虚拟地址空间的区域。一般用页表来为物理地址分配虚拟地址

虚拟地址关系到进程的用户空间和内核空间,而物理地址用来寻址实际可用的内存

物理内存页经常被称为页帧,而页一般用来指虚拟地址空间的页


内存分配管理方式

  • 连续(块式

    为用户程序分配一个连续的内存空间

  • 非连续(页式 和 段式

    允个程序使用的内存分布在离散或者说不相邻的内存中

  1. 块式管理

    远古时代将内存分为几个固定大小的块,每个块中只包含一个进程

    如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,称为碎片

  2. 页式管理

    相比块式管理的划分力度更大,提高内存利用率,减少碎片

    通过 页表 对应逻辑地址和物理地址

  3. 段式管理

    页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义

    段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多

    最重要的是段是有实际意义的,如,主程序段 MAIN、子程序段 X、数据段 DATA 及栈段 STACK 等

段页式管理机制

结合段式管理和页式管理的优点。把主存先分成若干段,每个段又分成若干页,也就是说 段与段之间以及段的内部的都是离散的

os 3- 内存管理算法

假设这是一段连续的页框,阴影部分表示已经被使用的页框,现在需要申请一个连续的5个页框

这段内存上不能找到连续的5个空闲的页框,就会去另一段内存上去寻找5个连续的页框,久而久之形成了页框的浪费