学习技术特别是工做中常常运用不到的技术也许会让不少人感受烦闷枯燥,理论若是不结合实践终究会成为过眼云烟。技术没有捷径,须要讲究方法地持续学习。既然是原理,他确定是一个动做化地一系列行为总和。行为的背后确定有主体或者实体以及引入的背景。因此我的以为在面试中,若是面试官问一些原理性东西,最好的方式仍是why what how;why-用于解答为何须要这个东西,what-说明这个东西是什么以及其特色 how-主要是结合what行为化的描述其是如何实现特色的。java
本节是我的学习总结,主要是结合简单的案例demo代码以图文并貌的形式总结下本身学习的JVM工做原理。从为何引入JVM的java代码如何运行?到JVM运行时候须要用到什么主体-类加载器、字节码执行引擎、JVM的各个运行时候数据区以及JVM在怎样运行咱们代码的,类加载器加载的数据会加载存储到哪些数据区,字节码执行引擎的时候是如何结合咱们的JVM运行数据去来工做的?以此来加深本身的知识体系。面试
咱们工做中写的代码是如何运行起来呢?咱们的.java后缀的代码(源代码)通过打包编译后成为字节码.class(jar/war包),而后启动一个tomcat或者使用java -jar xxx.jar便可开启一个JVM进程来运行咱们的java代码。上面就是咱们为何须要JVM的缘由。 tomcat
咱们从工做中写的代码如何运行起来能够知道?JVM运行起来时候是从编译后的.class文件进行加载,而后确定是加载到内存中,因此这里面就有JVM的运行数据区,运行的代码文件不可能永无止境在加载到内存,确定会存在内存满的时候,因此就存在垃圾,而后就牵涉到垃圾回收。因此JVM运行时候主要从类加载->在JVM的内存区域运行代码->而后运行的代码进行垃圾回收。多线程
类加载确定是加载java中的.class文件,可是何时才会加载一个类?以及类加载到使用释放的过程?类加载有哪些类加载器以及类加载的时候类加载的原理。学习
JVM在什么状况下会加载一个类?
当咱们经过java -jar命令运行一个JVM进程,此时JVM会先加载咱们main方法所在的类,而后经过字节码引擎执行执行main方法,当须要某一个类的时候就会加载某个类。fetch
类从加载到使用经历的过程
当须要使用到一个类的时候,会加载这个类,这个类从加载到使用的过程以下:
加载->校验->准备->解析->初始化->使用->卸载。
加载:类加载器加载须要使用到的类(按需加载)。
校验:校验加载的类是否符合JVM规范,不符合规范的报错,再也不进行其余步骤。
准备:给加载进来的类变量分配空间,并设定默认值。引用类型为null,基本类型为对应值的原始值,好比int类型的为0等
解析:将符号引用替换为直接引用
初始化:先初始化类相关的代码好比静态成员变量,静态代码块,而后初始化实例数据,初始化一个类时候,发现父类还没初始化,先初始化父类。
使用:使用对象。
卸载:垃圾回收。 this
咱们看下下面的代码执行步骤: spa
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
:code
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类加载机制使用双亲委派机制,好比你的本身写大代码执行时候,会先询问让应用类加载器去加载,而后应用类加载器去询问扩展类加载器是否能够加载,扩展类加载器去询问启动类加载器去加载。而后启动类加载器去在对应职权范围内发现没有找到这个类,那么就让其子扩展类加载器去加载,而后扩展类加载器去对应职权范围内没有发现的话交给器子类去加载,而后应用类加载器去加载
咱们经过字节码执行引擎执行代码而后将用到的类经过类加载器加载到内存以后,就会使用这些类执行咱们的代码。首先咱们会把类加载到存放类信息以及常量的方法区(因为类只会加载一次,因此此方法去是线程共享的),执行的代码后咱们须要记录下字节码执行引擎执行的位置,因此须要程序计数器,记录下每个线程对方法执行到的位置,加载完类以后会建立对象将对象存放到java堆内存中(线程共享),对象建立以后会执行对象的方法,因为方法的执行是多线程的,因此此时会为此方法建立栈帧,而后将方法跟对象的局部变量压栈道Java虚拟机栈,一样若是方法底层调用native方法时候还会将其native方法压入本地方法栈中。固然还有不其余内存,叫作堆外内存:NIO中allocateDirect建立的内存空间不属于JVM,而是在堆外分配的内存空间。
JVM的垃圾回收机制是用来干吗的?为何要垃圾回收?
对象的分配与引用
字节码执行引擎执行机器指令的时候,若是执行到某一个方法时候,会分配一个线程对应的Java虚拟机栈,并建立一个方法栈帧;而后将方法跟建立的局部变量压入栈帧。局部变量若是是建立的一个对象,那么会在java堆内存分配,并让java虚拟机栈的局部变量引用java堆内存对象。
一个方法执行完毕以后会怎么样?
方法执行完毕以后,其方法会从对象的线程的java虚拟机栈中出栈,而后方法的栈帧消失,局部变量小时,此时在Java堆内存里面的对象将可能变成未被引用的对象,变成垃圾对象。
再也不须要的那些对象应该怎么处理?
JVM一旦启动,他就会自带一个垃圾回收的后台线程,这个线程会在后台在触发GC时候会不断检查JVM堆内存里面的各个实例对象。java堆内存里面的对象没有被局部变量引用以后,他还占用着空间,因此咱们须要使用JVM的垃圾回收机制去回收这个对象。
什么是JVM中的"垃圾"?
ava堆内存里面的实例对象,没有任何一个方法的局部变量指向他,也没有任何一个类的静态变量,包括常量指向他。
什么是JVM中的垃圾回收?
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工做原理图. 。
上图说明了:
一、执行多个方法的调用时,如何把方法的栈帧压入线程的Java虚拟机栈?
二、栈帧里如何放局部变量?
三、如何在Java堆里建立实例对象?
四、如何让局部变量引用那个实例对象?
五、方法运行完以后如何出栈?
六、垃圾回收是如何运行的?
加载到方法区的类会被回收吗?何时被回收呢?为何?在此状况下,方法去里面的类会被回收:一、首先,该类的全部实例变量都已经从Java堆内存里被回收。 二、其次加载这个类的ClassLoader已经被回收。三、最后,对该类的Class对象没有任何引用。