本文介绍JVM
中的几个面试题,十分有用
主要有几题
简单来说,可以这样理解分类
.class
文件加载至JVM
中int
是0,包装类为null
)什么是符号引用,什么又是直接引用 可以这样进行理解,我们有一个
A
类和B
类,A
类中使用到了B
类 在字节码中,会用一个符号代表这是B
类,这就是符号引用 而在B
类进行类加载后,JVM
成功的加载了这个B
类,使得堆内存中有对应的B.class
的对象,同时方法区中有静态方法与属性。 这个时候,A
类就会将之前的符号引用,改为直接引用,设置为上面堆内存的B.class
对象,或者方法区中的静态方法与属性类加载的时机
在了解双亲委派机制之前,我们先设想一个问题,就是如果我们用户自己写一个String
这样一个的类,会出现什么样的情况?
这个问题说简单也简单,说复杂就比较复杂了,这个问题正好是由双亲委派机制来进行解决的。
在了解双亲委派机制之前,我们先得了解几个ClassLoader
类加载器
类加载器 | 说明 | 加载类的范围 |
---|---|---|
Bootstrap ClassLoader | 启动类加载器,最顶层的类加载器,这个加载器,Java中不能获取,返回的是一个null | <JAVA HOME>/lib |
Extension ClassLoader | 扩展类加载器 | <JAVA HOME>/lib/ext |
Application ClassLoader | 应用程序类加载器,也是我们最常用的类加载器 | classpath/java.class.path |
User ClassLoader | 用户自定义的类加载器 | 任意来源的类 |
好的,当了解完上面的四种类加载器之后,我们将进行验证,看下面代码
package com.banmoon.parentsappoint;
public class ParentsAppointTest {
public static void main(java.lang.String[] args) {
System.out.println("java.lang.String:" + "abc".getClass().getClassLoader());
System.out.println("com.banmoon.parentsappoint.String:" + String.class.getClassLoader());
}
}
为什么,他们的类加载器是不同的呢。有人说了,是因为类加载器本身就是有不同的加载类职责范围。
那么当我们进行类加载的时候,程序怎么知道这个类要用什么类加载器。然而就是这段不同的类,确定使用不同类加载器的过程,就是我们将的双亲委派机制。
我们先看这段代码,正是双亲委派机制的代码,在ClassLoader.java
中可以找到这段代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,先检查类是否已经被加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// parent是父亲加载器,这里仅仅是逻辑层面上的,并不是指继承方面的父类
if (parent != null) {
// 如果父亲加载器不为空,则先交给父亲加载器进行类加载
c = parent.loadClass(name, false);
} else {
// 如果父亲加载器为空,那说明接下来是Bootstrap ClassLoader了,直接交给特殊的方法进行加载
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;
}
}
这就是双亲委派机制,下面可以画一个图
可以看到,类加载的时候,永远都是Bootstrap ClassLoader
(启动类加载器)尝试去加载
当然,如果类不合适,将会向下进行委派加载
上面的这种行为可以这样概括,向上检查,向下委托加载
下面这个就是JVM的内存模型,有些细节没有完全画出来,后续会补上
需要讲一下,其中的这些是什么意思
String
的属性,那么在类加载的连接阶段,常量池中会存储这么一个指针常量。
Class
对象。还有就是开发者编写的静态变量。GC
的主战场。下面篇幅会提到
Java
方法栈,这样好理解一下。Java
在调用方法时,会将字节码方法入栈,这个东西叫做栈帧。栈这种数据结构,就是先入后出。类似的,一个A
方法压入栈,这个方法调用一个B
方法,就会将B
方法压入栈。结构展示A
在最底下,B
在上。在结束的时候,是B
方法栈帧先结束,然后才是A
方法的栈帧。符合先入后出原则。在栈帧结构内部,我们可以如下进行划分,分别是
Java
是由C++语言
编写的,里面肯定会调用到C++
,故本地方法栈就是存储的是调用C++
方法时的变量存储。
我是半月,你我一同共勉!!!