快捷搜索:  汽车  科技

java jvm调优总结:带你手撸万元java进阶课程

java jvm调优总结:带你手撸万元java进阶课程Java byteCode由单字节(byte)指令构成,理论上最多支持256个操作码(opcode)。实际上java只使用了200左右的操作码,其他留给了调试操作。结构:java是一种面向对象、静态类型、编译执行,有VM(虚拟机)/GC和运行时、跨平台的高级语言。重点:VM(虚拟机)/GC(Garbage Collector)和运行时、跨平台。跨平台步骤:字节码文件被虚拟机加载(类加载器)加载到内存中,转换成具体的对象字节码

编程语言

演化:

机器语言->编程语言->高级语言(JAVA,c Go Rust等)

面向过程--面向对象-面向函数

java是一种面向对象、静态类型、编译执行,有VM(虚拟机)/GC和运行时、跨平台的高级语言。重点:VM(虚拟机)/GC(Garbage Collector)和运行时、跨平台。

java jvm调优总结:带你手撸万元java进阶课程(1)

跨平台步骤:字节码文件被虚拟机加载(类加载器)加载到内存中,转换成具体的对象

java jvm调优总结:带你手撸万元java进阶课程(2)

字节码

结构:

Java byteCode由单字节(byte)指令构成,理论上最多支持256个操作码(opcode)。实际上java只使用了200左右的操作码,其他留给了调试操作。

根据指令的性质大概分为四大类:

1.栈操作指令,包括与局部变量交互的指令,

2.程序流程指令,

3.对象操作指令,比如方法调用的指令,

4.算数运算以及类型转换的指令,

运行步骤:

java jvm调优总结:带你手撸万元java进阶课程(3)

JVM是一个基于栈的计算机,每个线程都有独属于自己的线程栈(JVM Stack),用语存储栈帧。每次调用方法就会自动创建一个线程栈。栈帧是由操作数栈、局部变量表以及一个class引用组成,class引用中又包含着我们使用的常量池

操作demo:https://juejin.cn/post/7141206840456511496/

类加载器

java jvm调优总结:带你手撸万元java进阶课程(4)

类生命周期的七个步骤:

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,静态字段和方法

三类加载器和特点:

java jvm调优总结:带你手撸万元java进阶课程(5)

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 ()方法及其获取字节码流的方式,这样会让自定义类加载器编写更为简单一些。

java jvm调优总结:带你手撸万元java进阶课程(6)

双亲委派

双亲委派机制的原理:

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。

如果父类的加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终达到顶层的启动类加载器。

如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制。

优点:

避免类的重复加载,确保一个类的全局唯一性

保护程序安全,防止核心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的相关知识,小伙伴可以先复习下,然后查漏补缺。

创作不易,如果这篇文章对你有用,请点个赞谢谢♪(・ω・)ノ!

猜您喜欢: