Java程序执行过程及内存机制详解

本讲将介绍Java代码是如何一步步运行起来的,其中涉及的编译器,类加载器,字节码校验器,解释器和JIT编译器在整个过程中是发挥着怎样的作用。此外还会介绍Java程序所占用的内存是被如何管理的:堆、栈和方法区都各自负责存储哪些内容。最后用一小块代码示例来帮助理解Java程序运行时内存的变化。

Java程序执行过程

  • 步骤 1: 写源代码,源代码将以.java的文件格式保存在电脑硬盘中。
  • 步骤 2: 编译器(compiler)检查是否存在编译期错误(例如缺少分号,关键字拼写错误等)。若通过检测,编译器就会将源代码翻译成字节码(bytecode),以.class的文件格式保存在电脑硬盘中。
  • 步骤 3: 若要运行此Java程序,JVM中会有一个叫类加载器(class loader)的内置程序把字节码从硬盘载入到正位于内存中的JVM里去。
  • 步骤 4: JVM中还有一个叫字节码校验器(bytecode verifier)的内置程序检测是否存在运行期错误(例如栈溢出)。若通过检测,字节码校验器就会将字节码传递给解释器(interpreter)。
  • 步骤 5: 解释器会对字节码进行逐行翻译,将其翻译成当前所在系统可以理解的机器码(machine code),
  • 步骤 6:将机器码交给操作系统,操作系统会以main方法作为入口开始执行程序。至此,一个Java程序就这样运行起来了。

细心的读者可能注意到了,在流程图中还涉及到一个叫JIT的东西在步骤中没有被解释。那么JIT编译器(Just-In-Time Compiler)是如果参与进程序的执行过程中呢?让我们来看以下两个例子。

  • 情况 1: 解释器对代码进行逐行解释,正如我们在步骤中所介绍的。
  • 情况 2: 这是JIT编译器参与进Java执行过程的情况,JIT编译器会扫描所有代码并对其进行优化。例如此时它发现最后一行代码是重复多余的,就会将其移除,只传递前4行代码给解释器。这样解释器就能运行地更快速高效,毕竟少了一行多余的代码需要翻译。

当然,这只是JIT编译器的优化手段之一,不同公司设计的JIT编译器对Java程序的运行会有不同的优化方式。此外需要知道的是,JIT编译器并不是每次都会参与到执行过程中来。

内存机制

在步骤3中我们谈到字节码会被类加载器载入到内存,那么载入之后JVM是如何对其进行内存管理的呢?

通常,在载入内存后,一个Java程序所占用的内存会被大致分为3块区域:堆(heap),栈(stack)和方法区(method area)。

堆:存放new出来的东西。

栈:存放局部变量。

方法区:类型信息,字段信息,常量池(constant pool),静态变量,方法信息等。

public final class Student extends Object implements Serializable
{
// 1.类信息
// 2.对象字段信息
private String name;
private int score;
// 3.常量池
public final int id = 0;
public final String gender = "male";
// 4.静态变量
public static int a = 0;
// 5.方法信息
public int getid()
{
return id;
}}

PC寄存器:存放将要执行的指令的地址。(因为机器的脑子不灵活,所以需要一块专门的区域帮他记住执行到哪一步,不然它会忘记)

本地方法栈:与JVM栈所发挥的作用是非常相似的,其区别不过是JVM栈为Java方法服务,而本地方法栈则是为使用到的Native方法服务。有的虚拟机(例如Sun HotSpot虚拟机)甚至直接就把本地方法栈和虚拟机栈合二为一。

每个线程拥有各自独立的(虚拟机)栈、PC寄存器和本地方法栈。而堆和方法区则是所有线程共享的。

最后让我们通过一个小例子来理解Java程序执行时内存的变化。

public class Person
{
int id; int age; Person(int id1, int age1)
{
id = id1; age = age1;
}
public static void main(String[] args)
{
Person Tom = new Person(1, 25);
}
}

首先,在stack中申请了一块内存,这块内存区域名字叫Tom,此时区域里存储的内容为null。

接着,调用Person的构造方法,方法的参数属于局部变量,因此在stack中有两块区域分别存放id1和age1。

通过构造方法,可以new出来一个Person的对象,这个对象连带着其成员变量会被存放在heap中。成员变量id和age的值由存放在stack中的局部变量id1和age1赋予。

最后,将这个对象的引用值(类似于地址)传递给Tom,通过引用值我们就可以找到这个对象。

(注意:位于stack中的id1和age1会随着构造方法调用的结束而消失,这里为了更好地表现全过程,因此保留在图中。)

关于栈和堆的其他小知识

  • 栈和堆的大小都是固定为一个默认值的,它们作为jvm的参数设定好了,不同的jvm设定的参数不同,相应的栈和堆的大小也就不同。
  • 栈是运行时的单位:里面存储的信息都是跟当前线程相关的,包括局部变量、程序运行状态、方法返回值等;而堆是存储的单位:它只负责存储对象。
  • 当一个方法调用结束后,方法里的局部变量会随之消失,不会在stack中继续占用空间。栈与堆的分离使得不同线程可以访问同一个对象,这是一种有效的数据交互方式(共享内存);此外也节省了空间,因为堆中的共享常量和缓存可以被所有栈访问。

参考https://simplesnippets.tech/execution-process-of-java-program-in-detail-working-of-just-it-time-compiler-jit-in-detail/https://blog.csdn.net/yfqnihao/article/details/8289363https://www.zhihu.com/question/29833675

到此这篇关于Java程序执行过程及内存机制详解的文章就介绍到这了,更多相关Java程序执行过程及内存机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

到此这篇关于Java程序执行过程及内存机制详解的文章就介绍到这了,更多相关Java程序执行过程及内存机制内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java程序执行时间的2种简单方法

    第一种是以毫秒为单位计算的.  Java代码  //伪代码   复制代码 代码如下: long startTime=System.currentTimeMillis();   //获取开始时间 doSomeThing();  //测试的代码段 long endTime=System.currentTimeMillis(); //获取结束时间 System.out.println("程序运行时间: "+(end-start)+"ms"); 第二种是以纳秒为单位计算的.

  • Java的内存机制详解

    Java把内存分为两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(比如,在函数A中调用函数B,在函数B中定义变量a,变量a的作用域只是函数B,在函数B运行以后,变量a会自动被销毁.分配给它的内存会被回收),Java会自动释放掉为该变量分配的内存空间,该内存空间可以立即另做他用. 堆内存用来存放由new创建的内存数组,在堆中分配的内存,由Jav

  • Java程序执行Cmd指令所遇问题记录及解决方案

    这篇是有关在编写Java程序执行Cmd指令时所遇到的问题记录,其中有一些是个人的理解,如有问题望不吝赐教,感谢❤ Windows 命令提示符(cmd.exe)是 Windows NT 下的一个用于运行 Windows 控制面板程序或某些 DOS 程序的shell程序 1.执行Cmd命令的两种方式 (1)RunTime.getRunTime().exec(多种重载方式) - 会得到一个Process对象通过其start()方法开启一个新进程以执行输入的指令. 这种方法就不多说了,最后这种形式还是用

  • Java程序执行过程及内存机制详解

    本讲将介绍Java代码是如何一步步运行起来的,其中涉及的编译器,类加载器,字节码校验器,解释器和JIT编译器在整个过程中是发挥着怎样的作用.此外还会介绍Java程序所占用的内存是被如何管理的:堆.栈和方法区都各自负责存储哪些内容.最后用一小块代码示例来帮助理解Java程序运行时内存的变化. Java程序执行过程 步骤 1: 写源代码,源代码将以.java的文件格式保存在电脑硬盘中. 步骤 2: 编译器(compiler)检查是否存在编译期错误(例如缺少分号,关键字拼写错误等).若通过检测,编译器

  • java中类加载与双亲委派机制详解

    目录 类加载是什么 类加载器 双亲委派机制 BootStrapClassLoader ExtClassLoader AppClassLoader 为什么使用双亲委派机制 全盘负责委托机制 自定义类加载器 打破双亲委派机制 类加载是什么 把磁盘中的java文件加载到内存中的过程叫做类加载 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM. 有如下 User 类 package dc.dccmmtop; public Class User { publi

  • Java SpringMVC拦截器与异常处理机制详解分析

    目录 拦截器(interceptor)的作用 拦截器快速入门 案例:用户登录权限控制 拦截器方法说明 SpringMVC异常处理 异常处理的思路 异常处理两种方式 拦截器(interceptor)的作用 Spring MVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理. 将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain).在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用.拦截器也是AOP思

  • SQL Server内存机制详解

    1.前言 对于数据库引擎来说,内存是一个性能提升的重要解决手段.把数据缓存起来,可以避免在查询或更新数据时花费多余的时间,而这时间通常是从磁盘获取数据时用来等待磁盘寻址的.把执行计划缓存起来,可以避免重复分析执行计划时带来额外的CPU及各种资源的开销.通过在内存中开辟查询内存空间,可以迅速地完成排序.哈希等计算,达到快速返回运算结果的目的.若没有足够的内存空间,数据库引擎将无法快速地响应用户的请求. 2.SQL Server如何从操作系统层面分配内存 SQL Server存储引擎本身是一个Win

  • nginx共享内存机制详解

    nginx的共享内存,是其能够实现高性能的主要原因之一,而其主要是用于对文件的缓存.本文首先会讲解共享内存的使用方式,然后会讲解nginx是如何实现共享内存的管理的. 1. 使用示例 nginx声明共享内存的指令为: proxy_cache_path /Users/Mike/nginx-cache levels=1:2 keys_zone=one:10m max_size=10g inactive=60m use_temp_path=off; 这里只是声明的一个名称为one,最大可用内存为10g

  • Python字节码与程序执行过程详解

    目录 问题: 1. 执行过程 2. 字节码 3. 源码编译 三种编译模式: 4. PyCodeObject 5. 反编译 6. pyc 问题: 我们每天都要编写一些Python程序,或者用来处理一些文本,或者是做一些系统管理工作.程序写好后,只需要敲下python命令,便可将程序启动起来并开始执行: $ python some-program.py 那么,一个文本形式的.py文件,是如何一步步转换为能够被CPU执行的机器指令的呢?此外,程序执行过程中可能会有.pyc文件生成,这些文件又有什么作用

  • Java 垃圾回收机制详解及实例代码

     Java 垃圾回收机制详解 乍一看,垃圾回收所做的事情应当恰如其名--查找并清除垃圾.事实上却恰恰相反.垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾.牢记了这点之后,我们再来深入地了解下这个被称为"垃圾回收"的自动化内存回收在JVM中到底是如何实现的. 手动管理内存 在介绍现代版的垃圾回收之前,我们先来简单地回顾下需要手动地显式分配及释放内存的那些日子.如果你忘了去释放内存,那么这块内存就无法重用了.这块内存被占有了却没被使用.这种场景被称之为内存泄露. 下面是用C写

  • Java基础之垃圾回收机制详解

    一.GC的作用 进行内存管理 C语言中的内存,申请内存之后需要手动释放:一旦忘记释放,就会发生内存泄漏! 而Java语言中,申请内存后会由GC来释放内存空间,无需手动释放 GC虽然代替了手动释放的操作,但是它也有局限性: 需要消耗更多的资源: 没有手动释放那么及时: STW(Stop The World)会影响程序的执行效率 二.GC主要回收哪些内存 (1)堆:主要回收堆中的内存 (2)方法区:需要回收 (3)栈(包括本地方法栈和JVM虚拟机栈):不需要回收,栈上的内存什么时候释放是明确的(线程

  • java开发分布式服务框架Dubbo原理机制详解

    目录 前言 Dubbo框架有以下部件 Consumer Provider Registry Monitor Container 架构 高可用性 框架设计 服务暴露过程 服务消费过程 前言 在介绍Dubbo之前先了解一下基本概念: Dubbo是一个RPC框架,RPC,即Remote Procedure Call(远程过程调用),相对的就是本地过程调用,在分布式架构之前的单体应用架构和垂直应用架构运用的都是本地过程调用.它允许程序调用另外一个地址空间(通常是网络共享的另外一台机器)的过程或函数,并且

随机推荐