java平台的理解


谈谈你对Java平台的理解?“Java是解释执行”,这句话正确吗?

★考点

题目是开放性的,往往考察的是多个方面,比如:

  1. 基础知识理解是否很清晰
  2. 是否掌握Java平台的主要模块和运行原理等

你需要尽量表现出自己的思维升入并系统化,java知识理解的也比较拳迷看,一定要避免让面试官觉得你是一个“知其然不知其所以然”的人.

对于java平台的理解,可以从很多方面简明扼要的谈一下,例如:

  1. java的特性,包括泛型 、Lambda等语言特性
  2. 基础类库,包括集合、IO/NIO、网络、并发、安全等基础类库。
  3. 或者谈谈JVM的一些基础概念和机制,比如JAVA的类加载机制,常用版本内嵌的Class-Loader,例如Bootstrap、Application和Exception Class-loader;类加载的大只过程:加载、验证、连接、初始化;自定义Class-Loader等。还有垃圾收集的基本原理,最常见的垃圾收集机制,如SerialGC、ParallelGC,CMS、G1等

★学习足迹

JAVA代码的运行流程

我们编写的代码都是在java文件中编写的,然后会编译成class字节码文件。 当我们使用到哪个类的时候就会通过类加载器把class字节码文件中的类加载到jvm内存中,然后就是在jvm内存中运行我们的代码了。类加载器是如何把类加载到jvm内存中的,小伙伴们有考虑过吗?

JVM什么时候加载类

一个类的加载过程会经历如下的几个过程:

加载、验证、准备、解析、初始化、使用、卸载

加载

”加载“是”类加机制”的第一个过程,在加载阶段,虚拟机主要完成三件事:

(1)通过一个类的全限定名来获取其定义的二进制字节流

(2)将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构

(3)在堆中生成一个代表这个类的Class对象,作为方法区中这些数据的访问入口。

jvm是什么时候去加载类的呢?
程序的入口,具有main方法的类,肯定是最开始的时候就加载到jvm中了。
jvm是通过什么方式对类进行加载的呢?
就是类加载器。

jvm的类加载器总体上可以分成4层:

  1. 动类加载器
    首先就是jvm启动的第一道关口,启动类加载器Bootstrap ClassLoader,它主要是加载java的核心类。
    相信大家都知道,无论是什么环节下运行java程序,都是要安装jvm虚拟机环境的,而在这个环境的目录中是有一个lib文件夹的,这个文件下就是java最核心的类库,支撑着java系统的运行.
    所以一旦jvm启动,那么首先就会通过启动类加载器去加载lib文件夹下的核心类库。
  2. 扩展类加载器
    然后我们就到了第二层,扩展类加载器Extension ClassLoader,这个类加载器其实与启动类加载器是类似的。
    在我们的jvm虚拟机环境目录下,是有一个lib/ext的文件夹的,这里面的类就是java运行环境的一些扩展类,这些扩展类就是在jvm启动后,通过扩展类加载器进行加载的。
  3. 应用程序类加载器
    加载完核心类库和扩展类,这时候就到了第三层,应用程序类加载器Application ClassLoader,这个类加载器你就可以理解成是加载我们写好的java代码的就可以了。
  4. 自定义类加载器
    前面的三层就是基本的类加载器了,然后第四层是自定义类加载器,根据一些特殊的需求来自己定义类加载器加载我们的类。

整体上类加载器就是这么的4层结构。很多小伙伴可能都听说过双亲委派机制,那么什么是双亲委派机制呢?

其实很好理解,就是当我们的类加载器要加载一个类的时候,它首先会委派给它的父亲去加载,但是如果它的父亲没找到就会把这个事交给他的孩子自己去完成了。

举个例子,假如我们的应用程序类加载器要加载一个类A,那么首先它会先回家找它老爸扩展类加载器,问问“老爸,你那有这个类A吗?”
然后扩展类加载器接到这个请求之后,同样也懒得处理,再去找它爷爷启动类加载器。
它爷爷找了一圈没找到类A,很生气,就对扩展类加载器说,“我这没有,你自己找去!”
然后扩展类加载器就灰溜溜的自己找了一圈,同样也没找到,这时候就找到应用类加载器了,说:“我这哪有你这个类A,这明明是你自己应该干的活,爱上哪找上哪找去,我不管了”。
这时候应用类加载器就只能自己去处理了,找了一圈发现找到了类A,就把它加载到jvm内存中了。

验证
jvm根据java规范,来校验你加载进来的class文件中的内容是否符合规范,如果不符合规范jvm是无法正常运行的。

  1. 文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解)。
  2. 元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
  3. 字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事。
  4. 符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。

准备
准备阶段主要为类变量分配内存并设置初始值
假设我们有一个类A,刚刚加载并通过了验证,那么就会进行准备工作。
这个准备工作其实就是给类A分配一定的内存空间,然后给里面的静态变量(static修饰的变量)也分配内存空间,并赋初始值。

解析

这个阶段干的事实际上是把符号引用替换为直接引用,这一过程网上有很多资料,还是比较复杂的,如果感兴趣小伙伴们可以自己查阅一下资料。

  • 符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)
  • 直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。

初始化
在准备阶段,我们把类A的内存已经分配完了,那么初始化阶段要做些什么事呢?我们先看一下类A的代码

1
2
3
public class A {
private static String i=System.getProperty("i");
}

准备阶段我们只是给变量i分配了内存空间,并赋值了初始值,但是后边的System.getProperty(“i”)是不会执行的。
没错,这部分代码就是在初始化阶段执行的,另外静态代码块也会在这一阶段执行。
举个例子,比如我们新建一个对象new A(),此时就会触发从加载到初始化的全过程,把这个类准备好并创建一个实例对象。
此外这里有一个规则,如果类A继承了类B,那么在初始化类A的时候,如果发现类B还没有初始化,会先初始化类B。

扩展
小伙伴们想过没有,Tomcat也是用java开发的,那么它的类加载机制是什么样的呢,为什么就能支持jsp呢?
其实它就是利用了自定义类加载器这一机制,自己自定义了很多类加载器
Tomcat自定义了这么多的类加载器,用来加载它自己的核心类库,并且Tomcat是打破了双亲委派机制的,感兴趣的小伙伴可以自己去查资料了解一下,本篇文章长篇大论来聊Tomcat了。


java是解释运行的吗?
不正确!

  1. Java源代码经过Javac编译成.class文件
  2. class文件经JVM解析或编译运行。
    • (1)解析:.class文件经过JVM内嵌的解析器解析执行。
    • (2)编译:存在JIT编译器(Just In Time Compile
      即时编译器)把经常运行的代码作为”热点代码”编译与本地平台相关的机器码,并进行各种层次的优化。
    • (3)AOT编译器: Java 9提供的直接将所有代码编译成机器码执行。

  以 Oracle JDK提供的HotSpot虚拟机为例,在HotSpot虚拟机中,提供了两种编译模式:解释执行即时编译(JIT,Just-In-Time)。解释执行即逐条翻译字节码为可运行的机器码,而即时编译则以方法为单位将字节码翻译成机器码(上述提到的“编译执行”)。前者的优势在于不用等待,后者则在实际运行当中效率更高。

 即时编译存在的意义在于它是提高程序性能的重要手段之一。根据“二八定律”(即:百分之二十的代码占据百分之八十的系统资源),对于大部分不常用的代码,我们无需耗时间将之编译为机器码,而是采用解释执行的方式,用到就去逐条解释运行;对于一些仅占据小部分的热点代码(可认为是反复执行的重要代码),则可将之翻译为符合机器的机器码高效执行,提高程序的效率,此为运行时的即时编译。


文章作者: TheMoonLight
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 TheMoonLight !
评论
  目录