JVM学习笔记

JVM通过加载不同操作系统的特定实现来实现跨平台,字节码作为中间表示,允许不同系统执行同一代码。类加载子系统包括加载、验证、准备、解析和初始化。双亲委派机制确保类加载一致性。Tomcat自定义类加载器以隔离应用程序。运行时数据区包括程序计数器、虚拟机栈、本地方法栈和堆。垃圾回收用于管理内存,包括标记-清除、复制、标记整理等算法,以及CMS和G1等垃圾收集器。
摘要由CSDN通过智能技术生成

JVM跨平台原理

不同操作系统上运行的JVM是不一样的,这才是JVM跨平台性的本质!

不同的操作系统都可以执行通一份字节码文件,字节码是什么呢?

就是把java文件翻译成了另一个格式,为什么要编译成class再给jvm呢,因为这样就让jvm当解释器,解释型语言了,运行起来就比较慢。如果我们提前已经编译好了,jvm运行起来就会非常快

如上图,别的语言也是可以编译成字节码给jvm运行的

JVM结构

1、类加载子系统

会把class文件加载到内存中的方法区中,然后验证(不可能随便一个方法区都处理他),准备阶段(为static属性分配一个0值,后面初始化才会赋值),解析(将类的名字解析为方法区对应的地址,那个class对象的地址),最后初始化阶段(给类里面static属性赋值) 

类加载器分类

双亲委派机制(重点)

双亲委派机制(Parent Delegation)是Java中的一种类加载机制。在Java中,类加载器负责将类加载到Java虚拟机中,而双亲委派机制就是一种类加载器之间的协作方式,通过这种方式可以保证Java程序中的类加载的顺序和一致性。

根据双亲委派机制,当一个类加载器需要加载一个类时,它首先会将这个任务委托给它的父类加载器去完成,如果父类加载器还存在父类加载器,那么它会继续委托给它的父类加载器,直到委托到最顶层的启动类加载器为止,如果启动类加载器无法加载这个类,那么这个任务会回到子类加载器中去,由子类加载器自己去加载这个类。

这种机制可以保证Java程序中的类的一致性,也可以避免类的重复加载,提高了类加载的效率。同时,通过双亲委派机制,也可以实现对Java程序中的类的访问权限的控制,防止恶意类的加载和使用

tomcat为什么要自定义类加载器

主要原因就是防止隔离,因为如果用jvm默认的类加载器,那么加载a应用的class和b应用class如果同名就不会再加载了,因为判断是类名+类加载器实例,名字相同所有相同,所有我们要对a和b设置单独的类加载器,webappclassloader。 

2、运行时数据区

蓝色是多个线程共享的,绿色是线程私有的

程序计数器

记录下一条指令的地址

程序控制流的指示器,循环、if else、异常处理、线程恢复都依赖它来完成

解释器工作是就是通过他来获取下一跳执行的字节码指令得到

唯一一个不会发生内存溢出的区域,因为只会记录一个数据

虚拟机栈(java栈、java方法栈)

线程私有的,因为执行完就出栈,所以虚拟机栈不需要垃圾回收

线程太多 和 单个栈帧态度都都会发送异常的,可以通过参数来设置虚拟机栈的大小的

栈帧

一个栈帧其实就是对应一个方法,方法里面就会定义局部变量,右边这个slot就是一个个局部变量

操作数栈是用来执行字节码指令过程中用来计算的(就是辅助计算的一个栈,比如a=10,b=10,算a+b,先10进操作数栈然后进局部变量表a=10,然后b也进操作栈然后进局部表,然后从局部变量表中取出a和b进操作栈计算结果)

本地方法栈

跟java的虚拟机栈是差不多的,只是方法是本地方法

jvm规范中规定所有对象和数组都应该放在堆中,在执行字节码就会创建对象放入堆中,对象对应的引用地址放入虚拟机栈的栈帧,不过当方法执行完之后,刚刚所有创建的对象不会立马回收,而是等jvm后台执行GC才回收

新生代和老年代默认比是1:2,新生代占三分之一,可以参数调整

默认下伊甸园和s0和s1的比是8:1:1,可以参数调整

假如来了个非常大的对象,伊甸园存活下来,这个对象要超过了s0和s1,那么将直接进入老年代

如图假如是这么个非常大对象,连伊甸园都进不了,直接进入老年代

垃圾回收

1、为什么要垃圾回收

垃圾是指JVM没有任何应用指向的对象,如果不清理这些垃圾对象,那么他们就一直占内存,而不能给其他对象使用,最终垃圾对象越来越多,就OOM

2、垃圾标记阶段(找垃圾)

JVM(主要堆中)有哪些垃圾对象,有引用技术法可达性分析法

引用技术法

可达性分析法

GC Roots是一组引用,包括:

3、垃圾回收算法

标记-清除算法

效率不高,而且会产生内存碎片

复制算法

因为复制算法要复制,所以垃圾越多越好,留下来的越少,移动的就越少,这样效率越高,所以很适合新生代,这个地方垃圾很多,所以s0和s1是不断交换的

标记整理算法

 效率是非常低的,但是没有内存碎片

对比总结

4、垃圾收集器

CMS垃圾回收器

并发标记清楚垃圾回收期,特点是低暂停

地暂停就是让STW的时间变短,而且在垃圾回收过程中大部分时间用户线程还在执行,用户体验非常好,但是整个垃圾回收过程长了,吞吐量也更低(单位时间内执行用户线程更少)

初始阶段: 先STW一下,暂停所有工作线程,然后标记GC Root能直接可达的对象(找第一层就ok,所以STW非常快)一旦标记完恢复所有线程工作

并发标记:从上个阶段标记出对象,开始遍历整个老年代,标记出所有可达对象,耗时比较长,但不用STW,用户一起执行,三色标记

重新标记:解决上一阶段产生的误差,需要修正,需要STW但是不长

并发清理:删除垃圾对象,由于不需要移动,这个阶段也可以和用户一起执行,不需STW

G1垃圾回收器

从原本物理连续的新生老年代变成了逻辑上的新生老年代,全部都放一起了,然后大对象超过一个region50%就用humongous区

橙色代表垃圾回收线程,满橙色代码STW,前面两个STW阶段耗时都是非常少的,最后一个是自己设置手动设置能接受的实际来回收

扩展

当前的类加载器找不到需要加载的类怎么办

  1. 检查类路径:类路径指定了JVM在哪里搜索类,如果类路径没有正确设置,JVM就无法找到需要加载的类。可以通过命令行或IDE配置正确的类路径。

  2. 检查类名:如果类名拼写错误,或者包名和目录结构不匹配,JVM也无法找到需要加载的类。可以检查类名是否拼写正确,以及类名是否和文件名一致,是否在正确的包中。

  3. 检查依赖项:如果需要加载的类依赖其他类,而这些依赖项没有被正确加载,JVM也会找不到需要加载的类。可以检查类依赖项是否已经正确加载,并且是否存在冲突。

  4. 检查类加载器:如果当前类加载器找不到需要加载的类,可以考虑使用其他类加载器。例如,可以使用父类加载器或扩展类加载器来加载需要的类。

  5. 检查权限:如果当前类加载器没有足够的权限加载需要的类,JVM也会找不到需要加载的类。可以检查类加载器是否有足够的权限,以及是否需要增加相应的权限。

综上所述,当当前的类加载器找不到需要加载的类时,可以从类路径、类名、依赖项、类加载器和权限等多个方面入手,以找到解决问题的方案。

如何打破双亲委派机制

在Java中,类加载器采用双亲委派模型,不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。

但是有时候,我们需要打破双亲委派机制,例如在某些应用场景下,需要加载同名但不同版本的类,或者需要动态修改已加载的类等。为了打破双亲委派机制,可以使用以下两种方式:

  1. 自定义类加载器:可以继承ClassLoader类,重写loadClass方法,在该方法中实现自定义的加载逻辑。在加载类时,可以先通过自定义的类加载器加载类,如果找不到,则使用原有的双亲委派机制去加载。这样可以达到打破双亲委派机制的目的。

  2. 使用线程上下文类加载器:在Java 2之后,引入了线程上下文类加载器的概念。在加载类时,可以使用当前线程的上下文类加载器去加载类,如果找不到,则使用原有的双亲委派机制去加载。这样可以实现类加载器的灵活切换,达到打破双亲委派机制的目的。

需要注意的是,打破双亲委派机制可能会引起类加载器的混乱和冲突,可能会导致类的版本不一致,从而引发各种问题。因此,打破双亲委派机制应该谨慎使用,只在必要的情况下使用,并且需要做好相关的测试和风险评估。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卒获有所闻

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

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

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

打赏作者

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

抵扣说明:

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

余额充值