面试官:谈谈你对 Java 平台的理解
本文约3100字,完整阅读大概会花费你「6分钟」左右的时间
文章导读
前言
面试的时候,经常会有面试官问:请你谈谈对 Java 平台的理解,「Java 是解释执行」,这句话正确吗?
其实这个问题,问得有点笼统。题目本身是非常开放的,往往考察的是多个方面,比如,基础知识理解是否很清楚;是否掌握 Java 平台主要模块和运行原理等。
个人认为,回答这类开放性问题的思路,可以从宏观的角度出发,从浅入深,由点到面。
总的来说可以从如下几个方面来回答:
面向对象:封装、继承、多态;
平台无关性:这个涉及到字节码,Java虚拟机等;
JVM:JVM 的一些基础概念、类加载机制、内存布局等;
垃圾回收:垃圾回收的基本原理,常见的垃圾收集器以及适用的工作负载;
语言特性:反射,泛型,Lambda 等;
类库:JDK 提供的各种类库,重点包括集合、并发、IO/NIO、网络等;
异常处理:Exception 和 Error;
生态:Spring、SpringBoot等。
参考回答
Java语言是一种面向对象的高级语言,它最显著的有两个特性:
一是通过平台中立的 class 文件格式和屏蔽底层硬件差异的 JVM 实现「一次编写,到处运行」;
二是Java 通过垃圾收集器(Garbage Collector)回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。
Java 是一种简单、严谨并且适合编写的语言,它不像 C/C++ 那样有很多晦涩难懂的内容,如头文件、指针、结构等等。
我们日常会接触到 JRE(Java Runtime Environment)或者 JDK(Java Development Kit)。
JRE,也就是 Java 运行环境,包含了 JVM 和 Java 类库,以及一些模块等,比如:集合,泛型,反射,并发,网络,IO/NIO等。
而 JDK 可以看作是 JRE 的一个超集,提供了更多工具,比如编译器、各种诊断工具等让java 语言更加安全、健壮。
Java 到底是解释执行还是编译执行?
这个问题并没有统一的答案,JVM 规范并没有强制要求 JVM 实现应该使用哪种方式来执行程序,只能说不同的JVM实现的方式不一样。有纯解释执行的、纯编译执行的(JRockit)、还有解释 + 编译两者混用的(HotSpot)。
知识点
面向对象
面向对象 vs 面向过程
当前主流的编程语言有 50 多种,主要分成两大阵营:面向对象编程和面向过程编程。
面向过程强调的是过程化的叙事思维,也就是让计算机有步骤地顺序地做一件事。
优点是流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,便于节点分析。
缺点是在大型项目开发过程中,代码重用性低,扩展能力差,后期维护难度比较大。
面向对象强调高内聚、低耦合,先抽象模型,定义共性行为,在解决实际问题。
优点:
结构清晰,程序是模块化和结构化,更加符合人类的思维方式;
易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;
易维护,系统低耦合的特点有利于减少程序的后期维护工作量。
缺点:
开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。
性能低,由于面向更高的逻辑抽象层,使得面向对象在实现的时候,不得不做出性能上面的牺牲,计算时间和空间存储大小都开销很大。
下面我们简单介绍一下面向对象。
首先什么是对象?
这里的「对象」与我们中文概念上的「对象」是有差异的,我们中文普遍意义上的对象是指「标的物」,翻译成英文是
。Target
但 Java 语言里的对象不是这个意思,而是指「任何物体」的一种统称,更接近于中文「东西」这个词,所以在英文里它被翻译成
。Object
Java中的对象是指任何物体的抽象,面向对象的设计过程即是事件的抽象过程。
面向对象的三大核心特性简介
1、封装
封装是指属性值的访问与修改需要使用相应的
方法,而不是直接对 public 的属性进行读取和修改。getter/setter
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
2、继承
继承是面向对象编程的基石,通过创建具有逻辑等级的类体系,形成继承树,实现基础模块的复用。
继承是
关系,通过继承,使代码更有层次感,更有扩展性,并为多态打下语法基础。is-a
3、多态
多态以前两个特性为基础,根据运行时的实际对象类型,同一个方法产生不同的运行结果,使同一个行为具有不同的表现形式。
我们先明确两个非常容易混淆的概念:
和 override
:overload
override
:覆写,指子类实现接口或者继承父类时,保持方法签名完全相同,实现的方法体不同,是垂直方向上行为的不同实现;overload
:重载,是指在同一个类中,方法名相同,参数类型或者参数个数不同的,是水平方向上行为的不同实现。
多态在编译层面无法判断最终调用的方法体,是在运行时由 JVM 进行动态绑定,调用合适的覆写方法体来执行。
重载是编译器确定的方法调用,属于静态绑定,所以笔者认为多态专指覆写。
平台无关性
Java 通过字节码和 Java 虚拟机(JVM)这种跨平台的抽象,屏蔽了操作系统和硬件的细节,这也是实现「一次编写,到处运行」的基础。
在运行时,JVM 会通过类加载器(Class-Loader)加载字节码,解释或者编译执行。就像前面提到的,主流 Java 版本中,如 JDK 8 实际是解释和编译混合的一种模式,即所谓的混合模式(-Xmixed)。
我们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode), 然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM, 都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。
JVM
类加载机制
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这就是虚拟机的类加载机制。
在 Java 语言里面,类的加载、连接和初始化过程都是在程序运行期间完成的。
Java 类的整个生命周期如下图:
3 个重要的类加载器:
:启动类加载器,由原生代码(如C语言)编写,不继承自Bootstrap ClassLoader
,java 程序无法直接操作这个类。它用来加载 Java 核心类库。java.lang.ClassLoader
:扩展类加载器,父类加载器为启动类加载器。负责加载相对次要、但又通用的类,比如存放在 JRE 的 lib/ext 目录下 jar 包中的类(以及由系统变量 java.ext.dirs 指定的类)。Extension
ClassLoader
:应用程序类加载器,父类加载器为启动类加载器。它负责加载环境变量Application
ClassLoaderclasspath
或者系统属性java.class.path
指定路径下的类库。它是程序中默认的类加载器,一般情况下,我们 Java 程序中的类,都是由它加载完成的。
垃圾回收
我们知道,程序在运行的时候,为了提高性能,大部分数据都是会加载到内存中进行运算的,有些数据是需要常驻内存中的,但是有些数据,用过之后便不会再需要了,我们称这部分数据为垃圾数据。
为了防止内存被使用完,我们需要将这些垃圾数据进行回收,即需要将这部分内存空间进行释放。
不同于 C++ 需要自行释放内存的机制,Java 虚拟机(JVM)提供了一种自动回收内存的机制,也就是垃圾回收(Garbage Collection,GC)。
垃圾判断算法:
引用计数法
可达性分析法
垃圾回收算法:
标记-清除算法(Tracing Collector)
标记-整理算法(Compacting Collector)
复制算法(Copying Collector)
适应性算法(Adaptive Collector)
分代收集算法(Generational Collector)
垃圾回收器:
Serial 回收器
CMS 回收器
G1
异常处理
Exception 和 Error 都是继承了 Throwable 类,Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类。
Error 是一种非常特殊的异常类型,它的出现标识着系统出现了不可控的错误,例如
、StackOverflowError
。针对此类错误,程序无法处理,只能人工介入。OutOfMemoryError
Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
Exception 又分为可检查(checked)异常和不检查(unchecked)异常:
可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。比如:
、IOException
。ClassNotFoundException
不检查异常就是所谓的运行时异常,类似
、NullPointerExceptio
nArrayIndexOutOfBoundsException
。通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。RuntimeException