关于JVM:一段代码告诉你JVM的工作原理类加载到运行及JVM各个运行时数据区

4次阅读

共计 4950 个字符,预计需要花费 13 分钟才能阅读完成。

前言

学习技术特地是工作中常常使用不到的技术兴许会让很多人感觉烦闷干燥, 实践如果不联合实际终究会成为过眼云烟。技术没有捷径, 须要考究办法地继续学习。既然是原理, 他必定是一个动作化地一系列行为总和。行为的背地必定有主体或者实体以及引入的背景。所以集体感觉在面试中, 如果面试官问一些原理性货色, 最好的形式还是 why what how;why- 用于解答为什么须要这个货色,what- 阐明这个货色是什么以及其特点 how- 次要是联合 what 行为化的形容其是如何实现特点的。

摘要

本节是集体学习总结, 次要是联合简略的案例 demo 代码以图文并貌的模式总结下本人学习的 JVM 工作原理。从为什么引入 JVM 的 java 代码如何运行?到 JVM 运行时候须要用到什么主体 - 类加载器、字节码执行引擎、JVM 的各个运行时候数据区以及 JVM 在怎么运行咱们代码的,类加载器加载的数据会加载存储到哪些数据区, 字节码执行引擎的时候是如何联合咱们的 JVM 运行数据去来工作的?以此来加深本人的常识体系。

内容

1、案例引入咱们为什么须要 JVM.

咱们工作中写的代码是如何运行起来呢?咱们的.java 后缀的代码 (源代码) 通过打包编译后成为字节码.class(jar/war 包), 而后启动一个 tomcat 或者应用 java -jar xxx.jar 即可开启一个 JVM 过程来运行咱们的 java 代码。下面就是咱们为什么须要 JVM 的起因。

2、JVM 工作原理运行时候须要哪些实体.

咱们从工作中写的代码如何运行起来能够晓得?JVM 运行起来时候是从编译后的.class 文件进行加载, 而后必定是加载到内存中, 所以这外面就有 JVM 的运行数据区, 运行的代码文件不可能永无止境在加载到内存, 必定会存在内存满的时候, 所以就存在垃圾, 而后就牵涉到垃圾回收。所以 JVM 运行时候次要从类加载 -> 在 JVM 的内存区域运行代码 -> 而后运行的代码进行垃圾回收。

2.1 Java 类加载机制

类加载必定是加载 java 中的.class 文件, 然而什么时候才会加载一个类? 以及类加载到应用开释的过程? 类加载有哪些类加载器以及类加载的时候类加载的原理。

JVM 在什么状况下会加载一个类?
当咱们通过 java -jar 命令运行一个 JVM 过程, 此时 JVM 会先加载咱们 main 办法所在的类, 而后通过字节码引擎执行执行 main 办法, 当须要某一个类的时候就会加载某个类。

类从加载到应用经验的过程
当须要应用到一个类的时候, 会加载这个类, 这个类从加载到应用的过程如下:
加载 -> 校验 -> 筹备 -> 解析 -> 初始化 -> 应用 -> 卸载。
加载: 类加载器加载须要应用到的类 (按需加载)。
校验: 校验加载的类是否合乎 JVM 标准, 不符合规范的报错, 不再进行其余步骤。
筹备: 给加载进来的类变量调配空间, 并设定默认值。援用类型为 null,根本类型为对应值的原始值, 比方 int 类型的为 0 等
解析: 将符号援用替换为间接援用
初始化: 先初始化类相干的代码比方动态成员变量, 动态代码块, 而后初始化实例数据, 初始化一个类时候, 发现父类还没初始化, 先初始化父类。
应用: 应用对象。
卸载: 垃圾回收。

咱们看下上面的代码执行步骤:

ReplicaManager:

/**
 * @Author: yexinming
 * @Description: 正本管理器
 * @Date: 2021/5/29 9:24 上午
 */
public class ReplicaManager extends AbstractReplicaManager{public static int flushInterval = Configuration.getInt("replica.flush.interval");

    public static Map<String,Replica> replicas;

    private Integer count;

    private int cnt;

    public ReplicaManager(){super(4);
        System.out.println("==ReplicaManager constructor 对象成员变量:count 之前 =="+this.count);
        System.out.println("==ReplicaManager constructor 对象成员变量:cnt 之前 =="+this.cnt);
        System.out.println("==ReplicaManager constructor 结构器办法执行 ==");
        this.count = 5;
        System.out.println("==ReplicaManager constructor 对象成员变量:count 之后 =="+this.count);
    }

    static {System.out.println("==ReplicaManager static  类成员变量:flushInterval=="+ReplicaManager.flushInterval);
        System.out.println("==ReplicaManager static  成员变量:replicas=="+ReplicaManager.replicas);

        System.out.println("==ReplicaManager static 动态代码块执行 ==");
        loadReplicaFromDisk();}

    public static void loadReplicaFromDisk(){System.out.println("==ReplicaManager loadReplicaFromDisk = 从本地加载正本");
        ReplicaManager.replicas = new HashMap<String, Replica>();}

    public void load(){System.out.println("==ReplicaManager load =ReplicaManager 加载");
    }

    public static void main(String[] args) {ReplicaManager manager = new ReplicaManager();
        manager.load();}
}

AbstractReplicaManager:

public class AbstractReplicaManager {

    private int size;

    static {System.out.println("==AbstractReplicaManager static==");
    }

    public AbstractReplicaManager(int size){System.out.println("==AbstractReplicaManager constructor before== size"+this.size);
        System.out.println("==AbstractReplicaManager constructor excuting==");
        this.size = size;
        System.out.println("==AbstractReplicaManager constructor after==AbstractReplicaManager size"+this.size);
    }
}

运行时候输入:

总结: 在应用类的时候咱们创建对象, 创建对象的时候会先进行类相干信息的创立(动态类变量,动态代码块), 而后执行对象信息的创立(结构器进行对象实现初始化)。在初始化阶段咱们发现其对应的累属性跟成员属性曾经有默认值了, 所以在筹备阶段就曾经赋值了, 在初始化类的时候会想执行其父类的初始化。

类加载器有哪些?
JVM 的类加载起具备亲子层级治理, 次要分为 4 级:
启动类加载器:Bootstrap ClassLoader: 次要负责加载咱们装置在机器上的 Java 目录下外围类。
扩大类加载器:Extension ClassLoader: 负责加载装置到机器上 Java 目录下的 lib/ext。
利用类加载器:Application ClassLoader 负责加载 ClassPath 环境变量下类, 能够了解为加载你写好的 java 代码。
自定义类加载器: 自定义类加载器, 依据本人需要加载你的类。

类加载器的规定
为了防止反复加载一个类:JVM 类加载机制应用双亲委派机制,比方你的本人写大代码执行时候,会先询问让利用类加载器去加载,而后利用类加载器去询问扩大类加载器是否能够加载, 扩大类加载器去询问启动类加载器去加载。而后启动类加载器去在对应职权范围内发现没有找到这个类,那么就让其子扩大类加载器去加载,而后扩大类加载器去对应职权范围内没有发现的话交给器子类去加载,而后利用类加载器去加载

2.2 JVM 的内存区域

咱们通过字节码执行引擎执行代码而后将用到的类通过类加载器加载到内存之后,就会应用这些类执行咱们的代码。首先咱们会把类加载到寄存类信息以及常量的办法区(因为类只会加载一次, 所以此办法去是线程共享的), 执行的代码后咱们须要记录下字节码执行引擎执行的地位, 所以须要程序计数器, 记录下每一个线程对办法执行到的地位,加载完类之后会创建对象将对象寄存到 java 堆内存中(线程共享), 对象创立之后会执行对象的办法, 因为办法的执行是多线程的, 所以此时会为此办法创立栈帧, 而后将办法跟对象的局部变量压栈道 Java 虚拟机栈, 同样如果办法底层调用 native 办法时候还会将其 native 办法压入本地办法栈中。当然还有不其余内存, 叫做堆外内存:NIO 中 allocateDirect 创立的内存空间不属于 JVM, 而是在堆外调配的内存空间。

2.3 什么是垃圾, 什么状况下进行回收?

JVM 的垃圾回收机制是用来干嘛的?为什么要垃圾回收?
对象的调配与援用
字节码执行引擎执行机器指令的时候, 如果执行到某一个办法时候, 会调配一个线程对应的 Java 虚拟机栈, 并创立一个办法栈帧; 而后将办法跟创立的局部变量压入栈帧。局部变量如果是创立的一个对象, 那么会在 java 堆内存调配, 并让 java 虚拟机栈的局部变量援用 java 堆内存对象。
一个办法执行结束之后会怎么样?
办法执行结束之后, 其办法会从对象的线程的 java 虚拟机栈中出栈, 而后办法的栈帧隐没, 局部变量小时, 此时在 Java 堆内存外面的对象将可能变成未被援用的对象, 变成垃圾对象。
不再须要的那些对象应该怎么解决?
JVM 一旦启动, 他就会自带一个垃圾回收的后盾线程, 这个线程会在后盾在触发 GC 时候会一直查看 JVM 堆内存外面的各个实例对象。java 堆内存外面的对象没有被局部变量援用之后, 他还占用着空间,所以咱们须要应用 JVM 的垃圾回收机制去回收这个对象。
什么是 JVM 中的 ” 垃圾 ”?
ava 堆内存外面的实例对象, 没有任何一个办法的局部变量指向他, 也没有任何一个类的动态变量, 包含常量指向他。

什么是 JVM 中的垃圾回收?
JVM 的后盾垃圾回收线程定期回收垃圾对象, 从内存外面革除掉, 让他不再占用内存。

3、JVM 的工作原理。

如下代码执行:

public class HDFS {private static ReplicaFetcher fetcher = new ReplicaFetcher();

    public static void main(String[] args) {loadReplicasFromDisk();
          fetcher.loadReplicaFromRemote();}

    public static void loadReplicasFromDisk(){ReplicaManager manager = new ReplicaManager();
        manager.load();}
}

咱们联合下面的代码从加载到执行最初隐没的 JVM 工作原理图.

上图阐明了:
1、执行多个办法的调用时,如何把办法的栈帧压入线程的 Java 虚拟机栈?
2、栈帧里如何放局部变量?
3、如何在 Java 堆里创立实例对象?
4、如何让局部变量援用那个实例对象?
5、办法运行完之后如何出栈?
6、垃圾回收是如何运行的?

扩大

加载到办法区的类会被回收吗?什么时候被回收呢?为什么?
在此状况下, 办法去外面的类会被回收:
1、首先, 该类的所有实例变量都曾经从 Java 堆内存里被回收。
2、其次加载这个类的 ClassLoader 曾经被回收。
3、最初, 对该类的 Class 对象没有任何援用。

正文完
 0