JVM相关
Java代码执行流程
源码编写:编写java文件
编译生成class文件:将源代码文件编译生成class字节码文件,字节码文件存放了类的字段、方法、父类、实现的接口等信息
加载class文件:通过类加载器将class二进制数据读入内存。读取class文件中的数据并存储在方法区中,建立一个Class对象,作为运行时访问类中数据的接口。
运行class文件:JVM执行class文件,执行引擎找到main()入口方法,在栈里创建一个栈帧,逐行执行方法中的字节码指令。操作完成后返回给调用方,栈帧出栈。
垃圾回收:JVM垃圾回收机制GC
Java内存结构
堆内存(线程共享):存放实例对象,是垃圾收集器管理的主要区域,也被称为GC堆
方法区(线程共享):存放类的所有信息,静态变量,常量。包含运行时常量池(存放编译器生成的各种字面量和符号引用)
虚拟机栈内存(线程私有):一个线程对应一个栈,生命周期与线程相同。描述的是java方法执行的内存模型:每个方法执行时会创建一个栈帧,用于存放局部变量、操作数栈、方法出口等信息。每一个方法从调用直至完成的过程,对应着一个栈帧在虚拟机中从入栈到出栈的过程
本地方法栈(线程私有):用于支持Native方法执行,存储了每个Native方法调用的状态
程序计数器(线程私有):指向方法区方法字节码(下一个指令的地址),并由执行引擎读取并执行下一指令
类加载机制
加载:通过类的全限定名获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转换为方法区内的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的数据的访问入口
验证:文件格式、源数据、字节码、符号引用
准备:正式为类变量分配内存并设置类变量初始值的阶段
解析:虚拟机将常量池内的符号引用替换为直接引用的过程
初始化:执行类构造器()方法的过程
类加载器
类加载器分类
启动类加载器(Bootstrap ClassLoader): 由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中,即负责加载Java的核心类。
其他类加载器: 由Java语言实现,继承自抽象类ClassLoader。如:
扩展类加载器(Extension ClassLoader): 负责加载\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库,即负责加载Java扩展的核心类之外的类。
应用程序类加载器(Application ClassLoader): 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器,通过ClassLoader.getSystemClassLoader()方法直接获取。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
这样的好处是不同层次的类加载器具有不同优先级,比如所有Java对象的超级父类java.lang.Object,位于rt.jar,无论哪个类加载器加载该类,最终都是由启动类加载器进行加载,保证安全。即使用户自己编写一个java.lang.Object类并放入程序中,虽能正常编译,但不会被加载运行,保证不会出现混乱。
整个流程大致如下:
a.首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
b.如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);),如果没有父加载器,调用bootstrap类加载器来加载。
c.如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载(自定义加载器)。
原文链接:https://blog.csdn.net/qq_29966203/article/details/100567609