在hotspot的具体线程有
系统线程 | 功能 |
---|---|
虚拟机线程(VM thread) | 等待 JVM 到达安全点操作出现。这些操作必须要在独立的线程里执行,因为当堆修改无法进行时,线程都需要 JVM 位于安全点。这些操作的类型有:stop-the-world 垃圾回收、线程栈 dump、线程暂停、线程偏向锁(biased locking)解除 |
周期性任务线程 | 定时器事件(中断)用来调度周期性操作的执行 |
GC 线程 | JVM 中不同的垃圾回收 |
编译器线程 | 将将字节码动态编译成为本地平台相关的机器码 |
信号分发线程 | 接收发送到 JVM 的信号并调用适当的 JVM 方法处理 |
JVM的线程分为线程私有(程序计数器、虚拟机栈、本地方法区)、线程共享(【JAVA 堆、方法区)和直接内存
程序计数器:当前线程所执行的字节码的行号指示器,记录的是虚拟机字节码指令的地址(当前指令的地址),如果为native方法,则为空
虚拟机栈:
本地方法区:虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务(区分下虚拟机栈和本地方法栈的不同。)
堆:是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域
方法区:即永久区用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
堆从垃圾回收角度分为新生代和老年代
新生代:存放新生对象。一般占据堆的1/3 。频繁创建对象,新生代触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo三个区
MinorGC 的过程:复制算法
老年代:生命周期长的内存对象,垃圾回收不会频繁执行
MajorGC的过程:标记清除算法
永久代:内存的永久保存区域。存放 Class 和 Meta(元数据)的信息,但随着Class的增多而胀满,会抛出OOM异常
引用计数法:通过引用计数来判断一个对象是否可以回收。如果一个对象没有引用,可能不太可能用到
可达性分析:通过一系列的“GC roots”对象作为起点搜索,在“GC roots”和一个对象之间没有可达路径,但不可达对象不代表为垃圾可回收。不可达对象要变为可回收起码两次可能面临回收
标记回收的对象,清除该对象变为未使用的内存空间
但会造成内存碎片化严重,后续大对象利用的空间小
为了解决内存碎片化严重
按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉
但缺点是内存被压缩挤压,都堆积在一起,虽然没有了碎片化,但是对象如果增多的话,复制的效率会减少
结合以上两种算法
标记后将存活对象移向内存的一端,清除端边界外的对象
目前JVM常用算法
根据对象存活的不同生命周期将内存划分为不同的域。老年代和新生代,老年代回收少,新生代大量回收,根据不同的区域确定不同算法
新生代与复制算法:Copying 算法
一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中
老年代与标记复制算法:Mark-Compact 算法
对象存活率高, 不必进行内存复制, 且直接腾出空闲内存
将整个堆空间划分为连续的不同小区间, 每个小区间独立使用,独立回收。根据停顿时间,每次合理回收几个区间
强引用:把一个对象赋给一个引用变量,这个引用变量就是一个强引用。该对象以后永远都不会被用到 JVM 也不会回收
软引用:当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收
弱引用:它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存
虚引用:跟踪对象被垃圾回收的状态。不能单独使用,必须和引用队列联合使用
新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程
在 Server 模式下:老年代中使用 CMS 收集器的后备垃圾收集方案
CMS的工作机制:
初始标记-并发标记-重新标记-并发清除
在不用暂停线程来看,CMS 收集器的内存回收和用户线程是一起并发地执行
改进CMS收集器
优点:
G1收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率
阻塞 IO 模型:读写数据过程中会发生阻塞现象
data = socket.read();
//如果数据没有就绪,就会一直阻塞在 read 方法
非阻塞 IO 模型:在非阻塞 IO 模型中,用户线程需要不断地询问( CPU 占用率非常高)内核数据是否就绪,也就说非阻塞 IO不会交出 CPU,而会一直占用 CPU。轮询实在用户线程下进行,效率低
while(true){
data = socket.read();
if(data!= error){
//处理数据
break;
}
}
多路复用 IO 模型:
信号驱动 IO 模型:
在信号驱动 IO 模型中,当用户线程发起一个 IO 请求操作,会给对应socket 注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作
异步 IO 模型:只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了
区别:在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用 IO 函数进行实际的读写操作;而在异步 IO 模型中,收到信号表示 IO 操作已经完成,不需要再在用户线程中调用 IO 函数进行实际的读写操作
NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector
Channel:
io中的流是单向的,而通道是双向的(可读可写)
实现方式有:
Buffer:
缓冲区,也是连续数组
在 NIO 中,Buffer 是一个顶层父类,是一个抽象类
子类有:ByteBuffer、IntBuffer、 CharBuffer、 LongBuffer、DoubleBuffer、FloatBuffer、ShortBuffer
Selector:
Selector 类是 NIO 的核心类
Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销
过程:加载,验证,准备,解析,初始化
加载:生成一个代表这个类的 java.lang.Class 对 象,作为方法区这个类的各种数据的入口(可从Class 文件、jar包中的war包、运行计算生成、JSP转换的class文件等获取均可)
验证:确保class文件中的字节流符合条件
准备:在方法区中分配变量所使用的内存空间。正式为类变量分配内存并设置类变量的初始值阶段
//实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,存放于类构造器方法之中
public static int v = 8080;
//在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080
public static final int v = 8080;
解析:将常量池中的符号引用替换为直接引用的过程,class文件CONSTANT_Class_info、 CONSTANT_Field_info、CONSTANT_Method_info等信息
—补充—
符号引用:引用的目标并不一定要已经加载到内存中
直接引用:可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄,有该引用,在内存中一定有
类构造器
初始化阶段是执行类构造器方法的过程
方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子方法执行之前,父类的方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法
类加载器:启动类、扩展类和应用程序类