jvm-狂神课程

news/2024/11/8 6:51:21/

一、JVM

JVM就是Java虚拟机,Java虚拟机就是JVM

1. JVM位置

6d6c0eefe96d780c0e37be2427ea91a0

  • 1、Java程序(跑的环境是在jvm(虚拟机)跑的,也可以说是在jre上跑的)java运行是需要在特定的环境的也就是这个jre这种。

  • 2、jvm(也就是jrejre包括了jvm):jvm是用c写的

  • 3、操作系统(也是个软件

  • 4、硬件体系(Intel,sapc)

2. JVM体系结构

2.1. jvm结构图

image

image

  • 1、java编译 - 命令javac

  • 2、编译生成Class File

  • 3、类装载器(类加载器Class Loader

  • 4、运行时数据区:(类加载完成后进入这个运行时数据区:Runtime Data Are)运行时异常是不可捕获的。这是在类加载器后的产物!运行时数据区里面就有(方法区(Method Area),java栈(stack),本地方法栈(Native Method Stack),堆(heap) ,程序计数器(pc寄存器))

    • 本地方法栈: 本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native 关键字修饰的方法所存储的区域。
    • 程序计数器:程序计数器也是线程私有的数据区,这部分区域用于存储线程的指令地址,用于判断线程的分支、循环、跳转、异常、线程切换和恢复等功能,这些都通过程序计数器来完成。
    • 方法区:方法区是各个线程共享的内存区域,它用于存储虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • :堆是线程共享的数据区,堆是 JVM 中最大的一块存储区域,所有的对象实例都会分配在堆上。JDK 1.7后,字符串常量池从永久代中剥离出来,存放在堆中。
    • 运行时常量池:运行时常量池又被称为 Runtime Constant Pool,这块区域是方法区的一部分,它的名字非常有意思,通常被称为 非堆。它并不要求常量一定只有在编译期才能产生,也就是并非编译期间将常量放在常量池中,运行期间也可以将新的常量放入常量池中,String 的 intern 方法就是一个典型的例子。
  • 5、本地方法接口(native)(和本地方法库相连),同时这一层还有执行引擎

2.2. jvm垃圾回收

垃圾回收,指的的堆内存的垃圾回收,垃圾回收机制简称GC

程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。

垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理

9d4f28349422c9714bbcb35604bc08ff

为什么java栈,本地方法栈,程序计数器是不会有垃圾回收的?

因为他们是栈,最终是要出栈的,若是上面的是一个垃圾阻塞了,那他就无法出栈了,Jvm调优也就是垃圾回收,调的就是方法区和堆,99%是调堆。

手动执行GC

System.gc(); // 手动回收垃圾

finalize方法作用

  • 1、finalize()方法是在每次执行GC操作之前时会调用的方法,可以用它做必要的清理工作。

  • 2、它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

public class Test {public static void main(String[] args) {Test test = new Test();test = null;System.gc(); // 手动回收垃圾}@Overrideprotected void finalize() throws Throwable {// gc回收垃圾之前调用System.out.println("gc回收垃圾之前调用的方法");}
}

2.3. jvm调优

505a192a9f4b519eec155d0cfd56b0ca

二、类加载器

类加载器: 负责把class文件加载到内存中

类加载机制

Java 虚拟机负责把描述类的数据从 Class 文件加载到系统内存中,并对类的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称之为 Java 的类加载机制。

1. 类加载的过程

一个类从被加载到虚拟机内存开始,到卸载出内存为止,一共会经历下面这些过程。

798bd696514621e2b5ce1605840e61f4

类加载机制一共有五个步骤,分别是加载、链接、初始化、使用和卸载阶段,这五个阶段的顺序是确定的。其中链接阶段会细分成三个阶段,分别是验证、准备、解析阶段,这三个阶段的顺序是不确定的,这三个阶段通常交互进行。解析阶段通常会在初始化之后再开始,这是为了支持 Java 语言的运行时绑定特性(也被称为动态绑定)。

1.1.加载

1、获取class文件加载成二进制字节流
2、把该文件的编码结构-->运行时的内存结构
3、在内存中生成该类的一个Class对象

1.2.链接

验证确保 Class 文件的字节流中的内容符合《Java虚拟机规范》中的要求
准备为类中的变量分配内存并设置其初始值
解析 相当于翻译的过程,Java虚拟机将常量池内的符号引用替换为直接引用的

1.3.初始化

类加载过程的最后一个步骤,在之前的阶段中,都是由 Java 虚拟机占主导作用,但是到了这一步,却把主动权移交给应用程序。

1.4.使用

初始化之后的代码由 JVM 来动态调用执行

1.5.卸载

当代表一个类的 Class 对象不再被引用,那么 Class 对象的生命周期就结束了,对应的在方法区中的数据也会被卸载。JVM 自带的类加载器装载的类,是不会卸载的,由用户自定义的类加载器加载的类是可以卸载的。

2.类加载器的分类

  • 虚拟机自带 的加载器

  • 启动类加载器 - null,获取不到,底层c++编写,向上委托到这里

  • 扩展类加载器 - ExtClassLoder

  • 应用程序类加载器 - AppClassLoder

3.双亲委派机制

  • 1、类加载器接收到一个加载请求时,他会委派给他的父加载器,实际上是去他父加载器的缓存中去查找是否有该类,如果有就加载返回,如果没有则继续委派给父类加载,直到顶层类加载器。
  • 2、如果顶层类加载器也没有加载该类,则会依次向下查找子加载器的加载路径,如果有就加载返回,如果都没有,则会抛出异常。

image

4.沙箱安全机制

如果我们要编写一个和核心类库全限定命一模一样的类,JDK为了保证核心代码的一个安全 阻止你的代码和全限定名相同 优点:保证原生JDK的安全,保证核心源代码 防止API被篡改,避免重复加载类

5.Native方法区

5.1. native

凡是使用了native关键字的,说明Java的作用范围已经达不到了,它会去调用底层的C语言的库。

  1. 进入本地方法栈。
  2. 调用本地方法接口。

5.2. 方法区

Method Area方法区(此区域属于共享区间,所有定义的方法的信息都保存在该区域)
方法区是被所有线程共享,所有字段、方法字节码、以及一些特殊方法(如构造函数,接口代码)也在此定义。

静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。

5.3. PC寄存器

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

6.java栈(虚拟机栈)

6.1. 栈的作用

栈是运行时的单位 程序如何运行 如何处理数据

栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就释放了,对于栈来说,不存在垃圾回收问题

6.2.栈帧

a.概念 每个线程都有自己的栈,栈中的数据以栈帧的格式存在,每个方法对应一个栈帧 b.存储内容 局部变量表每个方法的局部变量,数组结构,存放的形参 没有线程安全问题 因为数据是线程私有的操作数栈根据指令进行入栈 出栈 c.原理先进后出

6.3.栈存储的东西

8大基本类型、对象引用,实例的方法

6.4.五道面试题

1.举例栈溢出的情况

  • 1、函数中采用了很大的结构体,或者数组;
  • 2、有很深的函数调用,或者递归调用
  • 3、访问了非法的地址

通过-Xss1m调整栈空间

2.调整栈大小,就能保证不出现溢出吗?

不能

3.分配的栈内存越大越好吗?

不是

4.垃圾回收是否会涉及到虚拟机栈?

不会

5.方法中定义的局部变量是否线程安全?

是的

7.堆

堆内存的大小是可以调节的

7.1. 三种JVM

  1. Sun公司的HotSpot。(java -version查看)
  2. BEA的JRockit
  3. IBM的J9VM

7.2.堆的概述

一个JVM实例对应一个进程实例,一个JVM实例有一个运行时数据区(Runtime)

一个Runtime就有一个独立的方法区和堆

一个进程有多个线程,多个线程共享一个方法区和堆空间

一个线程拥有自己独立的程序计数器/本地方法栈/虚拟机栈

为了解决多个线程访问出现线程不安全问题–>TLAB(线程私有空间)

垃圾回收只会在堆(方法区)当中进行回收

7.3.堆内存中细分

image

主要区别在于jdk8以前是新生区、养老区、永久区。jdk8即以后使用元空间代替了永久区。

227ee50e966adf60e3b7e2088d9679dc

1.新生区

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

新生区:老年区=1:2

新生区=eden:from:to 【谁空谁是to】

创建对象在eden

2.永久区

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

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

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

3.老年区和元空间

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

jdk1.8之前:

694f8dbf7bb31c9833c915c137bc8285

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

796ca080b420c447170d30c6b7dc7351

4.常量池

在jdk1.7之前,运行时常量池+字符串常量池是存放在方法区中,HotSpot VM对方法区的实现称为永久代。

38f66c4b577cb78d826bea4f50a92e2f

在jdk1.7中,字符串常量池从方法区移到堆中,运行时常量池保留在方法区中。

881a3ef67d3040347e9c1c249237d6d0

jdk1.8之后,HotSpot移除永久代,使用元空间代替;此时字符串常量池保留在堆中,运行时常量池保留在方法区中,只是实现不一样了,JVM内存变成了直接内存。

e9892b982ef64b9edc2f013d8a068090

8.GC垃圾回收

8.1. 垃圾回收的区域

主要都是在方法区和堆中,且99%都是在堆中

8.2.引用计数法

每个对象在创建的时候,就给这个对象绑定一个计数器。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,计数器为0就代表该对象死亡,这时就应该对这个对象进行垃圾回收操作。

优点:引用计数算法的实现简单,判定效率高,但建议不要使用

缺点:术无法解决对象之间的循环引用问题

24e10a4b449aa453a938d0c25786e921

8.3.复制算法

该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。

这个算法与标记-整理算法的区别在于,该算法不是在同一个区域复制,而是将所有存活的对象复制到另一个区域内。

优点:在存活对象不多的情况下,性能高,能解决内存碎片和java垃圾回收算法之-标记清除 中导致的引用更新问题

缺点:会造成一部分的内存浪费。不过可以根据实际情况,将内存块大小比例适当调整;如果存活对象的数量比较大,复制算法的性能会变得很差

6c63c44718aba8c7ef44f536ec3d8145

7c45a21d5b193943ac2d08080e284e5a

8.4.标记清除算法

为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。
分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。

优点:解决循环引用的问题、必要时才回收(内存不足时)

缺点:1、回收时,应用需要挂起,也就是stop the world;2、标记和清除的效率不高,尤其是要扫描的对象比较多的时候;3、会造成内存碎片(会导致明明有内存空间,但是由于不连续,申请稍微大一些的对象无法做到)

b00a1294d10389dd6af9de3fb6a56bdf

8.5.标记压缩算法

在标记阶段,该算法也将所有对象标记为存活和死亡两种状态;不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。

优点:解决标记清除算法出现的内存碎片问题

缺点:压缩阶段,由于移动了可用对象,需要去更新引用

4b4022234c317444b03af09e036e5b54

8.6.GC算法总结

  • 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
  • 内存整齐度:复制算法=标记压缩算法>标记清除算法
  • 内存利用率:标记压缩算法=标记清除算法>复制算法

http://www.ppmy.cn/news/67946.html

相关文章

【C++】类和对象(中)---拷贝构造函数、赋值运算符重载

个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C之路】💌 本专栏旨在记录C的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长&…

路径规划算法:基于正余弦算法的路径规划算法- 附代码

路径规划算法:基于正余弦优化的路径规划算法- 附代码 文章目录 路径规划算法:基于正余弦优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要:本文主要介绍利用智能优化算法…

元宇宙虚拟展馆和VR的关系与区别

元宇宙是指一个数字化的虚拟世界,这个世界由虚拟现实、增强现实等多种技术构建而成。这个世界中,人们可以用虚拟身份在其中自由穿梭,享受到与现实世界不同的多样化和极致体验。虚拟现实(VR)则是一种技术手段&#xff0…

网络编程:TCP socket

文章目录 阅读前导 服务端定义日志框架成员属性服务端框架 初始化服务器创建套接字绑定开启监听 运行服务器netstat 工具获取连接和通信准备通信逻辑 单进程服务端函数(version1)telent 工具测试 多进程服务端(version2)创建子进程…

linux中shell脚本的执行过程

Shell脚本是一种在Linux和Unix操作系统中广泛使用的脚本语言,用于自动化和简化各种任务。Shell脚本通常以.sh扩展名保存,并且可以使用文本编辑器创建和修改。在本文中,我们将详细介绍Shell脚本的执行过程,包括Shell解释器的作用&a…

Flink从入门到精通之-10容错机制

Flink从入门到精通之-10容错机制 流式数据连续不断地到来,无休无止;所以流处理程序也是持续运行的,并没有一个明确的结束退出时间。机器运行程序,996 起来当然比人要容易得多,不过希望“永远运行”也是不切实际的。因…

Debian 12 “Bookworm” 的新特性和发布日期

导读Debian 12 即将发布。了解一下更多关于其新特性和发布日期的相关信息。 debian 12 Debian 即将发布系统代号为 “书虫” 的新版本。与 Debian 11 “Bullseye” 相比,有许多改进和新功能。 Debian 12 “Bookworm” 包含了超过 11200 个新软件包,软件…

java类加载机制

简述java类加载机制? 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初 始化,最终形成可以被 虚拟机直接使用的java类型。 描述一下JVM加载Class文件的原理机制 Java中的所有类,都需要由类加载器装…