在入门之前,先提出几个面试常问的问题。
1.请你谈谈对JVM的理解?Java8虚拟机和之前的变化更新?
2.什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
3.JVM常用的调优参数有哪些?
4.内存快照如何抓取,怎么分析Dump文件
5.谈谈JVM中,你对类加载器的认识?
下面将从JVM位置、体系结构、类加载器、Native、PC寄存器来介绍。相信介绍完后,你能顺利的回答上面提出的面试题。
一、JVM的位置 JVM是运行在操作系统之上的,它与硬件没有直接的交互,存在于JRE构建环境中。
作用:加载Class文件。
类加载器只负责class文件的加载,至于它是否可以运行,则由执行引擎决定。
public class Car {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
Class extends Car> aClass1 = car1.getClass();
Class extends Car> aClass2 = car2.getClass();
Class extends Car> aClass3 = car3.getClass();
//car1、car2、car3属于同一个模板
System.out.println(aClass1); //class Car
System.out.println(aClass2); //class Car
System.out.println(aClass3); //class Car
}
}
类加载器并没有大家想的那么简单,它还分为好几个等级。
1、虚拟机自带的加载器
2、启动类(根)加载器
3、扩展类加载器
4、应用程序加载器
public class Car {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
Class extends Car> aClass1 = car1.getClass();
Class extends Car> aClass2 = car2.getClass();
Class extends Car> aClass3 = car3.getClass();
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader); //sun.misc.Launcher$AppClassLoader@6d06d69c
System.out.println(classLoader.getParent()); //sun.misc.Launcher$ExtClassLoader@70dea4e
System.out.println(classLoader.getParent().getParent()); //null 1、不存在 2、java程序获取不到
}
}
值为null一般是两种情况,一种是不存在,一种是java程序获取不到。很明显,这里是第二种,因为启动类加载器不是Java的,是C++的,所以获得到的结果为null。
我们运行下面的代码,会发现报错,我们定义了String类,为什么找不到main方法呢?这是因为Java类加载器采取双亲委派机制来保证安全。
public class String {
public static void main(String[] args) {
String s = new String();
}
}
工作原理
1.类加载器收到类加载请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3.启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,通知子加载器进行加载。如果均加载石材,就会抛出ClassNotFoundException异常。
通俗来讲,每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了了时,儿子自己才想办法去完成。
双亲委派优点
1.安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String。,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
1.避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)。Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
沙箱安全机制
Java安全模型的核心就是Java沙箱。沙箱机制就是讲Java代码限定在虚拟机JVM特定的运行范围中,并且严格限制代码对本地资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。
当前最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示最新的安全模型(jdk 1.6)
通俗来说就是虚拟机把代码加载到拥有不同权限的域里,然后代码就拥有了该域的所有权限。这样就能控制不同代码拥有不同调用操作系统和本地资源的权限。
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层c语言的库,会进入本地方法栈,调用本地方法接口(JNI)。
JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用。最初:C、C++
Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序。所以它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法,在最终执行的时候通过本地方法接口加载本地方法库中的方法。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中较为少见。
程序计数器(Program Counter Register)
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
参考资料
1.JVM-沙箱安全机制
2.JVM类加载器(详解)
3.快速入门JVM第一讲——JVM体系结构概述
4.【狂神说Java】JVM快速入门篇
5.【java】jvm内存模型全面解析



