听说你还不知道Java代码是怎么运行的?
作者:Jay_huaxiao
作为一名Java程序员,我们需要知道Java代码是怎么运行的。最近复习了深入理解Java虚拟机这本书,做了一下笔记,希望对大家有帮助,如果有不正确的地方,欢迎提出,感激不尽。
java 代码运行主要流程
本文主要讲解流程如下:
- java源文件编译为class字节码
- 类加载器把字节码加载到虚拟机的方法区。
- 运行时创建对象
- 方法调用,执行引擎解释为机器码
- CPU执行指令
- 多线程切换上下文
编译
我们都知道,java代码是运行在Java虚拟机上的。但是java是一门面向对象的高级语言,它不仅语法非常复杂,抽象程度也非常高,并不能直接运行在计算机硬件机器上。
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境。
因此,在运行Java程序之前,需要编译器把代码编译成java虚拟机所能识别的指令程序,这就是Java字节码,即class文件。
所以,Java代码运行的第一步是:把Java源代码编译成.class 字节码文件。
类加载
在Class文件中描述的各种信息,需要被加载到虚拟机之后才能运行和使用。因此,需要把class字节码文件加载到Java虚拟机来。
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
加载
在加载阶段,虚拟机需要完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
加载阶段完成后,这些二进制字节流按照虚拟机所需的格式存储在方法区之中。
验证
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机的安全,Java虚拟机对输入的字节流走验证过程。
验证阶段包括四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。
- 文件格式验证: 验证字节流是否符合Class文件格式规范,如:是否以魔数0xCAFEBABE开头。
- 元数据验证: 对字节码描述的信息进行语义分析,如:这个类的父类是否继承了不允许被继承的类(被final修饰的类);
- 字节码验证: 主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。如:保证跳转指令不会跳转到方法体以外的字节码指令上。
- 符号引用验证: 发生在虚拟机将符号引用转化为直接引用的时候,如:校验符号引用中通过字符串描述的全限定名是否能找到对应的类。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。如:
public static int value =123;
变量value在准备阶段过后的初始值是0而不是123。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
比如:com.User类引用com.Tool类,在编译时,User类不知道Tool类的实际内存地址,因此只能使用符号com.Tool(假设)来表示。而在类加载加载User类的时候,可以通过虚拟机获取Tool类的实际内存地址,因此便可以将符号com.Tool替换为Tool类的实际内存地址,即直接引用地址。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。
初始化
到了初始化阶段,才真正开始执行类中定义的Java字节码。在这个阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
创建对象
Java虚拟机是如何执行字节码的呢?我们先来看一下运行时创建对象。
Java是面向对象的编程语言,程序的运行是以对象为调用单位的。
- 字节码文件加载到虚拟机的方法区后,在程序运行过程,通过 class字节码文件创建与其对应的对象信息 。
- 创建对象的方式有:new关键字,反射等。
- Java堆内存是线程共享的区域,创建后的对象信息就保存在Java堆内存中。
方法调用
JVM的调用单位是对象,但是真正执行功能性的代码还是对象上的方法。
在运行过程中,每当调用进入一个java方法,java虚拟机会在当前线程的java方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。方法栈内存是线程私有的,每个线程都有自己的方法栈。如果对应的方法是本地方法,则对应的就是本地方法栈。
java运行时数据区域如下:
解释
当调用Java对象的某个方法时,JVM执行引擎会将该方法的字节码文件翻译成计算机所能识别的机器码,机器码信息保存在方法区中。翻译有解释执行和即时编译两种方式。
两种翻译方式的区别如下:
解释执行来一行代码,解释一行,大部分不常用的代码,都是采用这种方式。
即使编译
对于部分热点代码,将一个方法包含的所有字节码翻译成机器指令,以提高java虚拟机的运行效率。
即时编译是建立经典的二八定律上,即20%代码占据了80%的计算资源。
执行指令
- Java程序被加载入内存后,指令也在内存中了。
- 指令的指令寄存器IP,指向下一条待执行指令的地址。
- CPU的控制单元根据IP寄存器的指向,将主存中的指令装载到指令寄存器,这些加载的指令就是一串二进制码,还需要译码器进行解码。
- 解码后,如果需要获取操作数,则从内存中取数据,调用运算单元进行计算。
多线程上下文切换
CPU一通上电,就会周而复始从内存中获取指令、译码、执行。
- 为了支持多任务,CPU 将执行时间这个资源划分成时间片,每个程序执行一段时间。
- java虚拟机的多线程是通过线程轮流切换分配处理执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条程序中的指令。
- 假设当前线程在运行中,CPU分配的时间执行完了,总得保存运行过的结果信息吧,要不然白白浪费之前的工作了,因此,程序计数器(PC寄存器)作用体现出来了,它是一块较小的内存空间,线程私有,可以看作当前线程执行的字节码的行号指示器。当CPU又给它分配时间跑的时候,可以把数据恢复,接着上一次执行到的位置继续执行就可以了。
原文:https://juejin.im/entry/5e6ccc05e51d4527110aa25f
Java虚拟机(jvm)-简介
一、Java运行时虚拟机内存区域划分
1.元空间(Metaspace)
元空间(Metaspace)从java8开始替换掉了原来的方法区(Method Area)。相比方法区(Method Area)在元空间中各个项目会共享同样的class内存空间,能提高内存的利用率且更利于垃圾回收;另外元空间并不在虚拟机中,而是使用本地内存,因此元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
2.堆(Heap)
堆(Heap)是Java 虚拟机所管理的内存中最大的一块,被所有线程共享的区域在虚拟机启动时创建。堆里面存放的都是对象的实例(new 出来的对象都存在堆中)。
堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”。堆又可分为新生代和老年代(Tenured Gen),更细一点划分新生代有可分为Eden Space(伊甸园区),Survivor Space(幸存者区)。
Eden:新创建的对象就会在Eden Space(伊甸园区)。当GC机制执行后没有被引用的对象将会被kill掉,其他的会进入Survivor Space(幸存者区)。
Survivor:保存新生代GC后还存活的对象。
Tenured Gen:对象存活时间比较长,经过多次新生代的GC(默认是15次)如果还存活将进入Tenured Gen(老年代)。
3.虚拟机栈(vm stacks)
虚拟机栈是线程私有的,生命周期与线程相同。创建线程的时候就会创建一个java虚拟机栈。
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈帧又分为一下几个区域:局部变量表、操作数栈、动态连接、方法出口等。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。
4.本地方法栈(Native Method Stack)
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,其区别是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。
5.程序计数器(Programe Counter Register)
程序计数器(Programe Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器,其记录当前线程执行程序的位置,通过改变计数器的值来确定执行的下一条指令,比如循环、分支、方法跳转、异常处理,线程恢复都是依赖程序计数器来完成。
Java虚拟机多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换能恢复到正确的位置,每条线程都需要一个独立的程序计数器,所以它是线程私有的。
如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。
二、Java虚拟机参数说明
-Xms size
设置堆的初始大小(以字节为单位)。该值必须是1024的倍数且大于1 MB。附加字母k或K表示千字节,m或M指示兆字节,g或G指示千兆字节。
-Xmx size
设置堆内存的最大大小(以字节为单位),附加字母k或K表示千字节,m或M指示兆字节,g或G指示千兆字节。一般的服务端部署,-Xms和-Xmx设置为同样大小以避免每次GC后调整堆的大小。-Xmx 与-XX:MaxNewSize等效。默认值为物理内存的1/4。
-Xmn size
设置堆新生代大小(以字节为单位),附加字母k或K表示千字节,m或M指示兆字节,g或G指示千兆字节。-Xmn与-XX:NewSize等效。
堆的年轻代区域用于新对象。GC在该区域比在其他区域更频繁地进行。如果年轻一代的规模太小,那么将会进行大量的小型垃圾收集。如果大小太大,则只执行完整的垃圾收集,这可能需要很长时间才能完成。Oracle建议您将年轻代的大小保持在整个堆大小的一半到四分之一之间。
-Xss size
设置线程堆栈大小(以字节为单位)通常只有几百K。附加字母k或K表示KB,m或M表示MB,g或G表示GB。默认值取决于平台(如:Linux / x64(64位):1024 KB)。
-XX:PermSize size -XX:MaxPermSize size
设置永久区的初始空间和最大空间。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。
-XX:PermSize size -XX:MaxPermSize size在Jdk1.8中已经被弃用,被-XX:MetaspaceSize size -XX:MaxMetaspaceSize size选项取代。
-XX:NewRatio
新生代和年老代的堆内存占用比例。
-XX CompressedClassSpaceSize size类指针压缩空间大小, 默认为1G,只有当-XX:+UseCompressedClassPointers开启了才有效。
「Java面试」什么是Java虚拟机,为什么要使用?
“什么是Java虚拟机,为什么要使用”。
最近一个1年Java开发经验的同学去面试阿里,遇到这个问题向我求助。
Hi,大家好,我是Mic,一个工作14年的Java程序员。
那么,这个问题,面试官希望考察什么呢?
Java虚拟机,是Java应用程序运行的平台。
很多初学者,第一步基本上都是学习怎么写代码,并没有关注Java代码所运行的平台。
因此,虽然写了几年代码,但是对Java本身的理解不够深刻,程序一旦出现问题,很难排查和解决。
面试官考察这个问题的出发点,我认为有三个
- 了解求职者对于Java语言的理解深度,这个方面有助于提升代码编写的质量
- 了解求职者对于JVM基础的掌握程度,良好的基础有助于快速解决GC问题、内存问题等
- 考察求职者的潜质,一个对技术有热情的人,有助于更好的陪伴公司成长
所以,对于这个问题来说,我们只需要从JVM关键特性Write Once、Run Anywhere这个角度去切入解释就行了。
下面我们来看看高手应该怎么回答。
Java虚拟机是Java语言的运行环境。
之所以需要Java虚拟机,主要是为Java语言提供Write Once,Run Anywhere能力。
实际上,一次编写,到处运行这个能力本身是不可能实现的。因为不同的操作系统和硬件。
最终执行的指令会有较大的差异。
而Java虚拟机就是解决这个问题的,它能根据不同的操作系统和硬件差异,生成符合这个平台机器指令。
简单理解,它就相当于一个翻译工具,在window下,翻译成window可执行的指令,在linux下,
翻译成linux下可执行的指令。
除了这个因素以为,我认为自动回收垃圾这个功能也是原因之一,它让开发者省去了垃圾回收这个工作。
减少了程序开发的复杂性。
好了,今天的分享就到这里结束了
如果喜欢我的作品,记得点赞、收藏、关注!
需要高手面试文档(附赠大厂内部十万字面试文档)或者有不懂的技术面试题想咨询的小伙伴可以后台私信【Mic】或者评论区留言。
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。