JVM整理

1.JVM
1.1.定义
Java平台可分为两部分,即Java虚拟机(Java virtual machine,JVM)和Java API类库。JVM是Java Virtual Machine(Java虚拟机)的缩写,引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
1.2.组成部分
Java虚拟机主要分为五大模块:类装载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。
在这里插入图片描述
1.2.1.类装载器子系统
(1)原理:
JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆(并不一定在堆中,HotSpot在方法区中)中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口。
(2)过程:
JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。其中加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的,而解析则未必。为了支持动态绑定,解析这个过程可以发生在初始化阶段之后。
在这里插入图片描述
① 加载:
加载过程主要完成三件事情:
1)通过类的全限定名来获取定义此类的二进制字节流
2)将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
3)在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。
这个过程主要就是类加载器完成。
② 校验:
此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
1)文件格式验证:基于字节流验证。
2)元数据验证:基于方法区的存储结构验证。
3)字节码验证:基于方法区的存储结构验证。
4)符号引用验证:基于方法区的存储结构验证。
③ 准备:
为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。例如:
public static int value = 123;
此时在准备阶段过后的初始值为0而不是123;将value赋值为123的putstatic指令是程序被编译后,存放于类构造器方法之中.特例:
public static final int value = 123;
此时value的值在准备阶段过后就是123。
④ 解析:
1)把类型中的符号引用转换为直接引用。
a.符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
b.直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在
2)主要有以下四种:
a.类或接口的解析
b.字段解析
c.类方法解析
d.接口方法解析
⑤ 初始化:
初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。
java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):
1)使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
2)初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
3)使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
4)虚拟机启动时,用户会先初始化要执行的主类(含有main)
5)jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。
(3)类加载器:
把类加载阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作交给虚拟机之外的类加载器来完成。这样的好处在于,我们可以自行实现类加载器来加载其他格式的类,只要是二进制字节流就行,这就大大增强了加载器灵活性。系统自带的类加载器分为三种:
① 启动类加载器。
② 扩展类加载器。
③ 应用程序类加载器。
(4)双亲委派机制:
在这里插入图片描述
① 双亲委派机制工作过程:
如果一个类加载器收到了类加载器的请求.它首先不会自己去尝试加载这个类.而是把这个请求委派给父加载器去完成.每个层次的类加载器都是如此.因此所有的加载请求最终都会传送到Bootstrap类加载器(启动类加载器)中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时.子加载器才会尝试自己去加载。
② 双亲委派模型的优点:
java类随着它的加载器一起具备了一种带有优先级的层次关系.
例如类java.lang.Object,它存放在rt.jart之中.无论哪一个类加载器都要加载这个类.最终都是双亲委派模型最顶端的Bootstrap类加载器去加载.因此Object类在程序的各种类加载器环境中都是同一个类.相反.如果没有使用双亲委派模型.由各个类加载器自行去加载的话.如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的ClassPath中.那系统中将会出现多个不同的Object类.java类型体系中最基础的行为也就无法保证.应用程序也将会一片混乱.
1.2.2.运行时数据区
(1)组成部分:
在这里插入图片描述
(2)各部分功能:
① 程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。
如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
② Java 虚拟机栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
③ 本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
④ Java 堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
⑤ 方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
⑥ 运行时常量池
属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
⑦ 直接内存
非虚拟机运行时数据区的部分
在 JDK 1.4 中新加入 NIO (New Input/Output) 类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。
OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。
在这里插入图片描述
1.2.3.垃圾收集模块
(1)定义
垃圾回收(Garbage Collection,GC),就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
(2)垃圾判断算法
① 引用计数法
1)定义:给每个对象添加一个计数器,当有地方引用该对象时计数器加1,当引用失效时计数器减1。用对象计数器是否为0来判断对象是否可被回收。
2)缺点:无法解决循环引用的问题。
3)引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的Stop-The-World的垃圾收集机制。
② 可达性分析算法
1)定义:通过GC ROOT的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收(可作为GC ROOT的对象:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)
在这里插入图片描述
2)Java内存区域中可以作为GC ROOT的对象:
a.虚拟机栈中引用的对象
b.方法区中类静态属性引用的对象
c.方法区中常量引用的对象
本地方法栈中引用的对象
(3)垃圾回收算法
① 标记-清除算法
在这里插入图片描述
1)标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。但它存在一个很大的问题,那就是内存碎片。
2)缺点:
a.效率问题
b.内存小块过多
② 复制算法
1)复制算法(Copying)是在标记清除算法基础上演化而来,解决标记清除算法的内存碎片问题。
2)过程:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况。
3)缺点:代价太大,将内存缩小了一半。效率随对象的存活率升高而降低。
③ 标记-整理算法
在这里插入图片描述
1)标记-整理算法标记过程仍然与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
2)优点:标记整理算法解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。
3)缺点:标记整理算法对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。
4)一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
④ 分代收集算法
1)分代收集算法分代收集算法严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳,根据对象存活周期的不同将内存划分为几块。
2)在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
3)在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理算法或者标记-整理算法来进行回收。
(4)内存区域与回收策略
对象的内存分配,往大方向讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中(大对象直接分到老年代),分配的规则并不是百分百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。
① 对象优先在Eden分配
大多数情况下,对象会在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机会发起一次 Minor GC。Minor GC相比Major GC更频繁,回收速度也更快。通过Minor GC之后,Eden区中绝大部分对象会被回收,而那些存活对象,将会送到Survivor的From区(若From区空间不够,则直接进入Old区) 。
② Survivor区
Survivor区相当于是Eden区和Old区的一个缓冲,类似于我们交通灯中的黄灯。Survivor又分为2个区,一个是From区,一个是To区。每次执行Minor GC,会将Eden区中存活的对象放到Survivor的From区,而在From区中,仍存活的对象会根据他们的年龄值来决定去向。(From Survivor和To Survivor的逻辑关系会发生颠倒: From变To , To变From,目的是保证有连续的空间存放对方,避免碎片化的发生)
③ Survivor区存在的意义
如果没有Survivor区,Eden区每进行一次Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次Minor GC没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。所以,Survivor的存在意义就是减少被送到老年代的对象,进而减少Major GC的发生。Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
④ 大对象直接进入老年代
所谓大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来 “安置” 它们。
虚拟机提供了一个XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用的是复制算法)。
⑤ 长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中(正常情况下对象会不断的在Survivor的From与To区之间移动),并且对象年龄设为1。对象在Survivor区中每经历一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁),就将会晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数 XX:MaxPretenuringThreshold 设置。
⑥ 动态对象年龄判定
为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到 MaxPretenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于改年龄的对象就可以直接进入老年代,无需等到MaxPretenuringThreshold中要求的年龄。
这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。
(5)垃圾回收器
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
① 相关概念:
1)并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
2)并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。
3)吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
② Serial收集器
1)Serial收集器是最基本的、发展历史最悠久的收集器。
2)特点:单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
3)应用场景:适用于Client模式下的虚拟机。
4)Serial/Serial Old收集器运行示意图
在这里插入图片描述
③ ParNew收集器
1)ParNew收集器其实就是Serial收集器的多线程版本。除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。
2)特点:
a.多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
b.和Serial收集器一样存在Stop The World问题
3)应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。
4)ParNew/Serial Old组合收集器运行示意图如下:
在这里插入图片描述
④ Parallel Scavenge收集器
1)与吞吐量关系密切,故也称为吞吐量优先收集器。
2)特点:
a.属于新生代收集器也是采用复制算法的收集器,又是并行的多线程收集器(与ParNew收集器类似)。
b.该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)
3)GC自适应调节策略:
a.Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
b.Parallel Scavenge收集器使用两个参数控制吞吐量:
XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间
XX:GCRatio直接设置吞吐量的大小。
⑤ Serial Old收集器
1)Serial Old是Serial收集器的老年代版本。
2)特点:同样是单线程收集器,采用标记-整理算法。
3)应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。Server模式下主要的两大用途:
a.在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
b.作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。
4)Serial/Serial Old收集器工作过程图(Serial收集器图示相同):
在这里插入图片描述
⑥ Parallel Old收集器
1)是Parallel Scavenge收集器的老年代版本。
2)特点:多线程,采用标记-整理算法。
3)应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old收集器。
4)Parallel Scavenge/Parallel Old收集器工作过程图:
在这里插入图片描述
⑦ CMS收集器
1)一种以获取最短回收停顿时间为目标的收集器。
2)特点:基于标记-清除算法实现。并发收集、低停顿。
3)应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
4)CMS收集器的运行过程分为下列4步:
a.初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
b.并发标记:进行GC Roots Tracing的过程,找出存活对象且用户线程可并发执行。
c.重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
d.并发清除:对标记的对象进行清除回收。
5)CMS收集器的内存回收过程是与用户线程一起并发执行的。
6)CMS收集器的工作过程图:
在这里插入图片描述
7)CMS收集器的缺点:
a.对CPU资源非常敏感。
b.无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
c.因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。
⑧ G1收集器
1)一款面向服务端应用的垃圾收集器。
2)特点如下:
a.并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
b.分代收集:G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
c.空间整合:G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。
d.可预测的停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
3)G1为什么能建立可预测的停顿时间模型?
因为它有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这样就保证了在有限的时间内可以获取尽可能高的收集效率。
4)G1与其他收集器的区别:
其他收集器的工作范围是整个新生代或者老年代、G1收集器的工作范围是整个Java堆。在使用G1收集器时,它将整个Java堆划分为多个大小相等的独立区域(Region)。虽然也保留了新生代、老年代的概念,但新生代和老年代不再是相互隔离的,他们都是一部分Region(不需要连续)的集合。
5)G1收集器存在的问题:
Region不可能是孤立的,分配在Region中的对象可以与Java堆中的任意对象发生引用关系。在采用可达性分析算法来判断对象是否存活时,得扫描整个Java堆才能保证准确性。其他收集器也存在这种问题(G1更加突出而已)。会导致Minor GC效率下降。
6)G1收集器是如何解决上述问题的?
采用Remembered Set来避免整堆扫描。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用对象是否处于多个Region中(即检查老年代中是否引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆进行扫描也不会有遗漏。
7)如果不计算维护Remembered Set的操作,G1收集器大致可分为如下步骤:
a.初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
b.并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
c.最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
d.筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)
8)G1收集器运行示意图:
在这里插入图片描述
1.3.执行过程
程序在执行之前先要把java代码转换成字节码(class文件),jvm首先需要把字节码通过一定的方式(类加载器(ClassLoader))把文件加载到内存中(运行时数据区(Runtime Data Area)),而字节码文件是jvm的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器(执行引擎(Execution Engine))将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口(本地库接口(Native Interface))来实现整个程序的功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值