JVM结构

jvm Hotspot 模型如下图所示

上图所示 jvm 中 Java stack,Native Method Stack ,Program Counter Register 是线程私有,不存在线程安全问题 ;Method Area 和Heap 是线程共享区

类加载器 – Classloader

类加载器(Classloader)负责加载 class,类加载器将class文件字节码解析到内存中,并且这些内容转换成方法区 中运行时数据结构,classloader加载完class文件后 Execution Engine决定是否能够执行程序

classloader分类

1、启动类加载器

2、拓展类加载器

3、应用程序类加载器

4、用户自定义加载器

启动类加载器

    启动类加载器BootstrapClassloader也叫根加载器,是虚拟机自带的加载器由 C++实现,用户加载 jre/lib/rt.jar包中的全部class文件,rt.jar是Java基础类库,包含常见的java基础类,

eg:Object类

1
2
3
4
5
6
public class Demo {
public static void main(String[] args){
Object obj = new Object();
System.out.println(obj.getClass().getClassLoader());
}
}

运行结果

1
null

拓展类加载器

    拓展类加载器 是 Java虚拟机自带实现,由Java语言实现用于加载 jre/lib/ext/ 目录中的全部jar包中的class文件 (jdk11 中变成 PlatformClassLoader)

使用zipfs验证结果

1
2
3
4
5
6
public class Demo {
public static void main(String[] args){
ZipInfo zipInfo = new ZipInfo();
System.out.println(zipInfo.getClass().getClassLoader());
}
}

但是,以上代码仅能在java8及以下版本,jdk11及以上版本中已经移除ext相关目录,集成到lib/modules文件中,取代ExtClassloader 被jdk.internal PlatformClassloader取代;

从Java9开始,jdk逐步替代相关api并移除部分lib,响应的目录结构也发生变化,eg: jdk11中的目录 已经不存在ext相关jar,统一合并到 lib/modules文件中

应用程序类加载器

    应用程序类加载器(App Classloader)是虚拟机自带的加载器,用于加载当前应用的classpath文件中的所有类

1
2
3
4
5
public class Demo{
public static void main(String[] args){
System.out.println(Demo.class.getClassLoader());
}
}

运行结果

1
jdk.internal.loader.ClassLoaders$AppClassLoader@71bc1ae4

用户自定义加载器

    除了上述三种jvm自带类加载器外,用户还可以通过继承 ClassLoader抽象类自定义一个类加载器;不妨通过代码查找一下classloader之间的继承关系

1
2
3
4
5
6
7
public class Demo{
public static void main(String[] args){
System.out.println(Demo.class.getClassLoader());
System.out.println(Demo.class.getClassLoader().getParent());
System.out.println(Demo.class.getClassLoader().getParent().getParent());
}
}

    运行结果

1
2
3
jdk.internal.loader.ClassLoaders$AppClassLoader@71bc1ae4
jdk.internal.loader.ClassLoaders$PlatformClassLoader@7e0ea639
null

从结果过可以看出

AppClassLoader 继承自 PlatformClassLoader

PlatformClassLoader 继承自 BootstrapClassLoader

各种类加载器之间关系如图所示

双亲委派机制

    通过上述ClassLoader之间的关系,可以看出不同加载器加载不同位置的class文件,但是如果出现一个class文件同时出现上述的多个位置会发生什么? 这就是类加载器常见的问题双亲委派机制 , 先看一个案例:

1
2
3
4
5
6
7
package java.lang;

public class Object {
public static void main(String[] args) {
System.out.println("test");
}
}

运行结果 (注意请在jdk1.8中测试,jdk11 有编译检查,无法正常运行)

出现这样结果,是由于jvm中的类加载器采用双亲委派机制寻找类;

双亲委派:

    当一个类加载收到加载请求,首先不会自己加载这个类,而是把相关请求交给父类去完成加载;如果是多层继承,则逐层转发;如果父类反馈无法完成加载请求,子类才会尝试自己去加载。

所以用双亲委派机制解释上述结果,就不难理解为什么会报错;首先APPClassLoader将类加载请求转发给ExtClassLoader,ExtClassLoader转交给BootstrapClassLoader,而BootstrapClassLoader找到 java.lang.Object 类,此时的class文件是rt.jar 自然不会有main方法,只有本地classpath中自定义的Object才有main方法。

jvm中双亲委派机制有一个好处,class文件加载规则相同;不管那个类加载器加载类,最终都是转交给BootstrapClassLoader,这样都能保证jvm自带的类库都是相同的,这样我们自定义的类不会污染jdk自带类即便类名相同,这种沙箱保护机制是非常常见的。这样不难理解jdk11删除了lib/ext并且集成到modules文件中,防止人为替换rt.jar和ext下jar文件造成安全问题。

程序计数器 – Program Counter Register

    程序计数器 简称 PC寄存器,是线程私有的,他是一个指针,用于指向方法区中的方法字节码(栈帧 操作数栈),用于记录下一条指令执行的地址 如图所示 

思考,为什么需要PC寄存器而且还是线程私有。在多线程环境中,CPU需要不停切换各个线程,有PC寄存器,CPU切换是能够准确找到当前切换回的线程改从什么地方开始继续执行

虚拟机栈 – Java Stack

虚拟机栈 Java Stack也叫做 Java栈。每个线程都会创建一个Java Stack(线程私有),其中Java Stack内部报错一系列 Stack Frame(栈帧),每个栈帧对应Java一次次执行的Java Method。虚拟机栈和其他线程私有的内存区一样,和线程保持相同生命周期。虚拟机栈结构如下图所示

    Java Stack只有压栈和出栈操作,遵循后进先出(FILO)原则,在一个活动的线程中同一个时间点只有唯一一个活动的栈帧,当前正在执行的方法对应的栈帧;如果方法调用另外一个方法,对应的方法就会创建新的栈帧并且放在栈顶成为当前栈帧。

    Java Stack执行方法是只有两种结果,正常退出或者抛出异常结束;者两种情况都会导致栈帧被弹出结束。

虚拟机栈大小调整

    我们可以通过-Xss参数设置虚拟机栈大小,默认单位字节。

1
2
3
-Xss1m
-Xss1g
-Xss24k

    虚拟机栈大小影响方法调用的深度,栈设置越大,方法调用深度越深