JVM运行流程
jvm执行流程
程序执行之前先要把写的java代码转换成字节码(class文件),JVM首先需要把字节码通过类加载器(ClassLoader)把文件加载到内存中的运行时数据区(Runtime Data Area),而字节码文件是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,所以需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交给CPU去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface) 来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。

总结来看,JVM主要由以下4部分,来执行Java程序的:
1.类加载器
2.运行时数据区
3.执行引擎
4.本地库接口
运行时数据区:
运行时数据区也叫内存布局,但它与Java内存模型(Java Memory Model)简称JMM完全不同。
它由以下5部分组成:
1.堆(线程共享)
堆的作用:程序中所有创建的对象都放在堆区。
堆里面分为两个区域:新生代和老年代,新生代放新建的对象,当经过一定的GC次数之后还存活的对象会放入老生代。新生代还有三个区域:一个Eden+两个Survivor(S0/S1)

垃圾回收时会将Eden中存活的对象放到一个未使用的Survivor中,并将当前的Eden和正在使用的Survivor清除掉。
2.JAVA虚拟机栈(线程私有)
Java 虚拟机栈的作⽤:Java 虚拟机栈的⽣命周期和线程相同,Java 虚拟机栈描述的是 Java ⽅法执⾏的内存模型:每个⽅法在执⾏的同时都会创建⼀个栈帧(Stack Frame)⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟栈。

JAVA虚拟机栈中包含了以下4部分:
1.局部变量表
简单来说就是放方法参数和局部变量的。
2.操作栈
每个法规范会生成一个先进后出的操作栈
3.动态链接
指向运行时常量池的方法引用
4.方法返回地址
PC寄存器的地址
什么是线程私有
由于JVM的多线程是通过线程轮流切换并分配处理器执⾏时间的⽅式来实现,因此在任何⼀个确定的 时刻,⼀个处理器(多核处理器则指的是⼀个内核)都只会执⾏⼀条线程中的指令。因此为了切换线程后 能恢复到正确的执⾏位置,每条线程都需要独⽴的程序计数器,各条线程之间计数器互不影响,独⽴ 存储。我们就把类似这类区域称之为"线程私有"的内存。
3.本地方法栈(线程私有)
本地⽅法栈和虚拟机栈类似,只不过 Java 虚拟机栈是给 JVM 使⽤的,⽽本地⽅法栈是给本地⽅法使⽤的。
4.程序计数器(线程私有)
程序计数器的作⽤:⽤来记录当前线程执⾏的⾏号的。
程序计数器是⼀块⽐较⼩的内存空间,可以看做是当前线程所执⾏的字节码的⾏号指⽰器。
如果当前线程正在执⾏的是⼀个Java⽅法,这个计数器记录的是正在执⾏的虚拟机字节码指令的地
址;如果正在执⾏的是⼀个Native⽅法,这个计数器值为空。
5.方法区(线程共享)
方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。
运行时常量池
运行时常量池是方法区的一部分,存放字面量与符号引用。
字面量:字符串(JDK8移动到堆中)、final常量、基本数据类型的值。
符号引用:类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。
JVM类加载
1.类加载过程

其中前5步是固定的顺序并且也是类加载的过程,其中中间3步我们都属于连接,所以类加载总的来说分为以下步骤:
1.加载
2.连接
3.初始化
2.双亲委派模型
提到类加载机制,不得不提的⼀个概念就是“双亲委派模型”。
站在 Java 虚拟机的⻆度来看,只存在两种不同的类加载器:⼀种是启动类加载器(Bootstrap
ClassLoader),这个类加载器使⽤ C++ 语⾔实现,是虚拟机⾃⾝的⼀部分;另外⼀种就是其他所有的类加载器,这些类加载器都由Java语⾔实现,独⽴存在于虚拟机外部,并且全都继承⾃抽象类
java.lang.ClassLoader。站在 Java 开发⼈员的⻆度来看,类加载器就应当划分得更细致⼀ 些。⾃ JDK 1.2 以来,Java ⼀直保持着三层类加载器、双亲委派的类加载架构器。
什么是双亲委派模型?
如果⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把这个请求委派给⽗类加载器去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最 终都应该传送到最顶层的启动类加载器中,只有当⽗加载器反馈⾃⼰⽆ 法完成这个加载请求(它的搜索范围中没有找到所需的类)时,⼦加载器才会尝试⾃⼰去完成加载。
双亲委派模型的优点
1. 避免重复加载类:⽐如 A 类和 B 类都有⼀个⽗类 C 类,那么当 A 启动时就会将 C 类加载起来,那么在 B 类进⾏加载时就不需要在重复加载 C 类了。
2.
安全性:使⽤双亲委派模型也可以保证了 Java 的核⼼ API 不被篡改,如果没有使⽤双亲委派模
型,⽽是每个类加载器加载⾃⼰的话就会出现⼀些问题,⽐如我们编写⼀个称为 java.lang.Object
类的话,那么程序运⾏的时候,系统就会出现多个不同的 Object 类,⽽有些 Object 类⼜是⽤⼾⾃
⼰提供的因此安全性就不能得到保证了。
3.破坏双亲委派模型
双亲委派模型虽然有其优点,但在某些情况下也存在⼀定的问题,⽐如 Java 中 SPI(Service Provider Interface,服务提供接⼝)机制中的 JDBC 实现。

这样⼀来就破坏了双亲委派模型,因为 DriverManager 位于 rt.jar 包,由 BootStrap 类加载器加载,⽽其 Driver 接⼝的实现类是位于服务商提供的 Jar 包中,是由⼦类加载器(线程上下⽂加载器Thread.currentThread().getContextClassLoader )来加载的,这样就破坏了双亲委派模型了(双亲委派模型讲的是所有类都应该交给⽗类来加载,但 JDBC 显然并不能这样实现)。
垃圾回收相关
垃圾回收算法
a)标记-清除算法
标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : ⾸先标记出所有需要回收
的对象,在标记完成后统⼀回收所有被标记的对象(标记过程⻅3.1.2章节)。后续的收集算法都是基于这种思路并对其不⾜加以改进⽽已。
"标记-清除"算法的不⾜主要有两个 :
1.
效率问题 : 标记和清除这两个过程的效率都不⾼
2.
空间问题 : 标记清除后会产⽣⼤量不连续的内存碎⽚,空间碎⽚太多可能会导致以后在程序运⾏中需要分配较⼤对象时,⽆法找到⾜够连续内存⽽不得不提前触发另⼀次垃圾收集。

b)复制算法
复制"算法是为了解决"标记-清理"的效率问题。它将可⽤内存按容量划分为⼤⼩相等的两块,每次只使⽤其中的⼀块。当这块内存需要进⾏垃圾回收时,会将此区域还存活着的对象复制到另⼀块上⾯,然后再把已经使⽤过的内存区域⼀次清理掉。这样做的好处是每次都是对整个半区进⾏内存回收,内存分配时也就不需要考虑内存碎⽚等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运⾏⾼效。算法的执⾏流程如下图

c)标记-整理算法
复制收集算法在对象存活率较⾼时会进⾏⽐较多的复制操作,效率会变低。因此在⽼年代⼀般不能使 ⽤复制算法。
针对⽼年代的特点,提出了⼀种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程⼀致,但后续 步骤不是直接对可回收对象进⾏清理,⽽是让所有存活对象都向⼀端移动,然后直接清理掉端边界以外的内存。流程图如下

d)分代算法
分代算法和上⾯讲的 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策
略,从⽽实现更好的垃圾回收。
当前 JVM 垃圾收集都采⽤的是"分代收集(Generational Collection)"算法,这个算法并没有新思想, 只是根据对象存活周期的不同将内存划分为⼏块。⼀般是把Java堆分为新⽣代和⽼年代。在新⽣代中,每次垃圾回收都有⼤批对象死去,只有少量存活,因此我们采⽤复制算法;而老年代中对象存活率高、没有额外空间对它进⾏分配担保,就必须采⽤"标记-清理"或者"标记-整理"算法。
垃圾收集器
如果说上⾯我们讲的收集算法是内存回收的⽅法论,那么垃圾收集器就是内存回收的具体实现。
垃圾收集器的作⽤:垃圾收集器是为了保证程序能够正常、持久运⾏的⼀种技术,它是将程序中不⽤的死亡对象也就是垃圾对象进⾏清除,从⽽保证了新对象能够正常申请到内存空间。
以下这些收集器是 HotSpot 虚拟机随着不同版本推出的重要的垃圾收集器:

上图展⽰了7种作⽤于不同分代的收集器,如果两个收集器之间存在连线,就说明他们之间可以搭配使用。所处的区域,表示它是属于新⽣代收集器还是⽼年代收集器。在讲具体的收集器之前我们先来明 确三个概念:
•
并⾏(Parallel) : 指多条垃圾收集线程并⾏⼯作,⽤⼾线程仍处于等待状态
•
并发(Concurrent) : 指⽤⼾线程与垃圾收集线程同时执⾏(不⼀定并⾏,可能会交替执⾏),⽤⼾程序
继续运⾏,⽽垃圾收集程序在另外⼀个CPU上。
•
吞吐量:就是CPU⽤于运⾏⽤⼾代码的时间与CPU总消耗时间的⽐值。
吞吐量
=
运⾏⽤⼾代码时间
/(
运⾏⽤⼾代码时间
+
垃圾收集时间
)
例如:虚拟机总共运⾏了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。