1.1 JVM的运行机制
JVM(Java Virtual Machine)是用于运行Java字节码的虚拟机。包括一套字节码指令集、一组程序寄存器、一个虚拟机栈、一个虚拟机堆、一个方法区和一个垃圾回收器。JVM运行在操作系统之上,不与硬件设备直接交互。
Javva程序的具体运行过程如下。
(1)Java源文件被编译器编译成字节码文件。
(2)JVM将字节码文件编译成相应操作系统的机器码。
(3)机器码调用相应操作系统的本地方法库执行相应的方法。
Java虚拟机包括一个类加载器子系统(Class Loader SubSystem),运行时数据区(Runtime Data Area),
执行引擎和本地接口库(Native Interface Library)。本地接口库通过调用本地方法库(Native Method Library)与操作系统交互。
其中:
- 类加载器子系统用于将编译好的.Class文件加载到JVM中;
- 运行时数据区用于存储在JVM运行过程中产生的数据,包括程序计数器、方法区、本地方法区、虚拟机栈和虚拟机堆;
- 执行引擎包括即时编译器和垃圾回收器,即时编译器用于将Java字节码文件编译成具体的机器码,垃圾回收器用于回收再运行过程中不再使用的对象;
- 本地接口库用于调用操作系统的本地方法库完成具体的指令操作
1.2 多线程
在多核操作系统中,JVM允许在一个进程中同时并发执行多个线程。JVM中的线程与操作系统中的线程是相互对应的,在JVM线程的本地存储、缓冲区分配、同步对象、栈、程序计数器等准备工作完成后,JVM会调用操作系统的接口创建一个与之对应的原生线程;在JVM线程运行结束后,原生线程随之被回收。操作系统负责调度所有线程,并为其分配CPU时间片,在原生线程初始化完毕时,就会调用Java线程的run()执行该线程;在线程结束时,会释放原生线程和Java线程所对应的资源。
在JVM后台运行的线程主要有:虚拟机线程,周期性任务线程、GC线程、编译器线程、信号分发线程等。
1.3 JVM的内存区域
JVM内存区域分成线程私有区域(程序计数器、虚拟机栈、本地方法区),线程共享区域(堆、方法区)和直接
内存。
1.3.1 程序计数器:线程私有,无内存溢出问题
程序计数器是一块很小的内存空间,用于存储当前运行的线程所执行的字节码的行号指示器。方法执行时候存储的是实时虚拟机字节码指令的地址;如果是Native方法,则值为空。
在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
1.3.2 虚拟机栈:线程私有,描述Java方法的执行过程
虚拟机栈是描述Java方法的执行过程的内存模型。每个java方法在执行时,会创建一个“栈帧(stack frame)”,栈帧的结构分为“局部变量表、操作数栈、动态链接、方法出口”几个部分。我们常说的“堆内存、栈内存”中的“栈内存”指的便是虚拟机栈,确切地说,指的是虚拟机栈的栈帧中的局部变量表,因为这里存放了一个方法的所有局部变量。
对于执行引擎来说,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
方法调用时,创建栈帧,并压入虚拟机栈;方法执行完毕,栈帧出栈并被销毁,如下图所示:
1.3.3 本地方法区:线程私有
本地方法区和虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。
1.3.4 堆:也叫运行时数据区,线程共享
在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆也是被线程共享的内存区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。由于现代JVM采用分代收集算法,因为Java堆从GC的角度还可以细分为新生代、老年代和永久代。
1.3.5 方法区:线程共享
方法区也被称为永久代,用于储存已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的机器码、运行时的常量池,跟堆一样,被所有的线程共享。
类信息:类的元数据(metadata),也就是通过反射能拿到的信息,比如说访问权限、实现的接口、类要继承的类……我们之所以可以通过反射获取对象,就是因为类的元数据被加载到了方法区中。方法区不是线程独享的,而是线程共享的(堆也是), Java线程安全问题由此而生。
常量池:方法区的一部分,常量池用于存放编译期生成的各种字面量和符号引用(还有翻译出来的直接引用),这部分内容在类加载后进入方法区的运行时常量池中存放。 运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,运行期间也可能将新的常量放入池中。
JVM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区。
1.4 JVM的运行时内存
JVM的运行时内存也叫JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。
1.4.1 新生代:Eden区、ServivorTo区和ServivorFrom区
JVM创建的对象(除大对象外)会被存在新生代,默认占1/3的堆空间。频繁创建对象,会频繁的触发MinorGC进行垃圾回收。新生代的GC过程叫做MinorGC,采用复制算法实现。
1.4.2 老年代
老年代主要存放有长生命周期的对象和大对象。老年代的GC过程叫做MajorGC。对象比较稳定,不会被频繁触发。MajorGC采用标记清除算法。
1.4.3 永久代
永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。
Java8,永久代被元数据代(也叫做元空间)取代。
1.5垃圾回收与算法
1.5.1 如何确定垃圾
Java采用引用计数法和可达性分析来确定对象是否应该被回收。
1.5.2 常用的垃圾回收算法
1.标记清除算法
2.复制算法
3.标记整理算法
4.分代收集算法
1.6 Java中的4种引用类型
在Java中一切皆对象,对象的操作是通过该对象的引用实现的。4种引用:1.强引用 2.软引用 3.弱引用 4.虚引用
Java中提供这四种引用(强软弱虚)类型主要有两个作用:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。
1.7 分代收集算法与分区收集算法
1.7.1 分代收集算法
新生代-复制算法
老年代-标记整理算法
1.7.2 分区收集算法
分区算法将整个堆空间划分连续的大小不同的小区域,对每个小区域都进行单独内存使用和垃圾回收,这样做的好处是可以根据每个小区域的内存大小灵活使用和释放内存。
1.8 垃圾收集器
1.9 Java网络编程模型
1.9.1 阻塞I/O模型
1.9.2 非阻塞I/O模型
while(true){
data = socket.read();
if(data == true){
//处理数据
}else{
//用户线程处理其他的任务
}
}
1.9.3 多路复用I/O模型
1.9.4 信号驱动I/O模型
1.9.5 异步I/O模型
1.9.6 Java I/O
在整个Java.io包中最重要的5个类和1个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader,1个接口指的是Serializable。
1.9.7 Java NIO
Java NIO的实现主要涉及三大核心内容:Selector(选择器)、Channel(通道)和Buffer(缓冲区)。Selector用于监听多个Channel的事件,比如连接打开和数据到达,因此,一个线程可以实现多个数据Channel的管理。传统I/O基于数据流进行I/O读写操作;而Java NIO基于Channel和Buffer进行I/O读写操作,并且数据总是被从Channel读取到Buffer中,或者从Buffer写入Channel中。