目录

        • 一、JVM初探
          • 1.1 JVM的位置
          • 1.2 JVM体系结构
        • 二、双亲委派机制
          • 2.1 类加载器
          • 2.2 面试问题
        • 三、沙箱安全机制
          • 3.1 什么是沙箱
          • 3.2 组成沙箱的基本条件

一、JVM初探

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM 是一种用于计算机设备的规范,它是一个虚构出来的计算机,是通过在实际计算机上仿真模拟各种计算机功能来实现的。

引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。

1.1 JVM的位置

1.2 JVM体系结构

本地方法接口:JNI(Java Native Interface)


二、双亲委派机制

在介绍双亲委派机制前先介绍一下 类加载器

2.1 类加载器

作用:加载Class文件


Java是运行是通过Java的虚拟机(JVM)的,但是它是如何运行在JVM中了呢?我们第一天学Java 就知道,编写的Java源代码需要被编译器编译成.class的字节码文件,然后再通过解释器,电脑才能执行。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:

1. 虚拟机自带的加载器
2. 启动类(根) 加载器【BOOTStrapClassLoader】: 主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
3. 扩展类加载器 【EXTClassLoader】: 主要负责加载jre/lib/ext目录下的一些扩展的jar。
4. 应用程序加载器【AppClassLoader】: 主要负责加载应用程序的主函数类

:加载过程 从 4到1进行加载

看一下JDK 8 中的双亲委派是怎么实现的
搜索ClassLoader 类

看到 loadClass 方法

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先检查类是否被加载过// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try { // 如果父类加载器存在 就是使用父类加载器,否则使用Boot根加载器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) {//如果仍未找到,则调用 findClass 以查找该类。// 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}// 如果已经加载过,就直接解析if (resolve) {resolveClass(c);}return c;}}

解释:双亲委派机制(安全):APP—>EXT—>BOOT【最终执行】

类加载器收到类加载的请求
将这个请求向上委托为父类加载器去完成,一直向上委托,直到启动类加载器
启动类加载器检查是否能够加载当前的这个类,能加载就结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载
重复步骤 3

但是BOOT根加载器输出是null,这是java调用不到~ C,C++ Java = C+±- :去掉繁琐的东西(指针、内存管理等)

举例:我自己建立一个 java.lang.String 类,写上 static 代码块

package java.lang;public class String {static{System.out.println("我是自定义的String类的静态代码块");}}

在另外的程序中加载 String 类,看看加载的 String 类是 JDK 自带的 String 类,还是我们自己编写的 String 类

public class StringTest {public static void main(String[] args) {java.lang.String str = new java.lang.String();System.out.println("hello,atguigu.com");StringTest test = new StringTest();System.out.println(test.getClass().getClassLoader());}}

程序并没有输出我们静态代码块中的内容,可见仍然加载的是 JDK 自带的 String 类

为什么呢?
由于我们定义的String类本应用系统类加载器,但它并不会自己先加载,而是把这个请求委托给父类的加载器去执行,到了扩展类加载器发现String类不归自己管,再委托给父类加载器(引导类加载器),这时发现是java.lang包,这事就归引导类加载器管,所以加载的是 JDK 自带的 String 类

/** * 双亲委派机制 */public class Car {public static void main(String[] args) {Car car1 = new Car();Car car2 = new Car();Car car3 = new Car();//不同的实例System.out.println("car1 hashCode="+car1.hashCode());System.out.println("car2 hashCode="+car2.hashCode());System.out.println("car3 hashCode="+car3.hashCode());//同一个类模版Class<? extends Car> aClass1 = car1.getClass();Class<? extends Car> aClass2 = car2.getClass();Class<? extends Car> aClass3 = car3.getClass();System.out.println("aClass1 hashCode="+aClass1.hashCode());System.out.println("aClass2 hashCode="+aClass2.hashCode());System.out.println("aClass3 hashCode="+aClass3.hashCode());//由于类模版都是一个,以下就选择一个进行测试ClassLoader classLoader = aClass1.getClassLoader();System.out.println(classLoader);//AppClassLoaderSystem.out.println(classLoader.getParent());//ExtClassLoader所在位置:\jre\lib\extSystem.out.println(classLoader.getParent().getParent());//null 1.不存在2.java程序获取不到所在位置:rt.jar}}

运行测试结果:

2.2 面试问题

为什么需要双亲委派机制?(也就是双亲委派的优点):

  • 双亲委派机制使得类加载出现层级,父类加载器加载过的类,子类加载器不会重复加载,可以防止类重复加载;
  • 使得类的加载出现优先级,防止了核心API被篡改,提升了安全,所以越基础的类就会越上层进行加载,反而一般自己的写的类,就会在应用程序加载器(Application)直接加载。

如何打破双亲委派?

  • 自定义类加载器,重写loadClass方法
  • 使用线程上下文类加载器

三、沙箱安全机制

3.1 什么是沙箱

沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

​ 所有的Java程序运行都可以指定沙箱,可以定制安全策略。

3.2 组成沙箱的基本条件
  • 字节码校验器 (bytecode verifier): 确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
  • 类装载器(class loader): 其中类装载器在3个方面对Java沙箱起作用
    1、它防止恶意代码去干涉善意的代码;
    2、它守护了被信任的类库边界;
    3、它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式

1、从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
2、由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。

  • 存取控制器(access controller): 存取控制器可以控制核心API对操作系统的存取权限,而这个控制策略的设定,可以由用户自己设定。
  • 安全管理器(security manager): 是核心API 和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
  • 安全软件包(security package): java.security 下的类和扩展包下的类,运行用户未自己的应用增加新的安全特性,包括:
    1、安全提供者
    2、消息摘要
    3、数字签名
    4、加密
    5、鉴别