编程语言
演化:
机器语言->编程语言->高级语言(java,c++,Go,Rust等)
面向过程--面向对象-面向函数
java是一种面向对象、静态类型、编译执行,有VM(虚拟机)/GC和运行时、跨平台的高级语言。重点:VM(虚拟机)/GC(Garbage Collector)和运行时、跨平台。
跨平台步骤:字节码文件被虚拟机加载(类加载器)加载到内存中,转换成具体的对象
字节码
结构:
Java byteCode由单字节(byte)指令构成,理论上最多支持256个操作码(opcode)。实际上java只使用了200左右的操作码,其他留给了调试操作。
根据指令的性质大概分为四大类:
1.栈操作指令,包括与局部变量交互的指令,
2.程序流程指令,
3.对象操作指令,比如方法调用的指令,
4.算数运算以及类型转换的指令,
运行步骤:
JVM是一个基于栈的计算机,每个线程都有独属于自己的线程栈(JVM Stack),用语存储栈帧。每次调用方法就会自动创建一个线程栈。栈帧是由操作数栈、局部变量表以及一个class引用组成,class引用中又包含着我们使用的常量池
操作demo:https://juejin.cn/post/7141206840456511496/
类加载器
类生命周期的七个步骤:
1.加载:找到class文件;
2.验证:验证字节码文件格式是否正确、依赖是否完备;
3.准备:静态字段、方法表;
4.解析:符合解析为引用;
5.初始化:构造器,静态变量赋值,静态代码块;
6.使用
7.卸载
前五步是我们通常所说的类加载过程,其中2、3、4可以合在一起称为-链接:
1 找到class文件,读出来
2 验证格式,解析字段方法,所有符号转化为实际引用
3 类相关初始化
类的加载时机:
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
1.3.1 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
1.3.2 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
1.3.3当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
1.3.4 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
1.3.5 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
通过子类引用父类的静态字段,不会导致子类初始化。
通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
总结:显式,隐式
隐式,子类父类,实现类和接口,反射,动态调用
显式,main方法,new,静态字段和方法
三类加载器和特点:
1.启动类加载器(BootstrapClass Loader)
这个类加载使用C/C++语言实现,嵌套在JVM内部
它用来加载JAVA的核心库(JAVA_HOME/jre/lib/rt.jar,resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
并不继承自Java.lang.ClassLoader,没有父加载器
加载扩展类和应用程序类加载器,并指定为它们的父类加载器
出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类
启动类加载器不像其他类加载器有实体,它是没有实体的,JVM将C++处理类加载的一套逻辑定义为启动类加载器。因此,启动类加载器是无法被Java程序调用的。
2.扩展类加载器(Extension Class Loader)
java语言编写,由sun.misc.Launcher$ExtClassLoader实现
派生于ClassLoader类
父类加载器为启动类加载器
从Java.ext.dirs系统属性所指的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
URL[] urls = urlClassLoader.getURLs();
for (URL url : urls) {
System.out.println(url);
}
}
3.应用程序加载器(系统类加载器,System Class Loader/App Class Loader)
java语言编写,由sun.misc.Launcher&AppClassLoader实现
派生于ClassLoader类
父类加载器为扩展类加载器
它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
通过ClassLoader#getSystemClassLoader()方法可以获得到该类加载器
public static void main(String[] args) {
String[] urls = System.getProperty("java.class.path").split(":");
for (String url : urls) {
System.out.println(url);
}
System.out.println("================================");
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URL[] urls1 = classLoader.getURLs();
for (URL url : urls1) {
System.out.println(url);
}
}
4.用户自定义类加载器
在Java的日常应用程序开发中,类加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。
1、开发人员可以通过继承抽象类java.lang.classLoader类的方式,实现自己的类加载器,以满足一些特殊的需求
2、在JDK2.0之前,在自定义类加载器时,总会去继承classLoader类并重写loadclass ()方法,从而实现自定义的类加载类,但是在JDK2.0之后已不再建议用户去覆盖loadclass ()方法,而是建议把自定义的类加载逻辑写在findclass ()方法中
3、在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findclass ()方法及其获取字节码流的方式,这样会让自定义类加载器编写更为简单一些。
双亲委派
双亲委派机制的原理:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终达到顶层的启动类加载器。
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制。
优点:
避免类的重复加载,确保一个类的全局唯一性
保护程序安全,防止核心API被随意篡改
缺点:
无法做到不委派,无法做到向下委派
在某些场景下双亲委派制过于局限,所以有时候必须打破双亲委派机制来达到目的。例如:SPI机制,这个SPI机制涉及到打破双亲委派机制,工作中没有涉及到就不细说了,感兴趣的同学可以自己研究下。
双亲委派在JVM中的实现代码:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 首先,去检查类是否已经被加载
Class> c = findLoadedClass(name);
// 如果类还未被加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 获取父类加载器加载该类
if (parent != null) {
// this 是AppClassLoader, this.parent是ExtClassLoader
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) {
// 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 stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 判断类是否被解析
if (resolve) {
resolveClass(c);
}
return c;
}
}
这一期的课程大概就讲了这么多吧,说实话看完还是好多记不住和不理解,也是反复记忆并且查了好多资料才知道,所以不理解很正常,没有接触过就能一遍看懂的一般都是高级及以上了,慢慢看就可以了。看一点就是进步。
下期这周末写,大概是内存模型和JMM的相关知识,小伙伴可以先复习下,然后查漏补缺。
创作不易,如果这篇文章对你有用,请点个赞谢谢♪(・ω・)ノ!