「浅谈JVM」
来自 gpt 的摘要:
JVM 主要组成部分和作用
类加载器(ClassLoader)
运行时数据区(Runtime Data Area)
执行引擎(Execution Engine)
本地库接口(Native Interface)(JNI、native)

组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能
JVM 内存结构
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间。根据1.7 java 虚拟机规范,jvm 在运行时主要分为以下几个运行时内存区域:

垃圾收集算法
标记-清除算法
标记—清除算法是最基础的收集算法,过程分为标记和清除两个阶段,首先标记出需要回收的对象,之后由虚拟机统一回收已标记的对象。这种算法的主要不足有两个: 1、效率问题,标记和清除的效率都不高; 2、空间问题,对象被回收之后会产生大量不连续的内存碎片,当需要分配较大对象时,由于找不到合适的空闲内存而不得不再次触发垃圾回收动作。

复制算法
为了解决效率问题,复制算法出现了。算法的基本思路是:将内存划分为大小相等的两部分,每次只使用其中一半,当第一块内存用完了,就把存活的对象复制到另一块内存上,然后清除剩余可回收的对象,这样就解决了内存碎片问题。我们只需要移动堆顶指针,按顺序分配内存即可,简单高效。但是算法的缺点也很明显: 1、它浪费了一半的内存,这太要命了。 2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视。

这种收集算法经常被采用到新生代,因为新生代中的对象绝大部分都是朝生夕死,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活着的对象一次性的复制到另外一块 Survivor 空间上,最后清理 Eden 和刚才用过的 Survivor 空间,HotSpot 默认的空间比例是 8:1
标记-整理算法
根据老年代的特点,有人提出了另一种改进后的“标记—清除”算法:标记—整理算法。 标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历 GC Roots,然后将存活的对象标记。 整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。
不难看出,标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价,可谓是一举两得。
分代收集算法
现代商业虚拟机垃圾收集大多采用分代收集算法。主要思路是根据对象存活生命周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代,然后根据各个年代的特点采用最合适的收集算法。新生代中,对象的存活率比较低,所以选用复制算法,老年代中对象存活率高且没有额外空间对它进行分配担保,所以使用“标记-清除”或“标记-整理”算法进行回收。

加载类的过程
类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。
加载
基本概念:该过程完成查找并加载类的 class 文件。该 class 文件可以来自本地磁盘或者网络等。
Java规范中并没有规定 Class 对象的存放位置,对于Hot Spot 虚拟机来说,Class 对象虽然是对象,但却是存放在方法区中。
验证
基本概念:确保类型的正确性,比如class文件的格式是否正确、语义是否符合语法规定、字节码是否可以被JVM安全执行等
验证总体上分为4个阶段: 文件格式验证、元数据验证、字节码验证、符号引用验证。
准备
基本概念:为类的静态变量分配内存,并赋初值。基本类型的变量赋值为初始值,比如 int 类型的赋值为0,引用类型赋值为 null。
“准备”阶段是正式为类变量(仅仅是类变量,即 static 修饰的变量)分配内存并设置类变量初始值(除了 final 变量初始值是数据类型的零值,并不是类构造器方法中的初始值)的阶段,这些变量所使用的内存都将在方法区中进行。
解析
基本概念:将符号引用转为直接引用。比如方法中调用了其他方法,方法名可以理解为符号引用,而直接引用就是使用指针直接引用方法。
”解析“阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,
主要针对 类或接口、字段、类方法、接口方法、方法类型、方法句柄 和 调用限定符 7类符号引用进行。
初始化
基本概念:初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
JVM类加载器
概念
“将 class 文件加载进 JVM 的方法区,并在方法区中创建一个 java.lang.Class 对象作为外界访问这个类的接口。”实现这一动作的代码模块称为类加载器。
类和类加载器:
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性。通俗点说: 比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才用意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里的“相等” 包括: Class 对象的 equals () 方法、isInstance ()方法,也包括 instanceof 关键字
有哪些类加载器
启动类加载器(Bootstrap ClassLoader)
是虚拟机自身的一部分,用来加载 Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
扩展类加载器(Extension ClassLoader):负责加载\lib\ext 目录或 Java. Ext. Dirs 系统变量指定的路径中的所有类库;
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
自定义类加载器:通过继承 ClassLoader 抽象类,实现 loadClass 方法
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
类装载的执行过程
加载:根据查找路径找到相应的 class 文件然后导入;
检查:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。
