剑指Java面试-Offer直通车
@xiangzepro
1. 剑指Java面试-导学
// 现状:
缩减招聘人数(bat一线互联网大厂缩减招聘人数)
毕业生增长
企业内部 (末位淘汰、优胜劣汰 的用人机制)
就业门槛更高 企业对人才要求也更高
// 规划
小白/初级工程师 => 独挡一面甚至多面的高工
专辑1.项目驱动学习,积累项目经验,独立完成前后端全栈的项目开发
专辑2.分析归纳面试热点,功克Java面试,完善备战面试体系
未来专辑 未来专辑1:归纳项目面试的微课,解决项目面试无从会的痛点
未来专辑 未来专辑2:深入分析主流框架Spring的源码,并着手编写框架
课程导学
由五个部分组成
- 内容概述
- 学习门槛和适用人群
- 如何最大化利用好本门课程
- JDK版本的选择
- 战前动员
1.1 课程概述
- 凝结了一线资深开发工程师的心血。
- 包含JVM底层知识、Java垃圾回收、多线程、常用类库、常考算法和数据结构、框架、缓存、网络、Linux操作系统、调优等高频考点,以及一些面试技巧和软技能点拨。
Java基础考点:
如何应对如此多的考点:
- 面试官知识储备有限
- 考点频率不同、可以因地制宜地缩小备战范围
- 剥离出不变的通用知识点,以不变应万变
- 平时注意多积累
1.2 学习门槛和适用人群
门槛较低,适用范围较广
- 具备Java语言编程基础,最好了解些数据库、网络知识
- 适合有工作经验或者项目实战经验的想北站备战面试的同学
- 也适合对自己有高标准、高要求的同学
1.3 如何最大化利用好本门课程
- 合理利用检索引擎(Google、Baidu),培养主动解决问题的能力,常识加深和扩展自己的知识面,锻炼独立填坑的能力。
- 尽量问与本门课程内容相关的问题,并提供足够的信息方便定位。
1.4 JDK版本的选择
查看每个版本的维护时间
JDK8 和 JDK11 是长期支持的版本,分别支持到2025、2026年。(JDK9、JDK10只是过渡版本,只提供半年技术支持)
1.5 战前
- 面试并非只争朝夕,需要注重积累。
6. 剑指Java面试-Java底层知识:JVM
JavaEE基础
打包 WAR\EAR
基础 MVC Servlets JSPs
APIs JPA JAX-WS
内部/内嵌类
访问权限修饰词
APIs
OOPs
线程
集合
JVM
异常
6.1 谈谈你对Java的理解
Java的语言特点
- 平台无关性 跨平台,一次编译,到处运行
- GC机制 垃圾回收机制:不用手动释放内存
- 语言特性 泛型、反射、lamda表达式
- 面向对象 封装、继承、多态
- 类库 集合、并发库、网络库、IO、NIO等
- 异常处理
6.2 平台无关性的实现
Compile Once, Run Anywhere如何实现
答:图片中
1. 编译运行
javac指令,用来编译java的源码,即将源码编译生成字节码,存入到对应的.class文件当中,其会对程序的语法、句法以及一些语义进行检查。.class文件保存的就是java文件翻译成的二进制字节码,即java类文件中的属性、方法以及类中的常量信息都会被分别存储在class文件中。还会添加一个公有的静态常量属性.class,这个属性记录了类的相关信息及类型信息,是class的一个实例。
2. javap指令
是java自带的反汇编器,可以查看java编译器生成的字节码。通过比较字 节码和源代码,我们可以发现很多问题。“javap -c”对代码进行反汇编。
javac com/wt/javabasic/bytecode/ByteCodeSample.java
javac -encoding utf-8 com/wt/javabasic/bytecode/ByteCodeSample.java
java com.wt.javabasic.bytecode.ByteCodeSample
javap -help
javap -c com.wt.javabasic.bytecode.ByteCodeSample
// 参考:通过javap命令分析java汇编指令:https://www.jianshu.com/p/6a8997560b05
Compiled from "ByteCodeSample.java"
public class com.wt.javabasic.bytecode.ByteCodeSample {
// java默认的无参构造函数 => 当我们不指定类的构造函数 编译器会为我们创建一个不带参数的构造函数
public com.wt.javabasic.bytecode.ByteCodeSample();
Code:
0: aload_0 //对diss?进行操作//从本地变量表中加载索引为0的变量的值,也即this的引用,压入栈
1: invokespecial #1 // Method java/lang/Object."<init>":()V //出栈,调用java/lang/Object."<init>":()V 初始化对象,就是this指定的对象的init()方法完成初始化
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 // 对栈操作 把常亮1放入栈顶
1: istore_1 // 将栈顶的值放入局部变量1中(就是第一个变量i中)
2: iconst_5 // 把常亮5放入栈顶
3: istore_2 // 将栈顶的值5放入局部变量2中(也就是变量j中)
4: iinc 1, 1 (将变量1(i)+1)
7: iinc 2, 1 (将变量2(j)+1)
10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; //获取PrintStream的静态域 需要打印所以需要获取
13: iload_1 // 将PrintStream对象 将其压入栈顶,将本地变量i的值推送到栈顶
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V // 调用PrintStream的println方法,去打印第一个变量的值(i)
17: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_2
21: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
24: return
}
// 在linux中操作
// 上传
scp ByteCodeSample.class root@192.168.xxx.xxx:~
// 创建文件夹 复制 运行
mkdir -p com/wt/javabasic/bytecode/
cp ByteCodeSample.class com/wt/javabasic/bytecode/
ls com/wt/javabasic/bytecode/
java com.wt.javabasic.bytecode.ByteCodeSample
ByteCodeSample.java
package com.wt.javabasic.bytecode;
public class ByteCodeSample {
public static void main(String[] args) {
int i = 1,j=5;
i++;
j++;
System.out.println(i);
System.out.println(j);
}
}
回答:
java文件首先经过javac编译生成字节码。将字节码保存在.class文件中。.class文件是跨平台的基础。
由不同平台的JVM进行解析,java语言在不同的平台上运行时不需要进行重新编译,java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
3. 为什么JVM不直接将源码解析成机器码去执行
准备工作:每次执行都要进行语法,句法的检查。
使用javac编译成字节码之后,多次执行也2、不需要再校验和补全了。
兼容性:也可以将别的语言解析成字节码。(可以由别的语言(例如ruby、scala)生成字节码。同样可以被jvm调用执行,也可以增加平台的兼容扩展能力,符合软件设计的中庸之道)
6.3 JVM如何加载.class文件
1. Java虚拟机
- 是抽象化的计算机,通过在实际的计算机上个仿真模拟计算机功能来实现的,JVM有自己完善的硬件架构:处理器,堆栈,寄存器等,还具有相应的指令系统,JVM 屏蔽了与具体操作系统平台相关的信息,使得java程序只需要生成在Java虚拟机上运行的字节码,就可以在不同平台上不加修改的运行。(一般我们不需要知道虚拟机的运行原理,只要专注于写java代码就可以了,这也正是虚拟机之所以存在的原因,就是屏蔽底层操作系统平台的不同,并减少基于原生语言开发的复杂性,只需要虚拟机厂商在特定操作系统上定义如何将字节码解析成本操作系统可执行的二进制码,java这项语言就能实现跨越各种平台) 对于jvm最值得我们学习两点:JVM内存结构模型,GC。(既是面试的重点,更是程序调优的关键)
- JVM是内存中的虚拟机,JVM的存储就是内存,所有写的 类,常量,变量,方法都在内存中这决定着程序的健壮和高效。
- JVM由Class Loader、Runtime Data Area、Execution Engine、Native Interface组成。
简要回答:
JVM主要由Class Loader、Runtime Data Area、Execution Engine、Native Interface这四个部分组成,它主要通过Class Loader将符合特定格式要求的class文件加载到内存里,并通过Execution Engine去解析class文件中的字节码,并提交给操作系统去执行。
Native Interface:(既本地接口) 融合不同开发语言的原生库为Java所用。(不管我们如何吹嘘java,它得执行性能,在绝大多数情况下并没有c或者c++高,主流的jvm也是基于c++去实现的,因此在涉及到一些需要较高执行性能的运算操作的时候,是需要java直接调用它们的。此外在本着不重复造轮子的原则,在实际生产中,某个库如果已经用到别的语言进行开发了,我们就不需要再开发一套,而是希望java能够对这些库进行调用。为了满足上述需求,jvm在内存中专门开辟了一块区域处理标记为native的代码,他的具体做法是native methed中登记native方法,在Execution执行时,加载native Library)
Class.forName("com.wt.javabasic.bytecode.ByteCodeSample");
// =>
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
// =>
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader, Class<?> caller)
throws ClassNotFoundException;
Runtime Data Area: JVM内存空间结构模型。(我们缩写的程序 都会被加载到这里,之后再开始运行,该结构模型的设计,堪称神做)
6.4 什么是反射
1. 问:有没有了解过java的反射
回答:java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它得任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
2. 写一个反射的例子
Robot
package com.wt.javabasic.reflect;
public class Robot {
private String name;
public void sayHi(String helloSentence){
System.out.println(helloSentence + " " + name);
}
public String throwHello(String tag){
return "Hello " + tag;
}
}
ReflectSample
package com.wt.javabasic.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectSample {
public static void main(String[] args) {
try {
Class rc = Class.forName("com.wt.javabasic.reflect.Robot");
Robot r = (Robot) rc.newInstance();
System.out.println("Class name is " + rc.getName());
// r.sayHi("zhangsan");
// getDeclaredMethod可以获取 public protected private或者包类型的方法和属性,不能获取继承的方法(和实现接口的方法)
Method getHello = rc.getDeclaredMethod("throwHello", String.class);
getHello.setAccessible(true);
Object str = getHello.invoke(r, "zhangsan");
System.out.println("getHello result is " + str);
// getMethod可以获取public方法 包括继承的方法和实现接口的方法
Method sayHi = rc.getMethod("sayHi", String.class);
sayHi.invoke(r, "Welcome");
Field nameField = rc.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(r, "Alice");
sayHi.invoke(r, "world");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
- getDeclaredMethod 可以获取该类自有的所有的方法,但不能获取继承自父类的或者实现自接口的方法。
- getMethod 只能获取该类的public方法,但是它能获取继承自父类的或者实现自接口的方法。
- getHello 方法是私有的,所以在调用前要将其Accessible设置为true。
6.5 ClassLoader
1. 类从编译到执行的过程
2. 谈谈ClassLoader
3. 种类
- BootStrapClassLoader: C++编写,加载核心库 java.* (通常核心类class被签名,不能被替换掉,他是有jvm内核实现的,在主流java虚拟机是由c++实现的)
- ExtClassLoader: Java编写,加载扩展库 javax.* (用户可以自己定义类在这个目录下,通过这个ExtClassLoader去加载)
- AppClassLoader: Java编写,加载程序所在目录 (加载classpath下的内容)
- 自定义ClassLoader: Java编写,定制化加载
4. 自定义ClassLoader的实现
6.6 ClassLoader的双亲委派机制
1. 查看openJDK源码
为什么查看openjdk,因为JDK部分代码闭源,对外公布的代码要看openJDK的代码。Linux上经常用到openJDK,
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/
=> browse => src => share(不依赖平台的代码) => /native/java/lang/ClassLoader.c
/*
* Returns NULL if class not found.
*/
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findBootstrapClass(JNIEnv *env, jobject loader,
jstring classname)
...
cls = JVM_FindClassFromBootLoader(env, clname); // 通过jvm调用BootLoader
done:
if (clname != buf) {
free(clname);
}
return cls;
}
2. 为什么要使用双亲委派机制去加载类
- 避免多分同样字节码的加载 (内存宝贵)
6.7 loadClass和forName的区别
1. 类的加载方式:
- 隐式加载:new
- 显示加载:loadClass以及forName等方法
2.类的装载过程:
区别: