前言
学习技术特地是工作中常常使用不到的技术兴许会让很多人感觉烦闷干燥,实践如果不联合实际终究会成为过眼云烟。技术没有捷径,须要考究办法地继续学习。既然是原理,他必定是一个动作化地一系列行为总和。行为的背地必定有主体或者实体以及引入的背景。所以集体感觉在面试中,如果面试官问一些原理性货色,最好的形式还是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对象没有任何援用。