一个Java文件的执行全部过程你确定都清楚吗?

平时我们都使用 idea、eclipse 等软件来编写代码,在编写完之后直接点击运行就可以启动程序了,那么这个过程是怎么样的?
总体过程

一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
我们编写的 JAVA 文件在由编译器编译后会生成对应的 class 字节码文件,然后再将 class 字节码文件转给 JVM。JVM 会处理解析 class 文件,将其内部设置的类、方法、常量等信息全部提取出来,然后找到 main 方法开始一步一步编译成机器码并执行,中间会根据需要调用前面提取的数据 。
那为什么不让 JVM 直接编译 java 文件呢?这样效率不是更高么?首先要知道 java 之所以强大,原因之一就是 JVM 的强大 。
强大之一是 JVM 是 " 跨平台 " 的 。无论在哪种操作系统上执行,都可以转成对应的机器语言,不需要担心适配问题 。
第二点就是 JVM 是 " 跨语言 " 的,因为 JVM 只认 class 文件,所以其他语言只需要一个编译器编译成 class 文件就可以使用 JVM 来编译执行了 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
组件分析根据上面的说明可以知道 java 程序执行的核心是通过 JVM 来实现的,那么就需要知道 JVM 内部是如何执行的 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
JVM 内部可以分为四大部分,运行时数据区域、类加载系统、执行引擎、本地接口和本地方法库 。
类加载系统:主要就是指类加载器,用于把 class 数据文件加载到运行时数据区域,然后由数据区域来编译执行 。
运行数据区域:搭配执行引擎来编译传来的文件中的代码,然后执行,并且根据需要通过本地方法接口调用本地方法 。
执行引擎:主要用于代码的编译和 运行时对象的回收 。
本地库接口和本地方法库:提供一些 java 无法实现,需要底层执行调用的方法,是 jvm 访问底层的重要途径 。
类加载器用于进行类的加载 。
种类
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
一般分为启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器 。图中的从自定义类加载器到启动类加载器一层一层使用箭头连接,这种箭头并不是继承关系,而是上下级关系 。上下级的联系是通过 ClassLoader 抽象类继承过来的 parent 属性设置的 。
1、启动类加载器(Bootstrap ClassLoader)(引导类加载器),加载java 核心类库( <JAVA_HOME>/jre/lib/rt.jar), 无法被java程序直接引用,是用C++编写的 ,用来加载其他的类加载器(类加载器本质就是类),是所有加载器的父类 。
2、拓展类加载器(Extension ClassLoader),用来加载java 的拓展库( <JAVA_HOME>/jre/lib/ext) 。
3、系统类加载器(System ClassLoader )(应用程序类加载器),用来加载类路径下的 Java类
4、用户自定义类加载器,继承java.lang.ClassLoader类的方式实现 。
官方文档中将类加载器分为引导类加载器和自定义类加载器,这是因为引导类加载器是使用其他语言实现的,而拓展类、系统类、自定义类加载器全部都是通过继承 ClassLoader 抽象类实现的,所以都统一被划分为自定义类加载器 。
装载方式1、隐式装载:由加载器加载 。
2、显式装载: 自定义加载,比如使用反射Class.forName(类路径), 类加载器 ClassLoader.getSystemClassLoader().loadClass("test.A");使用当前进程上下文的使用的类装载Thread.currentThread().getContextClassLoader().loadClass("test.A") 。
类加载是动态的,它不会一次性加载所有类然后运行,而是保证程序运行的基础类(核心类库一部分的类)完全加载到JVM中就运行,这是为了节省内存开销 。
类加载器的特性主要包括全盘负责、双亲委托机制、缓存机制、可见性 。
1、全盘负责:当一个 Class 类被某个类加载器所加载时,该 Class 所依赖引用的所有 Class 都会由这个加载器负责载入,除非显式的使用另一个 ClassLoader 。( 当然只是这个加载器负责,并不一定就是由这个加载器加载,这是由于双亲委托机制的作用 )
2、缓存机制:当一个 Class 类加载完毕后,会放入缓存,在其他类需要引用这个类时就会从缓存中直接使用,这也是为什么我们在修改了文件后需要重启服务器才能使修改生效 。
3、双亲委托机制: 当一个类加载器收到了类加载的请求时,它首先会将这个请求委派给父类,父类不能执行再自己尝试执行,父类如果存在父类,也会委派给父类,这样传到了启动类加载器加载,当启动类加载器不能读取到类时才会传给子类加载器,然后子类加载器再尝试加载 。


推荐阅读