JVM学习笔记(一)类加载,双亲委派

5 篇文章 0 订阅

尚硅谷JVM学习笔记

第一章

第一节 JDK、JRE、JVM关系回顾

  • JVM:Java Virtual Machine,翻译过来是Java虚拟机
    • JRE:Java Runtime Environment,翻译过来是Java运行时环境
    • JRE=JVM+Java程序运行时所需要的类库
  • JDK:Java Development Kits,翻译过来是Java开发工具包
    • JDK=JRE+Java开发辅助工具

images

第二节 JVM工作的总体机制

1、Java源程序编译运行过程

Java源程序→编译→字节码文件→放到JVM上运行

2、JVM工作的总体机制

总体机制的粗略描述:

  • 第一步:使用类加载器子系统将 *.class 字节码文件加载到 JVM 的内存
  • 第二步:在 JVM 的内存空间存储相关数据
  • 第三步:在执行引擎中将 *.class 字节码文件翻译成 CPU 能够执行的指令
  • 第四步:将指令发送给CPU执行

images

3、JVM落地产品

  • Sun公司的HotSpot
  • BEA公司的JRockit
  • IBM公司的J9 VM

第二章 类加载机制[重要]

第一节 类加载器分类

1、类加载器

类加载器(英文:ClassLoader)负责加载 .class 字节码文件,.class 字节码文件在文件开头有特定的文件标识。ClassLoader 只负责 *.class 字节码文件的加载,至于它是否可以运行,则由 Execution Engine 决定。

images

2、分类

JVM 中类加载器分为四种:前三种为虚拟机自带的加载器。

中文名称英文名称说明加载范围
启动类加载器BootstrapC++ 语言编写,不是 ClassLoader 子类,Java 中为 null$JAVA_HOME/jre/lib/rt.jar
扩展类加载器Extensionsun.misc.Launcher.ExtClassLoaderJAVA_HOME/jre/lib/*.jar
-Djava.ext.dirs 参数指定目录下的 jar 包 JAVA_HOME/jre/lib/ext/classes 目录下的 class
应用类加载器AppClassLoadersun.misc.Launcher.AppClassLoaderclasspath中指定的 jar 包及目录中的 class
自定义类加载器程序员自己开发一个类继承 java.lang.ClassLoader,
定制类加载方式

3、父子关系

  • 父子关系1:启动类加载器是扩展类加载器的父加载器
  • 父子关系2:扩展类加载器是应用类加载器的父加载器

4、通过代码查看类加载器

// 1.获取Person类的Class对象
// 2.通过Class对象进一步获取它的类加载器对象
ClassLoader appClassLoader = Person.class.getClassLoader();

// 3.获取appClassLoader的全类名
String appClassLoaderName = appClassLoader.getClass().getName();

// 4.打印appClassLoader的全类名
// sun.misc.Launcher$AppClassLoader
System.out.println("appClassLoaderName = " + appClassLoaderName);

// 5.通过appClassLoader获取扩展类加载器(父加载器)
ClassLoader extClassLoader = appClassLoader.getParent();

// 6.获取extClassLoader的全类名
String extClassLoaderName = extClassLoader.getClass().getName();

// 7.打印extClassLoader的全类名
// sun.misc.Launcher$ExtClassLoader
System.out.println("extClassLoaderName = " + extClassLoaderName);

// 8.通过extClassLoader获取启动类加载器(父加载器)
ClassLoader bootClassLoader = extClassLoader.getParent();

// 9.由于启动类加载器是C语言开发的,在Java代码中无法实例化对象,所以只能返回null值
System.out.println("bootClassLoader = " + bootClassLoader);

第二节 双亲委派机制

  • 当我们需要加载任何一个范围内的类时,首先找到这个范围对应的类加载器
  • 但是当前这个类加载器不是马上开始查找
  • 当前类加载器会将任务交给上一级类加载器
  • 上一级类加载器继续上交任务,一直到最顶级的启动类加载器
  • 启动类加载器开始在自己负责的范围内查找
  • 如果能找到,则直接开始加载
  • 如果找不到,则交给下一级的类加载器继续查找
  • 一直到应用程序类加载器
  • 如果应用程序类加载器同样找不到要加载的类,那么会抛出ClassNotFoundException

第三节 验证双亲委派机制

1、实验1

  • 第一步:在与JDK无关的目录下创建Hello.java
public class Hello {
	
	public static void main(String[] args){
		System.out.println("AAA");
	}
}
  • 第二步:编译Hello.java
  • 第三步:将Hello.class文件移动到$JAVA_HOME/jre/classes目录下
  • 第四步:修改Hello.java
public class Hello {
	
	public static void main(String[] args){
		System.out.println("BBB");
	}
	
}
  • 第五步:编译Hello.java
  • 第六步:将Hello.class文件移动到$JAVA_HOME/jre/lib/ext/classes目录下
  • 第七步:修改Hello.java
public class Hello {
	public static void main(String[] args){
		System.out.println("CCC");
	}
}
  • 第八步:编译Hello.java
  • 第九步:使用java命令运行Hello类,发现打印结果是:AAA
    • 说明Hello这个类是被启动类加载器找到的,找到以后就不查找其他位置了
  • 第十步:删除$JAVA_HOME/jre/classes目录
  • 第十一步:使用java命令运行Hello类,发现打印结果是:BBB
    • 说明Hello这个类是被扩展类加载器找到的,找到以后就不查找其他位置了
  • 第十二步:删除$JAVA_HOME/jre/lib/ext/classes目录
  • 第十三步:使用java命令运行Hello类,发现打印结果是:CCC
    • 说明Hello这个类是被应用程序类加载器找到的

2、实验2

  • 第一步:创建假的String类
package java.lang;
public class String {

    public String() {
        System.out.println("嘿嘿,其实我是假的!");
    }
}
  • 第二步:编写测试程序类
    @Test
    public void testLoadString() {

        // 目标:测试不同范围内全类名相同的两个类JVM如何加装
        // 1.创建String对象
        java.lang.String testInstance = new java.lang.String();

        // 2.获取String对象的类加载器
        ClassLoader classLoader = testInstance.getClass().getClassLoader();
        System.out.println(classLoader);
    }
  • 第三步:查看运行结果是null
    • 假的String类并没有被创建对象,由于双亲委派机制,启动类加载器加载了真正的String类

第四节 双亲委派机制的好处

  • 避免类的重复加载:父加载器加载了一个类,就不必让子加载器再去查找了。同时也保证了在整个 JVM 范围内全类名是类的唯一标识。
  • 安全机制:避免恶意替换 JRE 定义的核心 API

第五节 双亲委派机制的弊端

  • 检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个 问题,即顶层的ClassLoader无法访 问底层的ClassLoader所加载的类。
  • 通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中提供了一个接口,该接口需要在应用类中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

结论

  • 由于Java虚拟机规范并没有明确要求类加载器的加载制一定要使用双亲委派模型,只是建议采用这种方式而已。
  • 比如在Tomcat中,类加载器所采用的加载机制就和传统的双亲委派模型有一定区别,当缺省的类加载器接收到-个类的加载任务时,首先会由它自行加载,当它加载失败时,才会将类的加载任务委派给它的超类加载器去执行,这同时也是Servlet规范推荐的一种做法。

第三章 总体机制中不重要的部分

1、本地接口 Native Interface

本地接口的作用是融合不同的编程语言为 Java 所用,它的初衷是融合 C/C++程序。因为 Java 诞生的时候是 C/C++ 横行的时候,要想立足,必须有能力调用 C/C++。于是就在内存中专门开辟了一块区域处理标记为 native 的代码,它的具体做法是 Native Method Stack 中登记 native 方法,在Execution Engine 执行时加载 native libraies。

目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用 Socket 通信,也可以使用 Web Service 等等,不多做介绍。

2、本地方法栈 Native Method Stack

专门负责在本地方法运行时,提供栈空间,存放本地方法每一次执行时创建的栈帧。它的具体做法是在 Native Method Stack 中登记 native 方法,在 Execution Engine 执行时加载本地方法库。

native 方法举例:

public static native void yield();

2、程序计数器

也叫PC寄存器(Program Counter Register)。用于保存程序执行过程中,下一条即将执行的指令的地址。也就是说能够保存程序当前已经执行到的位置。这个位置由执行引擎读取下一条指令,是一个非常小的内存空间,从内存空间使用优化这个角度来看:几乎可以忽略不记。

3、执行引擎 Execution Engine

作用:用于执行字节码文件中的指令。

执行指令的具体技术:

  • 解释执行:第一代JVM。
  • 即时编译:JIT,第二代JVM。
  • 自适应优化:目前Sun的Hotspot JVM采用这种技术。吸取了第一代JVM和第二代JVM的经验,在一开始的时候对代码进行解释执行, 同时使用一个后台线程监控代码的执行。如果一段代码经常被调用,那么就对这段代码进行编译,编译为本地代码,并进行执行优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。
  • 芯片级直接执行:内嵌在芯片上,用本地方法执行Java字节码。

4、直接内存

images

①作用

提高特定场景下性能。

②应用场景

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 本机直接内存的分配不会受到 Java 堆大小的限制,受到本机总内存大小限制。 配置虚拟机参数时,不要忽略直接内存防止出现 OutOfMemoryError 异常。

③直接内存(堆外内存)与堆内存比较

直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显。直接内存 I/O 读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙龙龙呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值