大柚子

这世界不过如此

java编译和执行过程

java代码到执行

java是高级语言,【执行】需【编译】成机器语言才能执行,如下所示


java代码 =》 编译 =》 执行

编译的内容需要先经过【class loader】 加载到 【JVM(Java Virtual Machine)】才能执行。


java代码 =》 编译 =》读取(class loader) =》 JVM环境 =》 执行

在这个过程中,Java代码编译后(javac *.java file)会生成【.class】文件,

类加载器(class loader)读取.class文件到JVM

之后【执行引擎(execution engine)把这些文件解析成【机器码(Binary Code)】存放到JVM的【运行数据区Runtime Data Area】后,执行程序。

类加载器Class Loader

.java文件编译后生成.class文件

类加载器Class Loader 的工作就是把.class文件读取到JVM中,在这个过程中类加载器Class Loader会做三件事情

  • 加载(Loading)
  • 链接(Linking)
  • 初始化(initialization)

加载Loading

一般情况下类加载器分3层结构

1、引导(启动)类加载器Bootstrap ClassLoader

最上层类。加载<JAVA_HOME>/jre/lib内容。用Native C编写

2、扩展类加载器Extension ClassLoader

加载<JAVA_HOME>/jre/lib/ext内容。用java编写

3、应用程序类加载器Application ClassLoader

加载 -classpath(或 -cp)的内容。用java编写


注:这3层结构遵守以下原则
Delegation Principle  =&gt; 从上到下委派加载任务
Visibility Principle  =&gt; 子类加载可以使用父类加载,相反则不行
Uniqueness Principle  =&gt; 子类加载时不会再次加载父类加载的内容

链接Linking

加载完成后,会做链接Linking,步骤如下:

1、验证Verify

确保加载的内容的正确性和安全性

2、准备Prepare

为类的静态变量分配内存,并设置默认初始值

3、解析Resolve

将常量池内的符号引用转换为直接引用的过程

初始化(initialization)

初始化是为类的静态变量赋予正确的初始值。

这是初始化不需要定义,是javac编译器自动收集类中的所有类变量的赋值和静态代码。

有父类先执行父类的初始化方法,再执行子类的初始化方法

执行引擎Execution Engine

用于执行字节码或者本地方法

执行的过程一般会用解释模式和JIT编译模式的混合模式

1、解释器Interpreter

执行引擎会把类加载器读取过来的.class文件的字节码校验合法性和安全性后,边读取边执行

2、JIT编译器JIT(just in time)Compiler

当解释模式运行的代码满足一定的条件以后,会编译到JVM的缓存中。

下一次执行的时候因为不需要再编译提高执行速度。


如果偶尔执行一次的代码或是只执行一次的话解释模式执行的速度会更快。
频繁的调用执行的代码的话,JIT编译模式下第一次执行速度会比较满,后续会提高执行速度
大部分情况下都会有混合模式。适用JIT编译模式的条件可以在不同的场景下需要做一些调整

3、垃圾回收器Garbage Collector

执行引擎还跟垃圾回收器Garbage Collector 有关。

一般情况下垃圾回收期会回收运行数据区Runtime Data Area的堆内存

它会释放栈区里已经用完,找不到相对应地址的堆内存,这些内存会分伊甸区、幸存区、老年区…用一些算法来管理和清理回收内存。

其实执行引擎是操控这这些编译和回收器,而不是它们属于执行引擎

运行数据区Runtime Data Area

为了执行程序,给JVM分配内存空间。

可以分 共享线程区(堆区、方法区、直接内存区)和私有线程区(栈区、程序计数器,本地方法栈)

方法区Method Area

是各个线程共享的内存区域,它用于存储已被JVM加载的类class,函数function,常量final variable,实例变量member variable

以及被即时编译器(JIT)编译后的代码都会存放在这里。

方法区有时被称为持久化(PermGen)。

堆Heap

通过New 创建的实例instance,队列等引用reference类型,Method Area的class才能声明这里。此内存区域就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。

如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的又Eden空间、From Survivor空间、To Survivor空间等。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

直接内存(堆外内存)Direct Memory

在JDK1.4中新加入了NIO(New INput/Output)类,其实不完全的被JVM所控制。
它可以使用Native函数库直接分配堆外内存,避免堆和Native堆来回复制数据,提高性能。

栈Stack

它的生命周期与线程相同是一个临时存储空间。存变量(boolean、byte、char、short、int、float、long、double),返回值,对象引用类型(指向一条字节码指令的地址,其实体是存在堆Heap区域),这些值遵守LIFO(Last In First Out),当函数执行完成以后会删除掉。

PC寄存器Program Counter Register

每个线程thread都有一个。记录每个线程用哪个指令执行哪部分的内容,存储着现在执行中的JVM命令地址。若该方法为native,则PC寄存器中不存储任何信息。

Java多线程情况下,每个线程都有一个自己的PC,以便完成不同线程上下文环境的切换。

本地方法栈Native Method Stack

为了调用java以外的其他语言的方法分配的空间

java调用C/C++方法是使用的stack领域就是这里

其实直接内存不是完全属于JVM的运行数据区Runtime Data Area

JVM(Java Virtual Machine)

把上面全部都结合起来就是JVM的结构了

举例说明


首先product.java通过java编译成product.class    
然后Class Loader会把product.class和prodcut引用的Class Library加载到JVM环境。  
这时Execution Engine做一些列验证和解析后(混合模式下,第一次不会直接走JIT编译)    
把类信息和方法存放到Runtime Data Area的Method Area里。  

当程序调用方法时,先把变量以及局部变量以及返回值加载到JVM的stack区域,    
之后用New方式声明的实例或排列是存放到Heap区域,stack区域只保存对应的内存地址。  
等方法结束后stack区域的值是跟着方法结束会一起清空,    
但是heap区域的空间是需要等Garbage Collector来回收。
Print Friendly, PDF & Email

发表回复

您的电子邮箱地址不会被公开。