Java 细聊JVM之类加载器

什么是JVM

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

JVM所处的位置

JVM是运行在操作系统之上的,它与硬件没有直接的交互,但是它可以通过 native 本地方法调用硬件接口

JVM体系结构

类装载器

负责加载 class 文件,class 文件在文件开头有特定的文件标示,将 class 文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构并且 ClassLoader 只负责 class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。

特定的文件标示

众所周知,Java 的 logo是一杯cafe。使用 sublime  任意打开一个 class 文件,发现开头的前八个字母都是“cafe babe”,这就是 class 的标识,这也是作者詹姆斯•高斯林(James Gosling)在创作 Java 之初就用到了两个魔数。

方法区简单介绍

JVM 中的方法区,并不是用来存类里面方法的区域,它是用来存放类的描述信息,也就是放模板的地方。

分类

Java中类装载器总共分为两大类,四小类,加载顺序是自上到下依次加载。

虚拟机自带的加载器

  • 启动类加载器(Bootstrap)C++编写,是根加载器,最早加载 JVM 基础类,java.lang.*,java.uitl.* 等等
  • 扩展类加载器(Extension)Java编写,javax.* 的所有类都是使用这种加载器
  • 应用程序类加载器(AppClassLoader)Java也叫系统类加载器,加载当前应用的classpath的所有类。除上面两种的加载的类,其他的都属于这种。最简单的理解就是用户自定义的类,都属于应用程序类加载器

用户自定义加载器

继承 Java.lang.ClassLoader 的子类,用户可以定制类的加载方式

示例

public class ClassLoaderDemo {
	public static void main(String[] args) {
		Object object = new Object();
		//根据反射机制查找 Object 类加载器。因为 Object是JDK自带的类
		//所以是,启动类加载器(Bootstrap),但是因为他是C++编写,所以输出 null
		System.out.println(object.getClass().getClassLoader());
		
		ClassLoaderDemo classLoaderDemo = new ClassLoaderDemo();
		//这个对象是用户自定义编写的,所以他的加载器是 应用程序类加载器(AppClassLoader)
		System.out.println(classLoaderDemo.getClass().getClassLoader());
		
		System.out.println();
		
		//启动类加载器(Bootstrap) 输出 null
		System.out.println(classLoaderDemo.getClass().getClassLoader().getParent().getParent());
		//扩展扩展类加载器(Extension)
		System.out.println(classLoaderDemo.getClass().getClassLoader().getParent());
		//应用程序类加载器(AppClassLoader)
		System.out.println(classLoaderDemo.getClass().getClassLoader());
	}
}

结果

双亲委派机制

 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。  

沙箱安全

假如我们不小心写了一个 String 类,包路径是 java.lang.String ,和JDK一样,运行以下看看结果

package java.lang; //包名和 String 一样

public class String {
	//主方法
	public static void main(String[] args) {
		System.out.println("String");
	}
}

结果

报的错误是找不到 main 方法,原因就是Java采用双亲委派机制和沙箱安全机制。当一个类被加载,首先是从启动类加载器(Bootstrap)加载,然后是扩展类加载器(Extension),最后才是应用程序类加载器(AppClassLoader)。所以这里加载的是 rt.jar 中的String类,所以没有 main 方法。

基本组件

1.类加载器结构

它防止了恶意代码去干涉善意的代码,这是通过为不同的类加载器装入的类提供了不同的命名空间来实现的,命名空间互相独立。
它守护了被信任类库的边界,这是通过分别使用不同的类加载器装载可靠的包和不可靠的包来实现的。
它将代码归入某类(命名空间),该类确定了代码可以执行哪些操作,一个保护域定义了这个代码在运行时将得到的权限。

2.class文件效验器

  • class文件结构的检查
  • 类型数据的语义检查
  • 字节码验证
  • 符号引用的检查
  • 二进制兼容

3.内置于JAVA虚拟机(及语言)的安全特性

  • 类型安全的引用转换
  • 结构化的内存访问(无指针算法)
  • 自动垃圾收集(不必显示的释放被分配的内存)
  • 数组边界检查
  • 空引用检查

4.安全管理器及API

虽然安全管理器是JAVA安全模型最大的优点之一,但是也有一个潜在的弱点。编写一个安全管理器是一项负责的任务,并可能导致出错。在实现安全管理器的check方法时,任何错误都将变成运行时的安全漏洞。

5.代码签名和认证

发布了80 篇原创文章 · 获赞 55 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/cong____cong/article/details/104784102