Q:谈谈你对JAVA的理解
- 平台无关性: 一次编译,到处运行。
- 面向对象: 继承、封装、多态。
- 语言特性: 泛型、反射、lambda表达式等。
- GC: 自动垃圾回收机制。
- 类库: java.util、java.io、java.sql等
- 异常处理:
Q:平台无关性如何实现
.java源码首先经由javap编译成 .class字节码文件,再通过不同平台的JVM进行解析,java文件在不同平台运行不需要重新编译。jvm将.class文件载入内存后转换成可在具体平台上执行的机器指令运行。
Q:JVM如何加载class文件
JVM架构
- ClassLoader: 加载符合规定格式的 .class 字节码文件
- Runtime Data Area: jvm内存空间模型
- Execution Engine: 解析字节码
- **Native Interface:**调用其他语言代码
Q:什么是反射
java的反射机制是,在运行过程中,对于任意一个类,都可以动态的获取其任意的属性和方法。对于任意一个对象,都可以动态的调用其任意方法修改任意属性。这种在运行期间动态获取类的信息和动态调用对象方法属性的能力称之为反射。
反射中涉及到的类
- Class
- Constructor
- Field
- Method
反射中涉及到的方法
- Class.forName()
- c.newInstance()
- c.getMethod(): 获取public和继承的方法
- c.getDeclaredMethod(): 获取类中所有声明的方法 但不包括继承方法
Q:谈谈ClassLoader
类从编译到执行的过程
- 编译器将.java文件编译成.class文件
- ClassLoader将字节码文件载入内存并生成对应Class对象
- jvm通过Class对象实例化对象
ClassLoader定义
ClassLoader工作在类装载的加载阶段,主要负责根据类的全限定名获取二进制流。它是java的核心组件之一,所有的class都经由classloader加载进内存后,交由jvm进行连接、初始化等操作。
ClassLoader类型
- BootstrapClassLoader: c++编写,jvm实现的一部分。负责加载<JAVA_HOME>/lib目录下且被jvm识别的类库。
- ExtClassLoader: 负责加载<JAVA_HOME>/lib/ext目录下的类库。
- AppClassLoader: 负责加载classpath指定的类。
- 自定义类加载器: 通过重写父类的findClass方法,实现自定义类的加载方式和途径。
Q:ClassLoader双亲委派
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
- 检查当前类加载器是否已经加载过类
- 没有则交由上层类加载器尝试加载,重复过程1
- 当parent为空时,则通过native接口调用bootstrapClassLoader
- 当上层类加载器未找到类时抛出ClassNotFoundException,并返回null
- 下层类加载器接收到返回结果,为null时调用自己的findClass方法,直至抛出ClassNotFoundException或成功加载并返回。
为什么使用双亲委派机制加载类
- 防止重复加载类(由类加载器和类本身确定其唯一性)
- 防止恶意代码被载入虚拟机
Q:loadClass和forName的区别
类的加载方式
- 显式加载:new
- 隐式加载:loadClass、forName
类的装载过程
- 加载
– 根据类的全限定名找到对应的二进制字节流
– 将这个字节流代表的静态数据结构转化为方法区的运行时数据结构
– 生成代表这个类的java.lang.Class对象,作为运行时获取这个类在方法区一切类信息的入口 - 连接
– 校验: 检查加载class的正确性和安全性
– 准备: 为类变量(static) 分配内存空间并设置初始值(内存位 置零)。类变量随类型信息存放在方法区中。
– 解析: 将常量池内符号引用转换为直接引用。可选步骤。 - **初始化: ** 执行类变量赋值和静态代码块。
区别
- loadClass: ** 方法的resolve**参数指明了是否执行 连接(link) 步骤。默认为false,所以loadClass得到的是尚未经过连接步骤的class对象。spring的ioc容器在启动的时候默认使用loadClass加载,提高启动的速度,而把实际的初始化留到真正使用的时候执行。lazy-load配置项指明了是否使用懒加载的方式启动容器。
- forName: ** 方法的initialize**参数指明了是否执行 初始化(init) 操作。
默认为true,所以forName得到的是经过初始化(执行初始化必须经过连接)的class对象。jdbc中使用forName加载com.mysql.jdbc.Driver是为了触发Driver类的初始化操作,执行其定义的静态代码块。
Java内存模型
JMM之线程独占
程序计数器(Program Counter Register)
- 当前线程正在执行的字节码的行号指示器
- 通过改变程序计数器的值来选取下一条要执行的指令
- 每条线程拥有独立的程序计数器,互不干扰(线程独占)。
- 执行java方法时记录字节码指令地址,执行native方法时值为undefined
- 虚拟机规范里唯一不会发生OOM的区域
java虚拟机栈
- 描述的是java方法执行的内存模型
- 线程私有,且声明周期与线程相同,与线程是一对一的。
- 每个方法调用会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、返回出口等信息。每个方法调用和栈帧是一对一的。
- 每个方法从调用到执行完成的过程,就对应一个栈帧从入栈到出栈的过程。
局部变量表
局部变量表是一组值存储空间,用于存放方法参数和方法内定义的局部变量。
在编译为class文件时,就确定了该方法所需分配的局部变量表的最大容量。
局部变量表的容量以变量槽(slot)为最小单位。
虚拟机通过索引定位的方式使用局部变量表,索引范围从0到最大slot数量。
如果执行的是实例方法(非static),那局部变量表的第0个索引位置存放的是所属对象实例的引用(即this)。
操作数栈
一个FILO的栈。
和局部变量表一样,也是编译时已知最大栈深度。
方法开始执行时,操作数栈为空。在方法执行过程中,伴随各种字节码操作指令往栈中写入读出,也就是出栈入栈操作。
StackOverFlow 深度递归
OutOfMemory 创建大量线程
本地方法栈
类似虚拟机栈,是执行本地方法时创建的。
JMM之线程共享
MetaSpace
线程共享的内存区域,用于存储已被虚拟机加载的类信息、静态变量等信息。字符串常量池在JDK1.7后被移入堆中。
JDK1.8使用元空间替代了永久代,元空间使用本地内存,永久代使用JVM内存。解决了此区域的OutOfMemory异常。
堆
对象实例分配的主要空间
垃圾回收管理的主要区域。
JMM常考面试题
Q:JVM三大性能调优参数-Xms、-Xmx、-Xss的含义
- -Xss: 指定每个线程虚拟机栈的大小 默认256kb
- -Xms: 指定jvm启动时堆得初始内存大小
- -Xmx: 指定jvm堆得最大内存大小
通常将-Xms和-Xmx参数大小指定为一致,避免堆内存不够用而发生扩容时导致的内存抖动,影响程序执行的稳定性。
Q:jvm内存模型中堆和栈的区别
联系: 引用对象、数组时,栈中定义的变量保存了堆中目标的首地址。
- 管理方式: 栈空间自动释放,堆空间需要垃圾回收
- 空间大小: 栈小,堆大
- 空间碎片: 堆得空间碎片高于栈
- 分配方式: 栈使用静态分配和动态分配,堆使用动态分配
- 效率: 栈的效率高
Q:JDK6和JDK6+中intern方法的区别
// JDK 1.6
String s1 = new String("a");
s1.intern();
String s2 = "a";
System.out.println(s1 == s2); // false
String s3 = new String("a") + new String("a");
s3.intern();
String s4 = "aa";
System.out.println(s3 == s4); // false
// JDK 1.6+
String s1 = new String("a");
s1.intern();
String s2 = "a";
System.out.println(s1 == s2); // false
String s3 = new String("a") + new String("a");
s3.intern(); // 发现字符串常量池中没有"aa", 将堆中String对象"aa"的引用放入常量池
String s4 = "aa"; // 此处s4指向的是堆中String对象"aa",所以s3 == s4为true
System.out.println(s3 == s4); // true