JVM基础总结

news2024/11/17 14:47:48

文章目录

  • 一、程序计数器
  • 二、Java虚拟机栈
    • 栈内存溢出【StackOverflowError】
    • 线程运行诊断
  • 三、本地方法栈【Native Method Stacks】
  • 四、堆【Head】线程共享
    • 堆内存溢出【OutOfMemoryError:Java heap space】
    • 堆内存诊断
  • 五、方法区【Method Area】线程共享
    • 运行时常量池
    • StringTable 特性【面试常考】
    • StringTable 性能调优
  • 直接内存
    • 直接内存分配和回收原理
  • 垃圾回收
    • 如何判断对象可以回收
      • 引用计数法
      • 可达性分析算法
      • 四种引用【面试常考】
    • 垃圾回收算法
      • 标记清除算法
      • 标记-整理算法
      • 复制
      • 分代垃圾回收
        • Minor GC
        • Full GC
    • 垃圾回收器/垃圾收集器
      • 串行【单线程】
      • 吞吐量优先【多线程】
      • 响应时间优先【多线程】
      • G1
    • GC调优
  • 类加载阶段


一、程序计数器

Program Counter Register程序计数器(寄存器)
作用: 是记住下一条jvm指令的执行地址
特点:

1、是线程私有的
2、不会存在内存溢出

二、Java虚拟机栈

-Xss
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

  • 垃圾回收是否涉及栈内存?

不涉及

  • 栈内存分配越大越好吗?

不是,栈内存分配越大,线程个数会相应的减少

  • 方法内的局部变量是否是线程安全?

如果方法内局部变量没有逃离方法的作用范围,那么就是线程安全的
如果是局部变量引用了对象,并逃离了方法的作用范围,需要考虑线程安全问题

栈内存溢出【StackOverflowError】

  • 栈帧过多导致栈内存溢出(递归调用,没有设置正确的出口)
  • 栈帧过大导致栈内存溢出(不太容易出现)

线程运行诊断

  • cpu占用内存过多

用top定位哪个进程对cpu的占用过高
在这里插入图片描述
ps H -eo pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高)
在这里插入图片描述
在这里插入图片描述
jstack 进程id (可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号)
在这里插入图片描述
32665转为16进制–>0x7f99
在这里插入图片描述

  • 程序运行很长时间没有结果【死锁问题】

用jstack查询:
在这里插入图片描述

三、本地方法栈【Native Method Stacks】

在这里插入图片描述
给本地方法的运行提供一个内存空间

四、堆【Head】线程共享

-Xmx
Heap堆:通过new关键字,创建对象都会使用堆内存
特点:

1、是线程共享的,堆中对象都需要考虑线程安全问题
2、有垃圾回收机制

堆内存溢出【OutOfMemoryError:Java heap space】

如果不断的产生对象,产生的新对象仍然有人在使用,那么这些对象就不会作为垃圾被回收,这些对象达到一定数量,就会导致堆内存耗尽。
java.lang.OutOfMemoryError: GC Overhead Limit Exceeded : 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。

堆内存诊断

  • jps工具
    查看当前系统中有哪些Java进程
    在这里插入图片描述

  • jmap工具
    查看堆内存占用情况
    在这里插入图片描述

  • jconsole工具
    图形界面的,多功能监测工具,可以连续监测

五、方法区【Method Area】线程共享

-XX:MaxMetaspaceSize 设置元空间大小【jdk1.8以后】
-XX:MaxPermSize 设置永久代大小【jdk1.8以前】
在这里插入图片描述
ClassLoader类加载器可以用来加载类的二进制字节码
ClassWriter作用是以代码的方式生成类的二进制字节码文件
在这里插入图片描述
设置元空间大小为8M
会出现OutOfMemoryError:metaspace

总结:

1.8以前会导致永久代内存溢出
1.8以后会导致元空间内存溢出
场景:CGLib动态代理【通过JVM动态生成字节码代理类对象】

运行时常量池

常量池: 就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。
运行时常量池: 常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

StringTable 特性【面试常考】

常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量的拼接原理是StringBuilder(1.8)
字符串常量拼接的原理是编译器优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池:1.8将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回。1.6将这个字符串对象尝试放入串池,如果有则不会放入,如果没有会把此对象复制一份放入串池,并把串池中的对象返回

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";//javac在编译期的一个优化,结果已经在编译期间确定为ab
        String s4 = s1 + s2; //堆当中的对象
        String s5 = "ab";//串池当中的一个对象
        String s6 = s4.intern(); //将这个字符串对象尝试放入串池,如果有则不会放入,如果没有则放入串池,会把串池中的对象返回
        System.out.println(s3 == s4);
        System.out.println(s3 == s5);
        System.out.println(s3 == s6);
    }

在这里插入图片描述

StringTable 性能调优

  • 调整 -XX:StringTableSize=桶个数【桶越多,速度越快】
  • 考虑将字符串对象是否入池【节约堆内存的使用】

直接内存

常见于NIO操作时,用于数据缓冲区
分配回收成本较高,但读写性能高
不受JVM内存回收管理【是通过一个unsafe对象来管理的】

在这里插入图片描述

直接内存分配和回收原理

使用了unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleanclean方法调用freeMemory来释放直接内存

垃圾回收

如何判断对象可以回收

引用计数法

只要一个对象被其他变量所引用,那么就让该对象对应的计数+1,如果某一个变量不再被引用了,那么就让该对象对应的计数-1。当这个对象它的引用计数变为0的时候,就可以进行垃圾回收

弊端: 循环引用问题
虽然两个对象都不再使用了,但是这两个对象的引用计数不能归0,因此这两个对象不能被回收,导致内存泄漏。
在这里插入图片描述

可达性分析算法

  • Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着GC Root 对象为起点的引用链找到该对象,找不到,表示可以回收

会确定一个根对象【肯定不能被垃圾回收的对象叫做根对象】,在垃圾回收之前,会对堆中的所有对象进行一遍扫描,判断是否被根对象直接或间接的引用,如果是,那么这个对象就不能被回收,反之,这个对象就可以作为垃圾被回收。

那么,哪些对象可以作为GC Root?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

四种引用【面试常考】

  • 强引用:只有所有GC Roots对象都不通过强引用引用该对象,该对象才能被垃圾回收
  • 软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象。可以配合引用队列来释放软引用自身【内存不足时,会被回收掉】
  • 弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象。可以配合引用队列来释放弱引用自身【不管内存够不够,都会被回收掉】
  • 虚引用【必须配合引用队列使用】:就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。
  • 终结器引用【必须配合引用队列使用】无需手动编码,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC时才能回收被引用对象

在这里插入图片描述

垃圾回收算法

标记清除算法

分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
两个明显的问题:
效率问题 :标记和清除两个过程效率都不高。
空间问题 :标记清除后会产生大量不连续的内存碎片。

标记-整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
优点:
可以解决碎片问题
缺点:
由于多了整理这一步,因此效率也不高

复制

为了解决标记-清除算法的效率和内存碎片问题,复制收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
在这里插入图片描述
优点: 没有内存碎片
缺点: 需要占用双倍内存空间

分代垃圾回收

Minor GC

新生代的垃圾回收:Minor GC,采用可达性分析算法,判断哪些对象可以被回收,采用复制算法进行回收。
在这里插入图片描述
会触发一次Minor GC,采用复制算法,把存活的对象复制到幸存区To中,并让幸存者寿命+1
在这里插入图片描述
做完复制动作后,交换幸存区From和幸存区To的位置
在这里插入图片描述
又可以继续向伊甸园分配一些新的对象。
……
伊甸园又满了,触发第二次Minor GC
在这里插入图片描述
将幸存区和伊甸园中存活的对象放入幸存区To中,并且寿命+1,交换幸存区From和幸存区To的位置
在这里插入图片描述

幸存区中的对象,寿命超过了一定的阈值(默认是15),把该对象从幸存区晋升到老年代去

Full GC

当老年代的空间不足,当新生代内存不够时,就会触发一次老年代垃圾回收

在这里插入图片描述

总结:

对象首先分配在伊甸园区域
新生代空间不足时,触发minor GC,伊甸园和幸存区From存活的对象使用复制算法复制到幸存区To中,存活的对象年龄加1,并且交换幸存区From To
Minor GC会引发stop the world(STW),暂停其他用户线程,等垃圾回收结束,用户线程才恢复运行
当对象寿命超过阈值时,会晋升至老年代
当老年代空间不足,会先尝试触发Minor GC,如果之后空间仍不足,那么触发Full GC,STW的时间更长
大对象直接进入老年代【大对象就是需要大量连续内存空间的对象(比如:字符串、数组)】

相关参数:
在这里插入图片描述

垃圾回收器/垃圾收集器

串行【单线程】

串行收集器是最基本的垃圾收集器了,是一个单线程收集器。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。

-XX:+UseSerialGC = Serial + SerialOld
新生代:复制算法
老年代:标记+整理
在这里插入图片描述

吞吐量优先【多线程】

多核CPU的作用:单核CPU类似于多个保洁员,但是只有一把扫帚,效率与单线程一致。
让单位时间内,STW的时间最短 0.2+0.2=0.4

-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
新生代:复制算法
老年代:标记+整理
在这里插入图片描述

响应时间优先【多线程】

多核CPU的作用:单核CPU类似于多个保洁员,但是只有一把扫帚,效率与单线程一致。
尽可能让单次STW的时间最短 0.1+0.1+0.1+0.1+0.1=0.5
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
新生代采用标记-复制算法
老年代采用标记-整理算法。
在这里插入图片描述
并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。

初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

当发生并发失败时,CMS的老年代的垃圾回收器就会退化为SerialOld

优点:
并发收集、低停顿
缺点:
对CPU资源敏感
无法处理浮动垃圾
它使用的回收算法“标记-清除”算法会导致收集结束时会有大量空间碎片产生

G1

适用场景:
同时注重吞吐量和低延迟,默认的暂停目标是200ms
超大堆内存,会将对划分为多个大小相等的Region
整体上是标记+整理算法,两个区域之间是复制算法

在这里插入图片描述
部分收集 (Partial GC):
新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;
混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
整堆收集 (Full GC):收集整个 Java 堆和方法区

GC调优

追求高吞吐量:Parallel GC
追求低延迟:CMS G1 ZGC

调优领域:

  • 内存
  • 锁竞争
  • cpu占用
  • io

最快的GC是不发生GC
如果经常发生GC,考虑以下问题:

  • 数据是不是太多?
    resultSet = statement.executeQuery("select * from 大表")
  • 数据表示是否太臃肿?
  • 是否存在内存泄漏?
    static Map map 一直给map中存放对象,造成内存吃紧,频繁的GC,甚至会造成OutOfMemoryError,因此对于长时间存活的对象,可以使用软弱引用、第三方缓存实现(redis)

新生代调优:
新生代的特点:

1、所有的new操作的内存分配非常廉价(TLAB Thread-local allocation buffer线程局部缓冲区)
2、死亡对象的回收代价是0
3、大部分对象用过即死
4、Minor GC的时间远远低于Full GC

新生代空间不是越大越好,如果内存过大,会导致老年代内存过小,那么Full GC出现的次数就会频繁,况且Full GC时间远远大于Minor GC的时间。

理想情况:
1、新生代能容纳所有【并发量*(请求-响应)】的数据
2、幸存区大到能保留【当前活跃对象+需要晋升对象】
3、晋升阈值配置得当,让长时间存活的对象尽快晋升

老年代调优:

以CMS为例:
1、CMS的老年代内存越大越好【垃圾回收的同时,其他的用户在继续运行,所以会产生新的垃圾–浮动垃圾,浮动垃圾导致内存不足就会导致CMS并发失败,CMS的垃圾回收器就会退化为SerialOld 串行老年代回收器。内存越大,预留更多的空间,避免浮动垃圾引起的并发失败】
2、先尝试不做调优,如果没有Full GC,这个系统工作已经很ok了,即使已经发生了Full GC,也应该先尝试调优新生代
3、观察发生Full GC时来年代内存占用,将老年代内存预设调大1/4 ~ 1/3

类加载阶段

系统加载class类型的文件主要三步:加载、连接、初始化。
连接过程又可以分为三步:验证、准备、解析

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/493010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot +Flowable,为流程设置租户

一.简介 什么叫flowable的租户呢?这边举个例子: 假设现在有 A、B、C、D 四个子系统,四个子系统都要部署同一个名为 leave 的流程,如何区分四个不同子系统的的流程呢?通过租户就可以解决这个问题。Flowable 中的租户其…

dell r750服务器安装centos系统全记录

1、启动盘制作 1.1 下载系统 打开 https://www.centos.org/download/ 任意选择一个镜像网站,博主选择的是163镜像 下载内存为4g镜像文件 这里也可以参考 https://blog.csdn.net/weixin_46703995/article/details/121191113 1.2 下载启动盘制作软件 linux系统一…

Denoising Diffusion Probabilistic Model,DDPM阅读笔记——(二)

Denoising Diffusion Probabilistic Model,DDPM阅读笔记 一、去噪扩散概率模型(Denoising Diffusion Probabilistic Model,DDPM) 一、去噪扩散概率模型(Denoising Diffusion Probabilistic Model,DDPM&…

C++数据结构:手撕红黑树

目录 一. 红黑树的概念及结构 二. 红黑树节点的定义 三. 红黑树节点的插入 3.1 初步查找插入节点的位置并插入节点 3.2 红黑树结构的调整 3.3 红黑树节点插入完整版代码 四. 红黑树的结构检查 4.1 检查是否为搜索树 4.2 检查节点颜色是否满足要求 附录:红黑…

TypeScript进阶

目录 TypeScript 与 Vue 文档说明 vscode 插件说明 准备页面基本结构 defineProps与Typescript defineEmits与Typescript ref与Typescript reactive与Typescript computed与Typescript 事件对象与Typescript 模板Ref与Typescript 可选链操作符和非空断言 TypeScript…

21.网络爬虫—js逆向详讲与实战

网络爬虫—js逆向 js逆向JavaScript逆向的详细讲解实战演示有道翻译设置密钥和初始向量对密钥和初始向量进行哈希处理创建AES对象并解密消息移除padding并返回结果 前言: 🏘️🏘️个人简介:以山河作礼。 🎖️&#x1f…

python基于卷积神经网络实现自定义数据集训练与测试

样本取自岩心照片,识别岩心是最基础的地质工作,如果用机器来划分岩心类型则会大大削减工作量。 下面叙述中0指代Anhydrite_rock(膏岩),1指代Limestone(灰岩),2指代Gray Anhydrite_r…

深度学习-第T6周——好莱坞明星识别

深度学习-第T6周——好莱坞明星识别 深度学习-第T6周——好莱坞明星识别一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据 四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签 2、数据可视化3、检查数据4、配置数据集 …

Flutter学习之旅 - 页面布局Stack层叠组件

文章目录 StackPositioned定位布局浮动导航(StackPositioned)FlutterMediaQuery获取屏幕宽度和高度StackAlign Stack Stack意思是堆的意思,我们可以用Stack结合Align或者Stack结合Positioned来实现页面的定位布局 属性说明alignment配置所有元素显示位置children子组…

23.Lambda表达式

Lambda表达式 一、Lambda表达式背景 Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式…

2023-05-05 背包问题

背包问题 1 01背包和完全背包问题 01背包问题 有N件物品和一个容量为V的背包,第i件物品的体积是v[i]、价值是w[i],每种物品只可以使用一次,求将哪些物品放入背包可以使得价值总和最大。这里的w是weight即权重的意思 这是最基础的背包问题&a…

【飞书ChatGPT机器人】飞书接入ChatGPT,打造智能问答助手

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 转载自远控源码文章:飞书接入ChatGPT - 将ChatGPT集…

Ubuntu 如何查看 CPU 架构、系统信息、内核版本、版本代号?

Ubuntu 查看 CPU 架构、系统信息、内核版本、版本代号等相关信息有很多方式,本文介绍几种常用的命令。 x86 架构与 ARM 架构的 CPU 架构不同,如果回显为 aarch64 表示为 ARM 架构,如果回显为 x86_64 表示为 x86 架构,参考《CPU 架…

Prometheus快速入门

Prometheus快速入门 环境准备 三台主机,配置好主机名 各自配置好主机名 # hostnamectl set-hostname --static server.cluster.com ... 三台都互相绑定IP与主机名 # vim /etc/hosts 192.168.126.143 server.cluster.com 192.168.126.142 agent.clu…

归并排序(看了就会)

目录 概念1. 基本思想2. 实现逻辑3. 复杂度分析4、代码 概念 归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单&…

智头条|欧盟达成《人工智能法》协议,全球前沿科技齐聚AWE 2023

行业动态 华为云联手多方推进数字化,软通动力深度参与 华为云宣布启动“‘百城万企’应用现代化中国行”,旨在推动应用现代化进程、助力数字中国高质量落地。软通动力是该行动的参与者之一,共同探索符合区域特点、产业趋势、政企现状的数字化…

Python进阶(Linux操作系统)

一,操作系统 1.1,Linux系统基础操作 1.2,linux进程与线程 1.2.1并发,并行 (1)并发:在一段时间内交替的执行多个任务:对于单核CPU处理多任务,操作系统轮流让让各个任务…

BasicVSR++代码解读(总体介绍)

本文代码主要来自于OpenMMLab提供的MMEditing开源工具箱中的BasicVSR代码。第一部分的解读主要是针对每一个部分是在做什么提供一个解释,便于后续细读每一个块的细节代码。 (1)导入库     basicvsr_plusplus_net中主要继承了torch,mmcv,m…

信号的产生——线性调频函数

信号的产生——线性调频函数 产生线性调频扫频信号函数chirp的调用格式如下: (1)y chirp(t,f0, t1,f1) 功能:产生一个线性(频率随时间线性变化)信号,其时间轴设置由数组t定义。时刻0的瞬间频…

SpringBoot的配置文件、日志文件

一、配置文件( .properties、.yml) 1、.properties 配置文件 1.1、格式 1.2、基本语法 1.2.1、如:一般配置(以键值的形式配置的,key 和 value 之间是以“”连接的。) 1.2.2、如:自定义配置&a…