1.JVM的介绍
JVM是Java Virtual Machine(Java虚拟机)的缩写
本章所论的JVM是HotSpot VM , 是 Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机
介绍我所知的另外两种:
Jrockit: 由BEA公司开发的专注于服务器端应用的虚拟机。号称世界上最快的虚拟机
J9 :J9由IBM公司开发,曾广泛应用于IBM公司系统内部及IBM小型机上。现已经捐献给Eclipse基金会。
2.JVM的作用
java是一门高级语言,符合人们的习惯,但不符合电话的理解,因此需要在硬件上进行转化
就是将.java文件转成Java字节码文件(.class),由JVM进行解析和执行.
3.JVM图解
接下来就按着图解来进行拆分讲解
4.Javac编译器
4.1编译器介绍
编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序,
或者是把程序转换成计算机或者微型处理器能够识别的机器代码 0 - 1
4.2编译器作用
对于 Java 虚拟机来说,识别的是.class文件,
而编译器的作用就是把.java.文件转化成.class文件
在JDK安装目录有一个叫 javac 的编译器的工具,它就是把Java代码编译成字节码文件的
一般讲的是Javac编译器,下面讲一下另外两种
4.3编译器种类
从源码到字节码 - javac编译器
从字节码到机器码:
2.1 使用 Java 解释器解释执行字节码
启动速度快-不需要翻译
运行速度慢- 机器码运行的效率比解释器高
2.2 使用 JIT 编译器(即时编译器)将字节码转化为本地机器代码。
启动速度慢,运行速度快
JIT编译器(动态编译)在JVM中有两种内置的即时编译器 Client Compiler 和 Server Compiler
Client Comlier: 优化少,编译速度快
Server Comlier: 根据性能优化,编译质量好,耗时长
虽然有两种模式,但是JVM提供了三种运行模式: mixed mode (默认,混合),Interpreted Mode(解释),Compiled Mode(编译)
从源码到机器码:
AOT编译器 (JDK9新特性) :在程序执行前生成 Java 方法的本地代码,以便在程序运行时直接使用本地代码,静态编译
以上三种是JVM重要的编译器
编译速度上,解释执行 > AOT 编译器 > JIT 编译器
编译质量上,JIT 编译器 > AOT 编译器 > 解释执行
5.类加载器
类加载介绍
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
类加载器是JVM一部分,负责动态加载Java类到Java虚拟机的内存空间中
类加载器作用
JVM中4种类加载器
3.类加载器的时机
1.new一个对象的时候,也就是创建类实例的时候
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射(Class.forName(“xxx.xxx”))
5.初始化一个类的子类(会首先初始化子类的父类)
6.JVM启动时标明的启动类,即文件名和类名相同的那个类
4.类加载器的机制 - 双亲委派模型
双亲委派模型介绍:
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
双亲委派机制好处:
保证java类库中的类不受用户类影响,防止用户自定义一个类库中的同名类,引起问题。
5.类加载的方式
- 命令行启动应用时候由JVM初始化加载
- 通过Class.forName()方法动态加载
通过ClassLoader.loadClass()方法动态加载
6.类加载的生命周期:
加载:
将编译后的.Class文件加载到内存中
连接:(验证,准备,解析)
**验证:** 确保**Class文件**的字节流中包含的信息符合当前**虚拟机的要求**,并且不会危害虚拟机自身的安全
准备: 为类变量分配内存并设置初始值,使用的是方法区的内存
解析: 将class文件的常量池的符号引用替换为直接引用的过程(是静态链接)。
初始化: 为类的静态变量赋予程序中指定的初始值,还有执行静态代码块中的程序。
使用
卸载JVM把java文件数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型
将指定的class文件读取到内存里,并运行该class文件里的Java程序的过程,就称之为类的加载;反之,将某个class文件的运行时数据从JVM中移除的过程,就称之为类的卸载。
6.运行时数据区
6.1虚拟机栈
线程私有
生命周期与线程相同
每个方法执行会创建一个栈帧
栈帧:
局部变量表:
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
存放的信息包括 基本数据类型,对象引用,returnAddress类型
操作数栈: 保存着Java 虚拟机执行过程中的数据
动态链接:指向运行时常量池中该栈帧所属方法的引用
方法出口:储存返回地址
退出方法的方式 正常完成出口 异常完成出口,不会返回值,返回地址通过异常处理器表来确定 退出过程 1)恢复上层方法的局部变量表和操作数栈 2)把返回值压入调用者的栈帧的操作数栈中 3)调整PC计数器指向下一条指令
6.2本地方法栈
线程私有
与虚拟机栈一样,只是服务的方法类型不一样使用到的Native方法服务
6.3程序计数器
线程私有
当前线程执行字节码的行号指示器此内存区域是唯一一个不会出现OutOfMemoryError情况的区域。
1.如果线程正在执行的是一个Java方法,则指明当前线程执行的代字节码行数
2.如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)
6.4方法区(元空间)
线程共享
储存已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据元空间使用本地内存,理论上电脑有多少内存,它就有多少内存,避免的内存溢出问题
常量池
编译器生成的各种字面量和符号引用
符号引用: 类符号引用, 方法符号引用, 字段符号引用
一个java类(假设为People类)被编译成一个class文件时,如果People类引用了Tool类,但是在编译时People类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。而在类装载器装载People类时,此时可以通过虚拟机获取Tool类的实际内存地址,因此便可以既将符号org.simple.Tool替换为Tool类的实际内存地址,及直接引用地址。字面量: 文本字符串, 八种基本类型, 声明为final的常量
String a = “abc”,这个abc就是字面量 int a = 1; 这个1就是字面量
6.5堆
线程共享
占内存最大的一块
存放对象实例
垃圾回收的主要区域
在GC角度来看,可以分为两个区理解: 新生代,老年代
新生代
类诞生和成长的地方,甚至是死亡
又分为三个区: Eden区, Survivor(from)区, Survivor(to)区
设置两个Survivor区是为了解决碎片化的问题采用的是复制算法
Eden区(伊甸园区):
所有对象都是这 New 出来的 (对象优先在Eden分配,大对象直接进入老年代)
如果这里空间满了,将会进行轻GC ,存活的对象将放进from区
Survivor(from)区:
出生时位于Eden,一次MinorGC存活后进入Survivor,每经历一次GC,年龄加一,年龄达到15进入老年代
from区和to区会进行互换角色
Survivor(to)区:
为空的就是to区
老年代
采用标记整理算法(标记压缩算法)
当Survivor中相同年龄的对象大小总和大于Survivor的一半,则这些对象进入老年代
如果老年代满了会处罚Full GC
GC
gc分类
Minor GC
当Eden区满时,会触发Minor GC,对新生代进行垃圾回收。
MajorGC
对老年代进行垃圾回收
full GC
对整个堆进行垃圾回收
触发条件
1.老年代空间不足
2.方法区空间不足
3.调用system.gc(),这个只是建议JVM执行full GC,但不是一定就会立刻执行。
4.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
两个名词
stop-the-world
JVM由于要执行gc而会暂停应用程序的执行,这一过程叫做stop-the-world。它会在任何一种GC算法中出现,而我们对gc的优化也是致力于减少它发生的时间来提高程序性能。
safepoint
当我们对某一个对象进行可达性分析时,它的引用关系此时最好是不能变化的,也就是它必须处于安全点的。也就是说safepoint就是分析过程中对象引用不会发生变化的点(场景:方法调用,循环跳转,异常跳转等)
4种算法
复制算法:
是将内存划分为两块大小相等的区域,每次使用时都只用其中一块区域,当发生垃圾回收时会将存活的对象全部复制到未使用的区域,然后对之前的区域进行全部回收。
优点
简单高效,不会出现内存碎片问题
缺陷
内存利用率低
存活对象较多时效率明显会降低
标记-清除算法:
先按照可达性算法标记所有被引用的对象,然后遍历堆,清除未被标记的算法。它的会产生内存碎片,以及出现大对象找不到连续空间的问题。(标记过程会暂停所有进程,也就是我们说的stop-the-world STW)
1.将需要回收的对象标记起来
2.清除对象
优点:
不需要额外的空间
缺点:
两次扫描,耗时长
会产生大量的不连续的内存碎片
标记-整理算法:
原理和标记清除算法类似,只是最后一步的清除改为了将存活对象全部移动到一端,然后再将边界之外的内存全部回收。
优点:
不需要额外空间
缺点:
需要移动大量对象,效率不高
分代回收算法:
根据各个年代的特点选取不同的垃圾收集算法
新生代使用复制算法
老年代使用标记-整理或者标记-清除算法
对象存活
引用计数法:
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
缺陷:循环引用会导致内存泄漏
可达性分析算法:
该算法是通过一系列的称为“GC Roots”的对象作为起始点,
从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),
当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
判断一个对象是否可回收的过程
1.找到GC Roots不可达的对象,如果没有重写finalize()或者调用过finalize(),则将该对象加入到F-Queue中
2.再次进行标记,如果此时对象还未与GC Roots建立引用关系,则被回收
垃圾收集器
G1 和 CMS
G1收集器:
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
同时收集新生代和老年代垃圾
流程:
初始标记:仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程(STW),但耗时很短。
并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行。
筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
并发和并行
分代收集
标记-整理算法
将堆分为多个大小相等的region
特点
空间整合,不会产生内存碎片
可预测的停顿
参数设置
-XX:+UseG1GC
-XX:MaxGCPauseMillis
CMS收集器
CMS 是英文 Concurrent Mark-Sweep 的简称,
是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。
对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。
它与其他老年代收集器不同的时,它采用标记清除算法,也就是说它存在内存碎片化的问题,如果要分配一个比较大的对象的内存,就只能触发GC。
流程
初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(STW)。
并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,它在整个回收过程中耗时最长,不需要停顿。
重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(STW)。
并发清除:不需要停顿。
标记-清除算法
并发收集-低停顿
吞吐量低
低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高
无法处理浮动垃圾 可能出现 Concurrent Mode Failure
浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收
由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。
如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
会产生空间碎片
标记 - 清除算法会导致产生不连续的空间碎片
回收对象引用类型
强引用
垃圾回收器绝对不会回收它,当内存不足时宁愿抛出 OOM 错误,使得程序异常停止
软引用
垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它
软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。
弱引用
垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。
ThreadLocal的key是弱引用
虚引用
如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动
强引用strong reference
不会回收
软引用soft reference
溢出才回收
弱引用weak reference
下一次被回收
虚引用phantom reference
7.执行引擎
所有的执行引擎的基本一致:
- 输入:字节码文件
- 处理:字节码解析
- 输出:执行结果。
物理机的执行引擎是由硬件实现的,和物理机的执行过程不同的是虚拟机的执行引擎由于自己实现的。
8.本地方法
用native修饰的,不能和abstract共同使用的,不显示方法体但却是用非Java语言实现方法体的方法。
9.本地方法库
个人理解:类似C, C++
小唠嗑:
本章到这里就结束了,谢谢耐心看到这里的各位Boss,如果觉得有哪里说的不好的地方,还请高抬贵手多多原谅,不惜指教。
最后,谢谢!