JVM相关
一 谈谈对Java的理解
- 平台无关性;
- GC;
- 语言特性;
- 面向对象;
- 类库;
- 异常处理;
二 平台无关性如何实现?
1. 编译时
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令;
1.1 为什么JVM不直接把源码解析成机器码执行?
首先这样做有两点好处:可以节省大量的准备工作,提升效率,其次可以提升兼容性;
2. 运行时
2.1 Java如何加载Class文件?
首先这个问题我们需要弄明白Java虚拟机,虚拟机是跑在内存中的,虚拟机核心的有两点:①内存模型,②GC;先来看一下JVM的架构图Class Loader : 负责加载符合格式的.class文件;
Execution Engine : 对命令进行解析;
Native Interface : 融合不同开发语言的原生库为Java所用;
Runtime Data Area : Java内存空间模型;2.2 什么是反射?
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象的功能称为Java语言的反射机制;
2.3 写一个反射的例子.
public class Robot {
private String name;
public void sayHi(String helloSentence){
System.out.println(helloSentence + " " + name);
}
private String throwHello(String tag){
return "Hello " + tag;
}
}
public class ReflectSample {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
Class rc = Class.forName("com.interview.javabasic.reflect.Robot");
Robot r = (Robot) rc.newInstance();
System.out.println("Class name is " + rc.getName());
Method getHello = rc.getDeclaredMethod("throwHello", String.class);
getHello.setAccessible(true);
Object str = getHello.invoke(r, "Bob");
System.out.println("getHello result is " + str);
Method sayHi = rc.getMethod("sayHi", String.class);
sayHi.invoke(r, "Welcome");
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r, "Alice");
sayHi.invoke(r, "Welcome");
}
}
2.3 类从编译到被执行的过程
- 编译器将源文件编译成Class文件;
- ClassLoader将字节码转换成JVM中的Class<T>对象;
- JVM利用Class<T>对象实例化为T对象;
三 谈谈ClassLoader
ClassLoader在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流.它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接/初始化等操作.
四 ClassLoader的种类
- BootStrapClassLoader : C++编写, 加载核心库Java.*;
- ExtClassLoader : Java编写,加载扩展库javax.*;
- AppClassLoader : Java编写,加载程序所在目录;
- 自定义ClassLoader : Java编写,定制化加载;
五 自定义ClassLoader的实现
- 重写findClass() : 这个函数是用来寻找Class文件的;
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
- 重写defineClass() : 解析字节码返回对象;
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
- 自定义ClassLoader
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}
六 ClassLoader的双亲委派机制
接下来我们看一下源码
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
很明显上边的是个重载,调用了loadClass(name,false)
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;
}
}
到这里就可以很明显的看出这段代码采用的是递归的逻辑进行调用的;启动时先使用等级最低的加载器执行加载,如果有直接返回,如果没有调用父类加载器执行加载然后执行递归执行同样的逻辑;如果都没有就会从上直下依次使用加载器调用findClass方法进行扫描加载;同时因为启动类加载器是C++实现的在java源码中是找不到的,所以感兴趣的话可以通过openJdk查看C++源码实现;
七 为什么使用双亲委派机制去加载类
- 避免重复字节码的加载;
- 提供了JDK核心类加载的沙箱环境;
八 类的加载方式
- 隐式加载 : new;
- 显式加载 : LoadClass ---> forName进行加载;
九 loadClass和forName的区别
同为显式加载这两个方法的相同点是他们都能在运行时知道一个类的所有属性和方法,对于任意一个对象都能调用它的方法和属性;如果要搞明白他们之间的区别则需要先明白一个概念就是类的装载过程;
1. 类的装载过程 接下来看一下源码
// loadClass
// 第一步
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
-------------------------------------------------------------------------------
// 第二步
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;
}
}
---------------------------------------------------------------------------------
// 第三步
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
由于resolve传的值默认为false,所以只会进行加载不会执行后续两步的装载过程;接下来看一下forName();
// forName
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
从上边的源码可以看出forName在执行的时候是传的值为true,所以他们的区别是
Class.forName得到的class是已经初始化完成的;
Classloder.loadClass得到的class是还没有链接的;