从面试开始:
请谈谈你对JVM 的理解?java8 的虚拟机有什么更新?
什么是OOM ?什么是StackOverflowError?有哪些方法分析?
JVM 的常用参数调优你知道哪些?
内存快照抓取和MAT分析DUMP文件知道吗?
谈谈JVM中,对类加载器你的认识?
位置:JVM是运行在操作系统之上的,它与硬件没有直接的交互
1. 结构图
方法区:存储已被虚拟机加载的类元数据信息(元空间)
堆:存放对象实例,几乎所有的对象实例都在这里分配内存
虚拟机栈(java栈):虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息
程序计数器:当前线程所执行的字节码的行号指示器
本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
2. 类加载器ClassLoader
负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
类加载器分为四种:前三种为虚拟机自带的加载器。
启动类加载器(Bootstrap)C++
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
扩展类加载器(Extension)Java
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
应用程序类加载器(AppClassLoader)Java
也叫系统类加载器,负责加载classpath(java.class.path)中指定的jar包及目录中class
用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式
工作过程:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
为什么要设计双亲委派机制?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
写段儿代码演示类加载器:
public class Demo {
public Demo() {
super();
}
public static void main(String[] args) {
Object obj = new Object();
String s = new String();
Demo demo = new Demo();
System.out.println(obj.getClass().getClassLoader());
System.out.println(s.getClass().getClassLoader());
System.out.println(demo.getClass().getClassLoader().getParent().getParent());
System.out.println(demo.getClass().getClassLoader().getParent());
System.out.println(demo.getClass().getClassLoader());
System.out.println("BootstrapClassLoader加载的文件: ");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url);
}
System.out.println("ExtClassLoader加载的文件: ");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println("AppClassLoader加载的文件: ");
System.out.println(System.getProperty("java.class.path"));
}
}
打印控制台中的sun.misc.Launcher,是一个java虚拟机的入口应用
AppClassLoader加载类的双亲委派机制源码:
//AppClassLoader的loadClass方法,里面实现了双亲委派机制 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查当前类加载器是否已经加载了该类 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(); //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 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; } }
3. 执行引擎Execution Engine
Execution Engine执行引擎负责解释命令,提交操作系统执行。
4. 本地接口Native Interface
本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序,Java 诞生的时候是 C/C++横行的时候,要想立足,必须有调用 C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是 Native Method Stack中登记 native方法,在Execution Engine 执行时加载native libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用 Socket通信,也可以使用Web Service等等,不多做介绍。
5. Native Method Stack
它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
6. PC寄存器(程序计数器)
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即 将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
7. Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。
静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在方法区中
But
实例变量存在堆内存中,和方法区无关