什么?你还不明白什么是ClassLoader?不如试试从JVM来入手ClassLoader是什么玩意吧!

news2024/11/18 1:34:09

文章目录

  • 环境配置篇
    • 如何执行一个文件
    • 配置JDK环境(简述)
  • Java文件执行流程
    • 编译
    • 加载
      • JVM环境准备
      • BootStrapClassLoader
      • sun.misc.laucher
      • AppClassLoader
    • 解释
    • 执行
    • 回收
  • ClassLoader讲解
    • 主要的三个ClassLoader
    • 双亲委派模型
    • loadClass方法讲解
    • 自定义ClassLoader
  • JVM内存管理

环境配置篇

如何执行一个文件

阶段一:手动+图形界面
在日常生活中,我们要运行一个exe文件,我们的做法是什么?双击这个文件就能让它运行起来,那我们把这个方法抽象出来就是:我们找到文件所在的位置,并在当前位置运行文件。在这个过程中,寻找文件位置的过程是我们手动完成的。例如我们要运行javac.exe文件,我们需要找到JDK\bin文件夹,然后找到javac.exe文件,双击它,这就执行了javac.exe文件

阶段二:手动+命令行
那么我们再通过命令行的方式执行javac.exe文件。打开命令窗口,一般情况下不会直接进入到JDK\bin文件夹中,所以我们要通过cd命令不断地进入到JDK\bin文件夹中,通过输入javac命令来代替双击文件,这样javac.exe文件就执行起来了

阶段三:自动+命令行
总是自己JDK\bin文件夹太麻烦了,而且这个文件夹位置就是固定的,我告诉计算机这个文件夹的位置,以后找到我要输入javac命令,计算机就帮我自行找到JDK\bin文件夹,然后执行javac文件行不行?当然是可以的了。下面就讲解如何配置JDK环境。

配置JDK环境(简述)

一般JDK环境配置都是这样子的

C:\Program Files\Java\jdk1.8.0_201\bin
JAVA_HOME:C:\Program Files\Java\jdk1.8.0_201
Path:%JAVA_HOME%\bin

至于为什么要把一个完整路径分成两部分呢?主要有如下两个原因

  • 首先,当我们重新安装了JDK或者是JDK升级之后,JDK的安装路径都会发生变化。在这种情况下,如果我们配置了JAVA_HOME环境变量,我们就可以只修改JAVA_HOME当中JDK的安装路径,而Path和CLASSPATH中涉及到的JDK的安装路径因为用%JAVA_HOME%代替了,所以不需要做任何修改,这样就减少了工作量和出错的概率。
  • 其次,某些集成开发环境(IDE)和Java Web服务器会以JAVA_HOME环境变量的值去寻找JDK的安装路径。所以,如果我们希望在实际开过程中减少遇到莫名其妙问题的概率,应该添加一个JAVA_HOME环境变量,并且在Path和CLASSPATH中用%JAVA_HOME%去代替JDK安装的路径。

然而我没有配置CLASSPATH环境变量,这里就总结一下它的作用,具体讲解点击这里

  • 结论1:在没有配置CLASSPATH环境变量时,java命令在找class文件时是默认在当前目录下寻找的。

  • 结论2:配置过CLASSPATH环境后,java命令是按照CLASSPATH变量中的路径来的寻找class文件的,这就是为什么CLASSPATH变量中配置没有当前目录时,即使当前目录中有class文件,java命令仍然不能正常运行的原因。

Java文件执行流程

这不是一个简简单单的Java文件执行流程介绍,里面包含了很多细节,但是这是原创文章,只是个人总结,所以难免会发生错误,大佬轻点喷,重谢!!!
在这里插入图片描述

编译

这一步没什么好说的,就是调用javac.exe,将.java文件编译成.class文件

加载

下面的讲解,一切都发生在java -jar xxx.jar等java命令之后,即运行之后。

JVM环境准备

当你在调用java -jar xxx.jar的时候,操作系统会在path下载你的java.exe程序,java.exe就通过下面一个过程来确定JVM的路径和相关的参数配置了。

首先查找jre路径,Java是通过GetApplicationHome api来获得当前的Java.exe绝对路径,c:\j2sdk1.4.2_09\bin\java.exe,那么它会截取到绝对路径c:\j2sdk1.4.2_09\,并作为jre路径

然后通过c:\j2sdk1.4.2_09\找到jvm.cfg,根据jvm.cfg配置文件找到jvm.dll。Java通过LoadJavaVM来装入JVM.dll文件.装入工作很简单就是调用Windows API函数:

LoadLibrary装载JVM.dll动态连接库.然后把JVM.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。JVM.dll的装载工作宣告完成。

这样就可以在Java中调用JVM的函数了.调用InvocationFunctions->CreateJavaVM也就是JVM中JNI_CreateJavaVM方法获得JNIEnv结构的实例。

BootStrapClassLoader

首先说明的是,BootStrapClassLoader是由C/C++编写的,它本身就是虚拟机的一部分,所以它并不是JAVA类,也就是说无法在Java代码中获取它的引用。

在JVM启动之后,就会唤醒的BootStrapClassLoader,BootStrapClassLoader就会把JDK\JRE\lib下的jar包全部加载进JVM,其中有一个rt.jar包中,有sun.misc.laucher类。这个类非常重要

sun.misc.laucher

看一下sun.misc.laucher部分源码

  • 加载了ExtClassLoader
  • 加载了AppClassLoader
public class Launcher {

	private ClassLoader loader
	
    public Launcher() {
        ClassLoader extcl;
        extcl = ExtClassLoader.getExtClassLoader();

        loader = AppClassLoader.getAppClassLoader(extcl);
    }
}

AppClassLoader

AppClassLoader源码如下,总的来说,就是根据java.class.path来加载.class文件。这个java.class.path就是我们在环境变量中配置的CLASSPATH变量的值。看吧,这里是不是就把这些知识串起来了。

    static class AppClassLoader extends URLClassLoader {
        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);     
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }
    }

将.class加载到JVM后, 在Java堆中生成一个代表这个类的java.lang.Class对象,作为这个对象的访问入口。
在这里插入图片描述

解释

解释阶段是在代码执行的时候来触发的,当我们尝试执行一个类的方法时,首先会通过这个类的对象作为入口,找到对应方法的字节码的信息,然后解析器会把字节码信息解释成系统能识别的指令码。

解释阶段会有两种方式把字节码信息解释成机器指令码,一个是字节码解释器、一个是即时编译器JIT,一般来说当我们运行某个代码的时候会默认使用字节码解释器进行指令解析,只有当某个方法成为热点方法后,即时编译器就会把热点方法的指令码保存起来,下次方法执行的时候就无需重复的进行解析,所以JIT是对解析过程中的一种优化手段。

执行

操作系统把解释器解析出来的指令码,调用系统的硬件执行最终的程序指令

回收

GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收
  • 可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象

可达性分析算法

  • 可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会:第一次是经过可达性分析发生没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法
  • 当对象编程(GC Roots)不可达时,GC会判断该对象是否执行过finalize方法,若执行了则直接回收。否则,若对象未执行过finalized方法,将其放入F-Queue队列,由低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则对象复活。
  • 每个对象只能触发一次finalize()方法
  • 由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用

四种JVM的垃圾回收算法

标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。

ClassLoader讲解

终于来到了心心念念的ClassLoader了

主要的三个ClassLoader

BootStrapClassLoader:在规定的sun.mic.boot.class路径加载类
ExtClassLoader:在规定的java.ext.dirs路径加载类
AppClassLoader:在规定的java.class.path路径加载类

双亲委派模型

类加载过程

  1. AppClassLoader查找类时,先看看缓存是否有,缓存有则从缓存中获取,否则委托给父加载器ExtClassLoader
  2. ExtClassLoader查找类时,先看看缓存是否有,缓存有则从缓存中获取,否则委托给父加载器BootStrapClassLoader
  3. ExtClassLoader查找类时,先看看缓存是否有,缓存有则从缓存中获取,否则sun.mic.boot.class路径查找,查找成功则加载类,否则让子类BootStrapClassLoader自己加载
  4. BootStrapClassLoaderjava.ext.dirs路径查找,查找成功则加载类,否则让子类AppClassLoader自己加载
  5. AppClassLoaderjava.class.path路径查找,如果找到就加载类,否则就抛出异常。

类加载器ClassLoader规则

  1. 一个ClassLoader创建时直接指定一个ClassLoader为parent,如果没有指定parent,那么它的parent默认就是AppClassLoader
  2. 一个ClassLoader的父加载器为null,则JVM内置的加载器去替代,也就是BootStrapClassLoader

双亲委派模型的好处

  1. 避免重复加载
  2. 防止核心API库被随意篡改
    在这里插入图片描述

loadClass方法讲解

在loadClass()方法里面,一般执行以下步骤

  1. 执行findLoaderedClass(String)去检测这个Class是不是已经加载过了
  2. 执行父加载器的loadClass()方法
  3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找

这样完全符合双亲委派模型

自定义ClassLoader

一般不会直接重写整个loadClass()方法,只会重写findClass()方法

1. 自定义一个Test.java文件,存放在D:\lib

package com.frank.test;
public class Test {
	public void say(){
		System.out.println("Say Hello");
	}
}

2. 重写findClass()方法

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class DiskClassLoader extends ClassLoader {
	
	private String mLibPath;
	
	public DiskClassLoader(String path) {
		// TODO Auto-generated constructor stub
		mLibPath = path;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		
		String fileName = getFileName(name);
		
		File file = new File(mLibPath,fileName);
		
		try {
			FileInputStream is = new FileInputStream(file);
			
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			int len = 0;
	        try {
	            while ((len = is.read()) != -1) {
	            	bos.write(len);
	            }
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	        
	        byte[] data = bos.toByteArray();
	        is.close();
	        bos.close();
	        
	        return defineClass(name,data,0,data.length);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return super.findClass(name);
	}

	//获取要加载 的class文件名
	private String getFileName(String name) {
		// TODO Auto-generated method stub
		int index = name.lastIndexOf('.');
		if(index == -1){ 
			return name+".class";
		}else{
			return name.substring(index+1)+".class";
		}
	}
	
}

3. 测试

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
	
		//创建自定义classloader对象。
		DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib");
		try {
			//加载class文件
			Class c = diskLoader.loadClass("com.frank.test.Test");
			
			if(c != null){
				try {
					Object obj = c.newInstance();
					Method method = c.getDeclaredMethod("say",null);
					//通过反射调用Test类的say方法
					method.invoke(obj, null);
				} catch (InstantiationException | IllegalAccessException 
						| NoSuchMethodException
						| SecurityException | 
						IllegalArgumentException | 
						InvocationTargetException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

在这里插入图片描述

JVM内存管理

既然看到这里了,那就把内存管理也看完吧
在这里插入图片描述

  1. 对象内存管理介绍
  • JVM为java程序提供并管理所需要的内存空间;
  • JVM内存分为“堆”、“方法区”、“栈”、“本地方法栈”、“程序计数器”,其中堆和方法区是线程共有的;栈、本地方法栈和程序计数器是线程私有的。
  • 这部分空间用于存储使用new关键字所创建的对象。
  • 访问对象需要依靠引用变量,当一个对象没有任何引用时,被视为废弃的对象,属于被回收的范围,该对象中所有成员变量也随之被回收。所以成员变量的生命周期为:从对象在堆中创建开始到对象从堆中被回收结束
    在这里插入图片描述
  • 这部分空间用于存储程序运行时在方法中声明的所有局部变量。例如:main()方法中有如下代码
  • 一个运行的Java程序从开始创建到结束会有多次方法的调用;JVM会为每一个方法的调用在栈中分配一个对应空间,这个空间称为该方法的栈帧;一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据,当某个方法调用完成后,其对应的栈帧将被清楚,局部变量失效。
    在这里插入图片描述
  1. 方法区
  • 存放类的所有信息(方法和变量)。java程序运行时,首先会通过类加载器载入类文件的字节码信息,经过解析后将其装入方法区。
  • 当类的信息被加载到方法区时,除了类的类型信息外,同时类内的方法定义也被加载到方法区,类在实例化对象时,多个对象会拥有各自在堆中的空间,但所有实例对象是共用在方法区中一份方法定义的,同时也是同用一个方法区的Class。
    在这里插入图片描述
  1. 本地方法栈
  • 本地方法栈与虚拟机的作用相似,不同之处在于虚拟机栈为虚拟机执行的Java方法服务,而本地方法栈则为虚拟机使用到的Native方法。有的虚拟机直接把本地方法栈和虚拟机栈合二为一。
  • 会抛出stackOverflowError和OutOfMemoryError异常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多芯片设计 Designing For Multiple Die

Why a system-level approach is essential, and why its so challenging作者&#xff1a;Ann MutschlerAnn Mutschler is executive editor at Semiconductor Engineering.将多个裸片或芯粒集成到一个封装中&#xff0c;与将它们放在同一硅片上有着很大的区别。在同一硅片上&a…

断点续传实现

断点续传 1、 什么是断点续传 通常视频文件都比较大&#xff0c;所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大小没有限制&#xff0c;但是客户的网络环境质量、电脑硬件环境等参差不齐&#xff0c;如果一个大文件快上传完了网断了没有上…

分布式之ZAB协议

写在前面 假定我们现在使用zk执行了如下的指令&#xff1a; [zk: 192.168.0.10:2181(CONNECTED) 0] create /dongshidaddy 123 Created /dongshidaddy [zk: 192.168.0.10:2181(CONNECTED) 1] create /dongshidaddy/mongo 456 Created /dongshidaddy/mongo假定因为节点故障最终…

Python曲线肘部点检测-膝部点自动检测

文章目录一. 术语解释二. 拐点检测肘部法则是经常使用的法则。很多时候&#xff0c;可以凭人工经验去找最优拐点&#xff0c;但有时需要自动寻找拐点。最近解决了一下这个问题&#xff0c;希望对各位有用。一. 术语解释 **肘形曲线(elbow curve)**类似人胳膊状的曲线&#xff…

Echarts 每个柱子一种渐变色的象形柱状图

第023个点击查看专栏目录本示例是解决每个柱状图的每一个柱子都呈现一种渐变色&#xff0c;每个柱子的颜色都不同。这里同时采用了象形的柱状图效果。 文章目录示例效果示例源代码&#xff08;共125行&#xff09;相关资料参考专栏介绍示例效果 示例源代码&#xff08;共125行&…

JavaScript DOM【快速掌握知识点】

目录 DOM简介 获取元素 修改元素 添加和移除元素 事件处理 DOM简介 JavaScript DOM 是指 JavaScript 中的文档对象模型&#xff08;Document Object Model&#xff09;&#xff1b;它允许 JavaScript 与 HTML 页面交互&#xff0c;使开发者可以通过编程方式动态地修改网页…

RocketMQ源码分析

RocketMQ源码深入剖析 1 RocketMQ介绍 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术&#xff0c;自主研发的云正式商用的专业消息中间件&#xff0c;既可为分布式应用系统提供异步解耦和削峰填谷的能力&#xff0c;同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠…

汽轮机胀差及轴向位移(转载的)

这个文章是微信公众号推送看到的。搬运到这里方便以后学习用。 1、轴向位移和胀差的概念 轴位移指的是轴的位移量&#xff0c;而胀差则指的是轴相对于汽缸的相对膨胀量,一般轴向位移变化时其数值较小。轴向位移为正值时&#xff0c;大轴向发电机方向移&#xff0c;若此时汽缸膨…

如何快速了解一个系统?

前言 开发人员经常会面临下面一些场景&#xff1a; 新人入职&#xff0c;需要学习已有系统&#xff0c;作为 landing 的一部分&#xff0c;如何学习&#xff1f;被拉过去参与一个陌生系统的迭代开发或者系统维护&#xff08;bugfix&#xff09;&#xff0c;如何快速上手&…

关键词聚类和凸现分析-实战1——亚急性甲状腺炎的

审稿人问题第8页第26行-请指出#是什么意思&#xff0c;并解释为什么亚急性甲状腺炎在这里被列为#8。我认为在搜索亚急性甲状腺炎相关文章时&#xff0c;关键词共现分析应该提供关键词共现的数据。这些结果的实际用途是什么?亚急性甲状腺炎是一种较为罕见但重要的甲状腺疾病&am…

vue + qiankun 项目搭建

一、cli3构建vue2项目 1、前期工作&#xff1a;查看cli安装情况与安装 npm install -g vue/cli 已安装情况查看&#xff1a;vue -V(大写的V) 2、新建项目 vue create main-project 3、选择自定义配置 配置选择 选择vue版本、babel、router、vuex、css预处理器、lint格式校…

【神经网络】GRU

1.什么是GRU GRU&#xff08;Gate Recurrent Unit&#xff09;门控循环单元&#xff0c;是循环神经网络&#xff08;RNN&#xff09;的变种种&#xff0c;与LSTM类似通过门控单元解决RNN中不能长期记忆和反向传播中的梯度等问题。与LSTM相比&#xff0c;GRU内部的网络架构较为简…

Android 实现菜单拖拽排序

效果图简介本文主角是ItemTouchHelper。它是RecyclerView对于item交互处理的一个「辅助类」&#xff0c;主要用于拖拽以及滑动处理。以接口实现的方式&#xff0c;达到配置简单、逻辑解耦、职责分明的效果&#xff0c;并且支持所有的布局方式。功能拆解功能实现4.1、实现接口自…

【员工管理系统】

员工管理系统前言需求分析系统设计系统框图所需技术系统实现编写代码测试前言 这是一个使用epoll实现TCP并发服务器&#xff0c;并让客户端登录服务器可以进行员工的管理&#xff0c;员工的信息存储在sqlite数据库中&#xff0c;对数据库进行增删改查实现对员工的添加&#xf…

一文理解服务端渲染SSR的原理,附实战基于vite和webpack打造React和Vue的SSR开发环境

SSR和CSR 首先&#xff0c;我们先要了解什么是SSR和CSR&#xff0c;SSR是服务端渲染&#xff0c;CSR是客户端渲染&#xff0c;服务端渲染是指 HTTP 服务器直接根据用户的请求&#xff0c;获取数据&#xff0c;生成完整的 HTML 页面返回给客户端&#xff08;浏览器&#xff09;展…

嵌入式 STM32 通讯协议--MODBUS

目录 一、自定义通信协议 1、协议介绍 2、网络协议 3、自定义的通信协议 二、MODBUS通信协议 1、概述 2、MODBUS帧结构 协议描述 3、MODBUS数据模型 4、MODBUS事务处理的定义 5、MODBUS功能码 6、功能码定义 7、MODBUS数据链路层 8、MODBUS地址规则 9、MO…

SpringBoot 2.x ——使用 mail 实现邮件发送

文章目录前言环境、版本等pom依赖引入springboot项目配置文件获取邮箱授权码配置properties文件定义接口信息接收类编写邮件发送服务类编写接口swagger测试1、简单邮件发送2、html格式发送(支持附件)前言 最近再看xxl-job的源码&#xff0c;其中在邮件告警通知中使用到了告警信…

Go调用dll 解决方案 dll查看工具

准备工作 Go需要1.10版本&#xff0c;即支持动态链接库 基本调用代码 lib : syscall.NewLazyDLL("lib/plugin.dll") // 读取dll f : lib.NewProc("Sum") // 调用dll函数 res, _, _ : f.Call(param) // 传值 fmt.Println(res)可能出现的问题 %1 is not a …

移动硬盘不显示怎么办?恢复硬盘的方法汇总

在日常工作和生活中&#xff0c;移动硬盘是非常重要的存储设备&#xff0c;它们可以储存大量的数据&#xff0c;比如照片、音乐、视频、文档等。但是&#xff0c;有时候你可能会遇到移动硬盘不显示的问题。这个问题通常会让人感到困惑&#xff0c;因为你无法访问移动硬盘里的数…

Appium自动化测试之启动时跳过初始化设置

Appium每次启动时都会检查和安装Appium Settings&#xff0c;这是完全没有必要的&#xff0c;在首次使用Appium连接设备是Appium Settings便已经安装好。怎样跳过安装Appium Settings呢&#xff1f;之前的做法是修改appium中的源文件中的android-helpers.js实现&#xff0c;如M…