Java 程序具体运行过程:

    1. Java 源文件被编译器编译成字节码文件。
    2. JVM 将字节码文件编译相应操作系统的机器码。
    3. 机器码调用相应的本地方法库执行相应方法。

    JVM系统交互图:
    image.png

    1. 类加载器子系统将编译好的.Class文件加载到JVM中
    2. 运行数据区用于存储JVM运行过程中的数据主要包括程序计数器、方法区、本地方法区、虚拟机栈堆
    3. 执行引擎包括及时编译器、垃圾回收器。即时编译器用于对Java字节码编译成具体机器码,垃圾回收器用于回收在运行过程中不在被使用的对象。
    4. 本地接口库用于调用操作系统的本地方法库完成具体操作。

    本地方法区

    主要实现非Java语言实现的方法。通常指C或者C++,也有C栈的称号。

    方法区
    image.png

    1. 方法区保存着,类、静态变量、静态方法、常量、普通方法。
    2. 方法区的大小不比固定。Jvm可以根据应用需求动态调整。Jvm也可以指定方法区的初始大小,最小与最大限制。
    3. 方法区也存在垃圾收集,通过用户定义的类加载动态扩展Java程序,导致部分类不在被使用需要进行回收。
    4. 方法区扩展到Java永久代,永久代的内存回收主要是常量池回收和类的卸载因此可回收内容很少

    JDK1.8后 方法区与永久代 现在叫元空间存在操作系统内并支持垃圾回收。

    栈内存

    1. 栈内存存储的都是局部变量,(定义在方法中的都是局部变量 方法外的全局变量 ,For循环定义的也是局部变量) 所以需要加载函数后才能进行局部变量的定义。所以方法先出栈然后在定义变量。变量一旦离开自己的作用域就会被释放。栈的更新速度快,所以局部变量的生命周期很短。

    堆内存

    1. 堆内存存储的是数组和对象(数组就是对象),实体用于封装多个数据,如果一个数据消失实体不会消失。所以堆是不会被随时释放。但是栈不一样,栈存放的是单个变量变量释放就没有了。堆的实体不会被释放但是会被当做垃圾基于Java垃圾回收机制进行收取。

    JVM运行时的内存
    JVM的运行时内存也叫作JVM堆从GC的角度可以将JVM堆分为新 生代、老年代和永久代。其中新生代默认占 1/3堆空间老年代默认占 2/3堆空间永久代占非常少的堆空间。新生代又分为Eden区、
    区 和 ServivorTo 区 Eden 区 默 认 占 8/10 新 生 代 空 间 ServivorFrom区和ServivorTo区默认分别占 1/10新生代空间

    image.png
    新生代:
    创建新对象会在新生代频繁触发GC进行垃圾回收,新生代又分为Eden区、 ServivorFrom 区 和 ServivorTo,每次在GC后ServivorTo 会保留上一次的幸存者。ServivorFrom 将上一次GC的幸存者作为这一次的扫描者。
    MinorGC采用复制算法
    1.具体是吧Eden区域ServivorFrom区域存活对象复制到ServivorTo区,当对象年龄达到老年代的标准则复制到老年代,一般老年代晋升标准是15通过设置 XX:MaxTenuringThreshold。
    2.清空Eden区和ServivorFrom区中的对象。
    3.将ServivorTo区和ServivorFrom区互换原来的ServivorTo区成为下 一次GC时的ServivorFrom区。

    老年代:
    老年代存放的长生命周期的对象和大对象。老年代GC过程叫MajorGC。相对新生代老年代GC不会过于频繁。在进行MajorGC前会进行MinorGC 在MinorGC过后仍然出现老年代空间不足或者无法找到连续空间创建大对象会进行MajorGC释放JVM空间
    MajorGC采用标记清除算法
    该算法会扫描所有对象并标记存活对象然后回收未标记对象并释放空间。而因为要扫描老年代所有对象所以MajorGC耗时较长。标记算法也会产生大量内存碎片。

    永久代:
    永久代存放Class和Meta元数据的信息。Class在加载时被放入永久代。永久代的GC不会再运行期间进行清理。这也导致内存随着Class文件增加而产生的内存溢出。比如Tomcat引入Jar文件过多导致的JVM内存不足。
    但是Java 8 中永久代已经被叫做元空间取代。元空间最大的改变在于不是用虚拟机内存而是本地内存。因此元空间的大小不在受限于Jvm内存。

    垃圾回收与算法

    image.png
    引用计数法
    Java在操作对象时必须获取对象的引用这时候进行引用计数法来判断在为一个对象添加引用时增加计数,删除一个引用时减去计数。如果引用计数为0则可以被回收。引用计数存在循环引用问题两个对象互相引用那么两个对象都不会被回收。

    可达性分析
    为了解决重复引用问题 Java还采用可达性来判断是否进行回收。首先定义GC Roots对象然后对这些对象向下搜索。如果GC Roots和一个对象无可达路径,这个对象就标记为不可达进行回收。

    常用垃圾回收算法
    image.png
    1. 标记清除算法
    标记清除算法是基础的垃圾回收算法其过程分为标记和清除两个 阶段。在标记阶段标记所有需要回收的对象在清除阶段清除可回收的 对象并释放其所占用的内存空间
    image.png

    由于标记清除算法在清理对象所占用的内存空间后并没有重新整 理可用的内存空间因此如果内存中可被回收的小对象居多则会引起内 存碎片化的问题继而引起大对象无法获得连续可用空间的问题。

    1. 复制算法
      复制算法是为了解决标记清除算法内存碎片化的问题而设计的。 复制算法首先将内存划分为两块大小相等的内存区域即区域 1和区域 2 新生成的对象都被存放在区域 1中在区域 1内的对象存储满后会对区域 1进行一次标记并将标记后仍然存活的对象全部复制到区域 2中这时区 域 1将不存在任何存活的对象直接清理整个区域 1的内存即可

    image.png
    复制算法的内存清理效率高且易于实现但由于同一时刻只有一个 内存区域可用即可用的内存空间被压缩到原来的一半因此存在大量的 内存浪费。同时在系统中有大量长时间存活的对象时这些对象将在内 存区域 1和内存区域 2之间来回复制而影响系统的运行效率。因此该算 法只在对象为“朝生夕死”状态时运行效率较高。

    1. 标记整理算法
      标记整理算法结合了标记清除算法和复制算法的优点其标记阶段 和标记清除算法的标记阶段相同在标记完成后将存活的对象移到内存 的另一端然后清除该端的对象并释放内存。
      image.png

    4.分代收集算法
    无论是标记清除算法、复制算法还是标记整理算法都无法对所有 类型长生命周期、短生命周期、大对象、小对象的对象都进行垃圾回 收。因此针对不同的对象类型JVM采用了不同的垃圾回收算法该算法 被称为分代收集算法。 分代收集算法根据对象的不同类型将内存划分为不同的区域JVM 将堆划分为新生代和老年代。新生代主要存放新生成的对象其特点是 对象数量多但是生命周期短在每次进行垃圾回收时都有大量的对象被 回收老年代主要存放大对象和生命周期长的对象因此可回收的对象相 对较少。因此JVM根据不同的区域对象的特点选择了不同的算法。 目前大部分JVM在新生代都采用了复制算法因为在新生代中每次 进行垃圾回收时都有大量的对象被回收需要复制的对象存活的对象较少不存在大量的对象在内存中被来回复制的问题因此采用复制算法能 安全、高效地回收新生代大量的短生命周期的对象并释放内存。 JVM将新生代进一步划分为一块较大的Eden区和两块较小的 Servivor区Servivor区又分为ServivorFrom区和ServivorTo区。JVM在运 行过程中主要使用Eden区和ServivorFrom区进行垃圾回收时会将在Eden 区和ServivorFrom区中存活的对象复制到ServivorTo区然后清理Eden区 和ServivorFrom区的内存空间。
    image.png
    老年代主要存放生命周期较长的对象和大对象因而每次只有少量 非存活的对象被回收因而在老年代采用标记清除算法。 在JVM中还有一个区域即方法区的永久代永久代用来存储Class 类、常量、方法描述等。在永久代主要回收废弃的常量和无用的类。 JVM内存中的对象主要被分配到新生代的Eden区和ServivorFrom区 在少数情况下会被直接分配到老年代。在新生代的Eden区和 ServivorFrom区的内存空间不足时会触发一次GC该过程被称为 MinorGC。在MinorGC后在Eden区和ServivorFrom区中存活的对象会被 复制到ServivorTo区然后Eden区和ServivorFrom区被清理。如果此时在 ServivorTo区无法找到连续的内存空间存储某个对象则将这个对象直接 存储到老年代。若Servivor区的对象经过一次GC后仍然存活则其年龄加 1。在默认情况下对象在年龄达到15时将被移到老年代。

    Java中的四种引用类型
    1.强引用:把一个对象赋值给一个引用变量时这个引用变量就是一个强引用。有强引用的对象一定为可达性状态所以不会被回收也是早餐内存泄露的主要原因。
    2.软引用:主要通过SoftReference类来实现。如果一个对象只有软引用则在系统内存空间不足被回收。
    3.弱引用:主要通过WeakReference类来实现。如果一个对象只有弱引用则在垃圾回收时候一定被回收
    4.虚引用:主要通过PhantomReference类来实现虚引用,虚引用主要用于跟踪对象的垃圾回收状态。

    Jvm类加载机制
    Jvm的类加载分为5个阶段加载、验证、准备、解析、初始化。
    image.png
    1.加载
    JVM读取Class文件并根据Class文件创建java.lang.Class对象的过程。类加载过程主要包含将Class文件读取到运行时区域的方法区,在堆中创建对象。读取Class文件也可以通过文件形式读取也可以通过Jar包,War包读取还可以通过代理自动生成Class或其他方式读取。
    2.验证
    主要确保Class文件是否符合虚拟机的安全要求只有符合才可以被JVM加载。
    3.准备
    对于方法区中的类变量分配内存空间并设置类的变量的初始值。非Final对象初始过程是对于非Final静态变量在准备阶段初始为初始值。设置参数的动作是在对象初始化进行完成的。但是如果变量是Final对象则JVM在编译阶段会Final变量生成对应的ConstantValue属性虚拟机在准备阶段根据ConstantValue属性直接赋值。
    4.解析
    JVM会将常量池中的符号引用替换为直接引用
    5.初始化
    主要通过执行类构造器的方法为类进行初始化。方 法是在编译阶段由编译器自动收集类中静态语句块和变量的赋值操作 组成的。JVM规定只有在父类的方法都执行成功后子类中的 方法才可以被执行。在一个类中既没有静态变量赋值操作也没 有静态语句块时编译器不会为该类生成方法。 在发生以下几种情况时JVM不会执行类的初始化流程。
    ◎ 常量在编译时会将其常量值存入使用该常量的类的常量池中该 过程不需要调用常量所在的类因此不会触发该常量类的初始化。
    ◎ 在子类引用父类的静态字段时不会触发子类的初始化只会触发 父类的初始化。
    ◎ 定义对象数组不会触发该类的初始化。
    ◎ 在使用类名获取Class对象时不会触发类的初始化。
    ◎ 在使用Class.forName加载指定的类时可以通过initialize参数设置 是否需要对类进行初始化。
    ◎ 在使用ClassLoader默认的loadClass方法加载类时不会触发该类 的初始化。