JVM课程学习笔记

JVM面试前突击笔记

b站网课地址:https://www.bilibili.com/video/BV1iJ411d7jS/

jvm探究

  • 请你谈谈你对JVM的理解?java8虚拟机和之前的变化更新?
  • 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?知道吗?
  • 谈谈JVM中,类加载器你的认识?

JVM位置

JVM是运行在操作系统上,JVM是用c语言写的

img

JVM体系结构

img

注意:栈一定不会有垃圾。垃圾回收指的是堆内存的垃圾回收

img

调优:一般是在调方法区和堆,尤其是堆

img

类加载器

作用:加载class文件 ~~

getClass:获取类对象

getClassLoader:获取类加载器对象

类加载的过程

img

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkaDMAak-1690952994064)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230722222536116.png)]

img

有哪些类加载器:

  1. 引导类加载器(BootstrapClassloader):用C++编写,是JVM自带的类加载器;负责加载Java的核心类库。(该加载器无法直接获取)
  2. 扩展类加载器(ExtClassloader):负责加载/jre/lib/ext目录下的jar包。
  3. 应用程序类加载器(AppClassloader):负责加载java -classpath或-D java.class.path所指的目录下的类与jar包。(最常用的加载器)

双亲委派机制

  1. 类加载器收到类加载的请求。

  2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器。

  3. 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常【class Not Found】,通知子加载器进行加载。

  4. 重复步骤3。

    img

    优点:

    1. 避免重复加载:当多个类加载器并发加载同一个类时,通过双亲委派机制可以避免重复加载,提高了类加载的效率。
    2. 确保类的一致性:通过双亲委派机制,可以保证Java类库中的类在不同的环境中都能得到一致的表现,避免了一些因为不同类加载器加载同一个类而导致的兼容性问题。
    3. 提高安全性:通过双亲委派机制,可以确保核心的Java类只由启动类加载器来加载,防止恶意代码替换核心类库,增加了Java程序的安全性。

沙箱安全机制(了解)

Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

**沙箱主要限制系统资源访问。**那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

Native

native关键字:凡是带了native关键字的方法,说明java的作用范围达不到了,它会去调用底层c语言的库。会进入本地方法栈------>调用本地方法接口 JNI【JNI的作用:扩展Java的使用,融合不同编程语言为Java所用】

Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序,
它在内存区域中专门开辟了一块标记区域: Native Method Stack,登记native方法。在最终执行的时候,加载本地方法库中的方法通过JNI

一般native用来操作硬件层面,如java程序驱动打印机、管理系统,在企业级应用中较为少见。

PC寄存器

程序计数器:Program Counter Register

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

方法区

Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说。所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关 static final Class 常量池

创建对象的内存分析----------)自己画一个

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ChWrixOU-1690952994064)(https://ts1.cn.mm.bing.net/th/id/R-C.33cb419457e0d34ee0dfcfb06de0c357?rik=mrPeaXU74ImtVQ&riu=http%3a%2f%2fwww.hongyanliren.com%2fwp-content%2fuploads%2f2013%2f10%2f5.jpg&ehk=HOArnRYddwxhPMDJ1enXus5w4H%2bf%2b6pl6EMjfq9wgpg%3d&risl=&pid=ImgRaw&r=0)]

pc寄存器

程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

作用:栈内存主管程序的运行,生命周期和线程同步;

线程结束,栈内存释放,对栈来说,不存在垃圾回收问题。一旦线程结束,栈就Over。

存放:8大基本类型+对象引用+实例方法

栈运行原理:栈帧

详细结构图:

img

栈+堆+方法区 交互关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WEDPNkwe-1690952994064)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230723180354552.png)]

三种JVM (java -version查看)

Sun 公司 -------------HotSpot

BEA公司 -------------JRockit

IBM公司 --------------JIT

一个jvm只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取了类文件后,一般把什么东西放到堆中? 类、方法、常量、变量 ~ 保存我们引用类型的真实对象。

堆中还要细分为三个区域:

​ 新生区:新生区又叫做伊甸园区,包括:伊甸园区、幸存0区、幸存1区

​ 养老区

​ 永久区:这个区域是常驻内存的。
​ 1)用来存放JDK自身携带的Class对象、Interface元数据,存储的是Java运行时的一些环境或类信息~。
​ 2)这个区域不存在垃圾回收
​ 3)关闭JVM虚拟机就会释放这个区域的内存。

什么情况下,在永久区就崩了?

  • 一个启动类,加载了大量的第三方jar包。
  • Tomcat部署了太多的应用。
  • 大量动态生成的反射类;不断的被加载,直到内存满,就会出现OOM

img

永久代和元空间

方法区是一种规范,不同的虚拟机厂商可以基于规范做出不同的实现,永久代和元空间就是出于不同jdk版本的实现。
方法区就像是一个接口,永久代与元空间分别是两个不同的实现类。
只不过永久代是这个接口最初的实现类,后来这个接口一直进行变更,直到最后彻底废弃这个实现类,由新实现类—元空间进行替代。

jdk1.8之后:在堆内存中,逻辑上存在,物理上不存在(元空间使用的是本地内存)

img

GC垃圾回收主要是在伊甸园区和养老区。假设内存满了,报OOM,堆内存不够 。在jdk8之后,永久存储区改了了个名字(元空间)

OOM:

1.尝试扩大堆内存看结果

2.分析内存,看一下那个地方出现了问题(专业工具)

​ 堆内存调优

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-at6cQDw0-1690952994065)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230724210207483.png)]

使用JProfiler工具分析OOM原因

public class Test {
    public static void main(String[] args) {
        // 获取虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        // 获取jvm初始化总内存
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("max="+max+"字节\t"+(max/(double)1021/1024)+"MB\t"+(max/(double)1021/1024/1024)+"GB");
        System.out.println("total="+total+"字节\t"+(total/(double)1024/1024)+"MB\t"+(total/(double)1021/1024/1024)+"GB");
        // 默认情况下:分配的最大内存是电脑内存的1/4;初始化的内存是电脑内存的1/64
        // 分析OOM:
        //          1.尝试扩大堆内存,看结果
        //          2.分析内存,看一下哪个地方出现了问题(专业工具)JProfiler
        // -Xms1024m -Xmx1024m -XX:+PrintGCDetails
        // 305664K+699392K = 1005056K   981.5M
    }
}

元空间:逻辑上存在,物理上不存在

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cpZHB97-1690952994065)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230728114900656.png)]

常量池:

  1. 在jdk1.7之前,运行时常量池+字符串常量池是存放在方法区中,HotSpot VM对方法区的实现称为永久代。
    img
  2. 在jdk1.7中,字符串常量池从方法区移到堆中,运行时常量池保留在方法区中。
    img
  3. jdk1.8之后,HotSpot移除永久代,使用元空间代替;此时字符串常量池保留在堆中,运行时常量池保留在方法区中,只是实现不一样了,JVM内存变成了直接内存。
    img

GC 垃圾回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4uhT4Vbf-1690952994066)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230728141555596.png)]

JVM在进行GC时,并不是对着三个区域统一回收,大部分时候回收都是新生代。

GC两种类型:轻GC(普通的GC,大部分时候子啊新生代区域,偶尔在幸存区),重GC(全局的GC)

  • 新生代
  • 幸存区(from,to)
  • 老年区

GC题目:

JVM的内存模型和分区~详细到每个区放什么?

堆里面的分区有哪些? Eden,form, to,老年区,说说他们的特点 !

GC的算法有哪些? 标记清除法,标记整理,复制算法,引用计数器

轻GC和重GC分别在什么时候发生?

GC算法

引用计数法

img

对象用了几次就+几,每个对象分配一个计数器。

复制算法

每次GC都会将Eden活的对象移到幸存区中【from中的复制到to区】,一旦Eden区被GC后,就会是空的!
谁空谁是to【to区永远是干净的区】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1pL1HzV-1690952994066)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230728153426738.png)]

img

好处:没有内存的碎片

坏处:浪费了内存空间(多了一半空间to永远是空)。假设对象100%存活(极端情况),不适合使用复制算法。

复制算法最佳使用场景:新生区

标记清除算法

扫描这些对象,对活着的对象进行标记,把没有标记的对象进行清除 。压缩,防止内存碎片产生,再次扫描,向一端移动存活的对象。多了一个移动成本。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGfNJctT-1690952994066)(C:\Users\19207\AppData\Roaming\Typora\typora-user-images\image-20230728155213588.png)]

优点:不需要额外的空间

缺点:两次扫描,严重浪费空间,会产生内存碎片

标记清除压缩

img

总结:

内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:复制算法=标记压缩算法>标记清除算法

内存利用率:标记压缩算法=标记清除算法>复制算法

GC:分代收集算法

年轻代:存活率低 复制算法

老年代:区域大 存活率高 标记清除+标记压缩 混合实现

JMM内存模型(Java Memory Model )

作用:缓存一致性的协议,用来定义数据读写的规则。

JMM定义了线程工作内存和主内存的抽象关系:线程的共享变量存储在主内存中,每个线程都有一个私有的本地工作内存。

使用volatile关键字来解决共享变量的可见性的问题。

Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的。

JMM的操作

img

JMM定义了8种操作来完成(每一种操作都是原子性的、不可再拆分的)

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎(每当虚拟机遇到一个需要使用到该变量的值的字节码指令时将会执行这个操作)。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量(每当虚拟机遇到一个给该变量赋值的字节码指令时执行这个操作)。
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

JMM定义的规则

8种操作必须满足的规则

  • 不允许read和load、store和write操作之一单独出现。(不允许一个变量从主内存读取了但工作内存不接受;或者从工作内存发起回写了但主内存不接受的情况出现)
  • 不允许一个线程丢弃它的最近的assign操作。(变量在工作内存中改变了值之后,必须把该变化同步回主内存)
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。(就是对一个变量实施use、store操作之前,必须先执行过了load和assign操作)
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。

并发编程的三大特性

1、原子性:一个或多个程序指令,要么全部正确执行完毕不能被打断,或者全部不执行

2、可见性:当一个线程修改了某个共享变量的值,其它线程应当能够立即看到修改后的值。

3、有序性:程序执行代码指令的顺序应当保证按照程序指定的顺序执行,即便是编译优化,也应当保证程序源语一致

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值