Java--JVM
JVM是Java跨平台特性的核心,它是一个虚构的计算机,通过在实际计算机上模拟各种计算机功能来实现。
一.JVM内存区域划分
JVM启动的时候,从操作系统申请一大块内存,这样应用程序后续使用的时候,就可以从JVM的内存进行分配了。
JVM内存区域主要分为以下几个部分:

程序计数器
很小的一个区域,只保存一个数字=>吓一跳要执行的Java字节码指令的地址。
若执行的是Native方法(非Java实现),则计数器的值为undefined。
栈
虚拟机栈
Java虚拟机栈的生命周期和线程相同,Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧用于存储信息并将栈帧压入栈,方法执行完毕后栈帧弹出。
栈帧组成
局部变量表:存放方法参数和局部变量
操作数栈:方法执行过程中用于临时数据运算的栈结构
动态链接:将字节码中的符号引用转换为实际运行的直接引用
符号引用:类在编译的时候,编译器不知道目标方法或者字段在内存中的具体地址,因此会用一个符号来描述他们。这些符号引用存储在方法区的运行时常量池中。
直接引用:可以直接指向目标的内存地址,是实际运行时可以直接使用的引用。
方法返回地址:记录方法执行完毕后返回的位置
本地方法栈
与虚拟机栈类似,不过本地方法栈是给本地方法来使用的(例C++实现的方法)
堆
JVM中最大的内存区域,用于存放对象实例和数组,是垃圾回收的主要场所
元数据区(方法区)
存储已经被JVM加载的类信息,常量,静态变量等等
二.JVM类加载机制
完整流程有五步:加载 验证 准备 解析 初始化
1.加载
把 .class文件 找到,打开文件并且将数据读取到内存中
2.验证
根据读取到的二进制内容,验证是否是合法的格式(确保加载的字节流符合JVM规范)
3.准备
给要创建的类对象分配内存空间
4.解析
将方法区常量池中的符号引用转换为直接引用
5.初始化
针对类对象进行初始化操作
初始化类的静态成员,执行静态代码块和对父类的加载
双亲委派模型
JVM中包括了三个类加载器
1.BootstrapClassLoader 负责加载Java标准库中的类
2.ExtensionClassLoader 负责加载Java扩展库中的类(JDK厂商做的扩展)
3.ApplicationClassLoader 负责加载第三方库以及当前项目中的类(第三方库以及当前项目中的类)
当我们要查找一个类的时候:
1.把全限定类名交给 ApplicationClassLoader ,然后 ApplicationClassLoader 把任务交给 ExtensionClassLoader,ExtensionClassLoader 再把任务转交给 BootstrapClassLoader ,
这时候 BootstrapClassLoader 就会处理请求,但如果在 BootstrapClassLoader 中找不到对应的类,BootstrapClassLoader 就会把任务转交给 ExtensionClassLoader,如果说ExtensionClassLoader 也找不到对应的类 就会再次转交给 ApplicationClassLoader ,最后如果ApplicationClassLoader 里还没有这个类,那么就会报错。
注意:当一个类加载器收到类加载的请求的时候,会优先委派给父类加载器,只有当父类加载器无法加载的时候,才会尝试自己加载。
三.JVM的垃圾回收GC
JVM垃圾回收的主战场是堆,而堆上存放的是对象,所以GC是以对象为单位进行回收的,一个对象要么不释放,要么整个释放,不会存在只释放了一半的情况。
垃圾回收首先要找出谁是垃圾,然后再进行内存的释放(宁可放过,不可错杀)
而判断的方式就是某个对象如果没有引用指向,此时就可以认为这个对象不再被使用了。(此处指的是强引用)
那么我们如何判断一个对象是否有引用指向呢?
引用计数
给每个对象设置一个引用计数器,当对象被引用时计数器+1,引用失效的时候,计数器-1,当引用计数器为0的时候,这时候就认为对象可回收。
优点是:简单高效,判断效率高
缺点是:无法解决循环引用的问题(A引用B,B引用A,这时候两者的计数器都不为0,但A,B两个对象实际上没有引用指向)
可达性分析:
在Java代码中,一系列对象的引用是存在一定的关联关系的,类似于树形结构,而可达性分析的原理就是:以GC Roots 为起点,便利这个对象树,只要能到达的节点,都标记成可达,而剩下的对象就标记成不可达,这些不可达对象也就是主要的回收对象。当然,随着代码的不断运行,对象之间的引用在不断发生变化,所以可达性分析要周期性的进行。而这种方案,就解决了循环引用的问题。
释放内存
清除算法:
标记可回收的对象,再统一进行清除
优点是:简单高效,无需移动对象
缺点是:内存不连续,大对象无法分配连续内存
复制算法
将内存大小分为相等的两块,只是用其中的一块,回收对象的时候将不需要回收的对象复制到另一边,让后清除原块。
优点:内存空间连续,无内存碎块,回收效率高(只需要复制存活对象)
缺点:内存利用率低,适合存活对象少的场景
标记整理算法
标记可回收的对象,将存活对象向内存的一端移动,然后清除边界外的内存。
像:一个顺序表删除其中的元素一样
优点:无内存碎片,内存利用率高
缺点:需要移动对象,效率低
分代回收算法
根据不同的特点,采用不同的回收方案:
将整个堆分为了两个区域:新生代 和 老年代(通过GC扫描的轮次来区分)
新生代又分为一个伊甸区和两个大小相等的幸存区 比例为8:1:1
其中伊甸区是刚刚创建的新对象,通常情况下,第一轮GC就会把大部分伊甸区对象回收掉,而幸存的对象就会放到幸存区,幸存区采用的是复制算法,下一轮GC扫描,没有淘汰的对象就会放置到另一个幸存区,此后再次经历过多次GC扫描的新生区对象,就会进入到老年代,老年代的处理方式是标记整理,虽然整理一次很耗费资源,但是整理频率比较低(如果某个对象占内存很大,那么他就可能直接进入老年代)

