一、JVM内存结构

1、方法区:存储编译后的类、常量等(.class字节码文件

2、堆内存:存储对象

3、程序计数器:存储当前执行的指令地址(计算机处理器(CPU)正在执行的下一条指令在内存中的地址)

4、虚拟机栈:java栈,存储局部变量、方法参数、返回值以及异常处理信息

5、本地方法栈:存储本地方法的执行状态信息以上是JVM内存结构的主要部分,其中除了方法区外其他部分都是java程序员直接操作和调优的重要部分

线程私有:程序计数器、虚拟机栈、本地方法栈。

线程共享:方法区、堆。

android jvm 原理_本地方法

以下是一个更完整的示例代码,演示了JVM内存结构的各个部分:

public class MemoryStructureExample {

    // 静态变量,存储在方法区
    private static String staticVar = "Static Variable";

    public static void main(String[] args) {
        // 局部变量,存储在虚拟机栈
        int localVar = 10;
        
        // 创建一个对象实例,存储在堆内存
        MemoryStructureExample obj = new MemoryStructureExample();
        
        // 调用方法,会在虚拟机栈中创建方法调用的栈帧
        obj.method();
    }

    // 实例方法
    public void method() {
        // 方法中的局部变量,存储在虚拟机栈
        String localVar2 = "Local Variable";
        
        // 创建一个对象实例,存储在堆内存
        Object obj = new Object();
        
        // 调用本地方法,本地方法栈存储本地方法的执行状态信息
        System.out.println(System.currentTimeMillis());
    }
}

在这个示例中,我们展示了JVM内存结构的各个部分的应用:

  • 静态变量staticVar存储在方法区;
  • main方法中的局部变量localVar存储在虚拟机栈;
  • MemoryStructureExample对象实例存储在堆内存;
  • method方法中的局部变量localVar2也存储在虚拟机栈;
  • 方法中创建的Object对象实例也存储在堆内存;
  • 调用本地方法System.currentTimeMillis()时,本地方法栈存储本地方法的执行状态信息。

二、JVM垃圾回收

        GC 的目的在于实现堆内存中无用对象内存自动释放,减少内存碎片、加快分配速度 。线程私有的不存在垃圾回收,线程共享才存在垃圾回收。以下我们围绕如何发现垃圾和如何进行垃圾回收进行详细描述:

(一)如何发现垃圾?

1、引用计数算法

        引用计数算法核心思想是,堆中的对象每被引用一次,则计数器加 1,每减少一个引用就减 1,当对象的引用计数器为 0 时可以被当作垃圾收集。

优点:效率高,比较快

缺点:无法检测出循环引用,如两个对象互相引用时,他们的引用计数永远不可能为 0

2、可达性分析(根搜索)算法

        根搜索算法是把所有的引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的
引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕
之后,剩余的节点则被认为是没有被引用到的节点,即可以当作垃圾。

3、三色标记法(黑灰白)

        三色标记法是用三种颜色记录对象的标记状态。这种算法通过标记对象的颜色来表示它们的状态,以确定哪些对象是活动的,哪些是垃圾对象。黑色-已标记,灰色-标记中,白色-未标记。原理是通过将引用链上的对象全部标记,最终剩余的不在引用链上的对象全部是白色的(未标记的),然后对未标记的无用的对象进行回收。这种算法通过标记对象的颜色来表示它们的状态,以确定哪些对象是活动的,哪些是垃圾对象。

3.1起始的三个对象还未处理完成,用灰色表示

android jvm 原理_本地方法_02

3.2该对象的引用已经处理完成,用黑色表示,黑色引用的对象变为灰色

android jvm 原理_堆内存_03

3.3依次类推

android jvm 原理_android jvm 原理_04

3.4沿着引用链都标记了一遍

android jvm 原理_垃圾回收_05

3.5最后为标记的白色对象,即为垃圾

android jvm 原理_垃圾回收_06

(二)如何清除垃圾?

1、标记清除算法(空间碎片,CMS)

        标记清除算法是通过GC Root引用链往下查找,对于引用链上有引用的对象进行标记,然后对之外的无用的对象进行清除。缺点是存在内存碎片的问题。

android jvm 原理_垃圾回收_07

2、标记整理算法(性能较差,G1)

        标记整理算法是在标记清除算法上多了一步整理的操作,去除了空间碎片的问题。缺点是性能较差

android jvm 原理_jvm_08

3、标记复制算法(占用成倍的空间

        3.1将整个内存分成两个大小相等的区域,from 和 to,其中 to 总是处于空闲,from 存储新创建的对象。

        3.2标记阶段与前面的算法类似。

        3.3在找出存活对象后,会将它们从 from 复制到 to 区域,复制的过程中自然完成了碎片整理

        3.4复制完成后,交换 from 和 to 的位置即可。

android jvm 原理_jvm_09

三、四种引用

        总的来说,强引用是最常见的引用类型,只有在不再被引用时才会被回收;软引用在内存不足时会被回收;弱引用在下一次垃圾回收时会被回收;虚引用在对象被回收时会被放入引用队列中,需要手动清除。根据不同的需求和场景,可以选择合适的引用类型来管理对象的生命周期。

(一)强引用:

        普通变量赋值即为强引用,如 A a = new A();通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收。

android jvm 原理_jvm_10

(二)软引用:

        例如:SoftReference a = new SoftReference(new A());如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象;软引用自身需要配合引用队列来释放,典型例子是反射数据。

android jvm 原理_堆内存_11

(三)弱引用:

        例如:WeakReference a = new WeakReference(new A());如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象,弱引用自身需要配合引用队列来释放,典型例子是 ThreadLocalMap 中的 Entry 对象。

android jvm 原理_堆内存_12

(四)虚引用:
  1. 例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);
  2. 必须配合引用队列一起使用,当虚引用所引用的对象被回收时,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理
  3. 典型例子是 Cleaner 释放 DirectByteBuffer 关联的直接内存。
  4. android jvm 原理_jvm_13