• JVM调优


    🤡一、JVM&JRE&JDK的关系

    🥇二、类加载机制

    (一)类加载的过程

    (1)装载

    ①获取类的全限定类名,把class文件转为二进制流
    ②把二进制中类的概述信息转入方法区中,如:创建时间、版本
    ③将java.lang.Class对象存入堆中

    在这里插入图片描述

    (2)链接

    ①验证:验证被加载类的正确性:如文件的格式,元数据等
    ②准备:在方法区中为静态变量分配空间,并设置初始值
    ③解析:把类的符号引用转为直接引用

    (3)初始化

    为类的静态变量设置默认值,执行静态代码块

    (二)类加载器的分类

    启动类加载器:主要负责加载JAVA的一些核心类库,主要是位于/lib/rt.jar中
    拓展类加载器: 主要加载JAVA中的一些拓展类,位于/lib/ext中,是启动类加载器的子类
    应用类加载器:主要用于加载CLASSPATH路径下我们自己写的类是拓展类加载器的子类

    (三)双亲委派模型

    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给符类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回。若父类加载器不能完成加载任务,子加载器才会尝试自己去加载
    在这里插入图片描述

    如何打破双亲委派机制?

    自定义类加载器类,继承ClassLoader类,重写loadClass方法

    🥈三、JVM内存模型

    在这里插入图片描述

    (一)程序计数器

    程序计数器会记录当前线程要执行指令的内存地址,只占用一小部分内存区域,只记录一个地址,所以我们认为程序计数器是不会出现内存溢出问题的分区。

    (二)本地方法栈

    Java中有些代码的实现是依赖于其他非Java语言的,本地方法栈存储的是维护非Java语句执行过程中产生的数据,一般我们认为本地方法栈不会出现内存问题

    (三)虚拟机栈

    主要是用来存储基本数据类型,同时也存储对象类型的引用,引用地址占4个字节
    在这里插入图片描述

    (1)栈帧的结构

    在这里插入图片描述

    为什么a=a+1的效率低于a++的效率呢?

    a=a+1要在操作数栈中进行运算,而a++只需要在局部变量中运算即可

    局部变量:
    存放当前方法的局部变量,基本数据类型存值i,引用数据类型存堆内存地址
    操作数栈:
    对方法中的变量提供技术的区域
    常量数据的引用:
    常量数据会存放到方法区的常量池中,不管是基本数据类型还是引用数据类型都会存放常量池的地址
    方法返回值的地址:
    方法返回数据会存到计算机内存的寄存器中

    (2)栈溢出及调优

    在这里插入图片描述

    设置栈的深度
    在这里插入图片描述

    (四)方法区

    在java8之后,我们把方法区称为元空间,方法区在逻辑上属于堆的一部分,但在一些具体机制和堆有所区别,如:一些JVM的方法区是可以不进行垃圾回收的,关闭JVM时才会释放方法区内存,所以方法去还有一个别名为非堆
    方法区会存储类信息、静态变量、常量(jdk8之后不存放字符串常量)
    如果加载大量class文件,也会造成方法区内存溢出,如一个tomcat运行20~30个项目

    (五)堆内存

    (1)Java对象内存布局

    在这里插入图片描述
    对象头
    MarkWord:一系列标记位(哈希码,分代年龄、锁状态标记),在64位系统中占8位
    ClassPoint:对象对应的类信息的内存地址,在64位系统中占8个字节
    Length:数组对象特有,表示数组长度,占4字节
    实时数据
    包含了对象的所有成员变量,大小有各个变量类型决定
    封装填充
    为了保证对象的大小为8字节的整数倍

    (2)JVM内存溢出和垃圾回收机制

    ①为什么要进行垃圾回收:

    如果对象只创建不回收,会造成堆内存溢出异常

    ②为什么要进行堆内存分区:
    1. 提高搜索效率
    2. 垃圾回收后可以更好的利用内存空间,存放大对象
    3. 尽可能减少GC次数

    (3)JVM堆内存的划分

    在这里插入图片描述
    老年代:
    对象会优先分配到新生代内存中,每次GC后没有回收的对象年龄+1,年龄到15还没有被后手,对象会存放到老年代内存中,如果对象较大,超过新生代内存的一半,对象也会存放到老年代区域
    新生代:
    为了减少young垃圾回收后的空间碎片,新生代又分为了Eden区和两个Survivor区,且始终有一个Suvivor区保持闲置,对象会先存放到Eden区中,Eden区空间满了之后会进行young区的垃圾回收,之后将young区所有存活的对象复制到闲置的Suvivor区中,并且清空Eden区和正在使用Suvivor区

    (4)YoungGC和OldGC

    YoungGC
    新生代区域的垃圾回收称为YoungGC,也叫MinorGC,Eden区满后会触发YoungGC
    OldGC
    老年代区域的垃圾回收称之为OldGC,也叫MajorGC,OldGC非常浪费性能,所以我们的JVM调优要尽可能减少OldGC的次数,OldGC往往伴随着YoungGC

    (5)面试题

    1.Survivor区空间并不大,如果满了怎么办?
    (1)一般情况下GC回收95%的对象,且超过15次GC的对象会放到Old区,所以Survivor区不容易满
    (2)如果Suvivor区满了,会触发担保机制,提前将对象存入old区
    2.为什么需要Suvivor区?
    为了减少垃圾回收带来的空间碎片,空间碎片过多会频繁出发YoungGC
    3.为什需要俩个Suvivor区?
    为了减少Suvivor区的空间碎片,如果只存在一个Suvivor区,则对象向从Eden区进入到Suvivor区,区满进行GC后,可能会产生空间碎片,采用两个Suvivor区,两区可以交替回收整理,减少空间碎片

    ❤️四、使用VisualVM监听JAVA进程的内存模型

    (一)下载VisualVM

    VisualVM在Java8以及之前都是不用下载的,在下载的java路径下的bin中
    在这里插入图片描述
    版本在8之后的得自行安装
    下载链接:https://visualvm.github.io/

    (二)安装以及使用

    安装一路无脑下一步
    下面就是初始页面
    在这里插入图片描述

    (1)编写代码测试各个内存区域

    【一个简单的循环创建对象的方法】
    在这里插入图片描述

    (2)设置堆栈的起始和最终大小

    -Xms10M -Xmx10M
    
    • 1

    设置成这样子可以更加直观的看到,新生代和年老代的变化,
    在这里插入图片描述

    (3)最后运行页面

    在这里插入图片描述
    在这里插入图片描述

    (五)垃圾回收机制

    (一)如何判断一个对象是垃圾

    (1)引用计数法

    如果要操作对象,必须通过引用来进行,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了,这种方法实现简单,效率较高,但是它无法解决循环引用问题,因此在java中并没有采用这种方式(pyhon使用的是引用计数法)

    循环引用问题:是指创建了两个属于同一个类的对象,这两个对象之间相互引用,如下图object,当我的object1和object2 不在指向那两个对象时,正常情况下,我们应该要把其回收掉,但是它们两个还有引用,所以采用引用计数法无法将他们回收!

    图1

    (2)可达性分析

      以一个GC ROOT 对象作为起点进行搜索,如果在GC ROOT和对象之间没有可达路径,则称该对象是不可达的。
    GC ROOT对象

    • 栈帧中的本地变量表中引用的对象
    • 方法区中静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中引用的对象

    (二)垃圾回收算法

    (1)标记-清除算法

    (2)复制算法

    在这里插入图片描述

    (3)标记-整理算法

    在这里插入图片描述
    在这里插入图片描述

    (三)垃圾收集器

    (1)垃圾收集器的评判标准

    垃圾回收器是对垃圾回收算法的实现,JVM中提供了很多垃圾回收器,我们通过以下式子来评判垃圾回收器的好坏
    垃圾回收器的执行效率= 吞吐量/停顿时间
    吞吐量 = 用户代码执行时间/(用户代码执行时间+停顿时间)

    (2)垃圾收集器分类

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    (3)详解并发垃圾回收器

    ①CMS收集器

      CMS(并发标记扫描)收集器是并发收集器,是基于标记-清理算法进行垃圾后手,用于OldGC
    优点: 并发收集,低停顿
    缺点: 会产生大量空间碎片,停顿时间虽然短但是不可控

    为什么CMS收集器不进行并发的初始标记?
      因为标记垃圾所需要耗费的时间较少,没有必要再并发开启多线程,这样子会使程序更加耗费资源

    ②G1收集器

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    ③ZGC收集器

      ZGC从JDK11开始支持,目前还是实验性版本,原理类似G1,是目前效率最高的垃圾收集器,平均暂停时间为0.05毫秒

  • 相关阅读:
    【C++ Primer】 第八章 IO库 习题 答案
    day37-IO流04
    Django REST framework 简介
    最小栈 与 栈的压入、弹出序列
    网卡限速工具之WonderShaper
    kafka查看数据_Kafka 数据积压情况查看
    2. 字符串
    Redis Cluster集群方案
    vue 路由中 vite 与webpack 动态 导入的方法汇总
    Oracle表空间、用户详解
  • 原文地址:https://blog.csdn.net/weixin_52162723/article/details/126796835