总结JVM重要知识点

news2024/11/24 2:37:29

一.类加载和创建对象的过程

1.类加载

      1.编译 : 将源码文件(.java)编译成JVM可以解释的.class文件 . 语法分析>语义分析>注解处理 , 生成class文件

      2.加载 :

                装载 : 字节码本来存储在硬盘上 , 需要运行时 , 有类加载系统负责将类的信息加载到内存中(方法区) , 使用的是类加载器进行加载 , 充当的是一个快递员的身份. (双亲委派机制)下面会有:

                连接 : 效验class信息 , 为类的静态变量分配内存 , 初始化为默认值

                初始化 : 为变量赋值为正确的初始值

      3.解释 : 将字节码转换成操作系统可识别的执行指令.

                字节码解释器(直接解释) : jvm运行程序时 , 逐行对字节码指令进行翻译 , 效率低

                即时编译器(JIT)(编译解释) : 对某段代码整体编译后执行 , 效率高 , 但是编译需要耗费一定的时间.

      4.执行 : OS(操作系统)识别解释阶段生成的指令 , 调用系统的硬件执行最终的程序指令.

2.创建对象的过程(在我们使用new创建对象的时候)

  1. jvm会先去加载这个对象的类的字节码文件 , 并把其中static的属性放到方法区中保存 , 此时这些属性还是其类型对应的默认值 , (static final的变量除外 , jvm会直接对其进行赋值)

  2. 紧接着jvm会对这些方法区中的static变量进行赋值

  3. 在方法区中static属性赋完值后 , new关键字就会在堆中去是申请一块空间用来存储对象 , 此时对象中的成员属性就在内存中存在了 , 但会被赋予其默认值

  4. 在new关键字在堆中开辟空间后 , jvm就会调用对象的构造方法 , 在构造方法中对成员属性开始赋值 , 当构造方法执行完毕后 , 成员属性也随之完成了赋值 , 到这里 , 整个对象的创建过程才算完成

3.双亲委派机制

  把请求交由父类处理 , 是一种任务委派模式

加载一个类时 , 委托给父类加载器进行加载 , 如果父类加载器没有找到 , 就向上级委托 , 直到引导类加载器 , 父类加载器找到就直接返回 , 如果没有找到就委托给自定义加载器 , 如果还没有找到 , 就直接抛异常ClassNotFoundException

JVM内存结构

1.运行时数据区

线程的私有区域 : 程序计数器 , 虚拟机栈 , 本地方法栈

线程的共享区域 : 堆 , 方法区

线程的私有区域的生命周期与线程相同 , 随着线程的启动而创建 , 随着线程的结束而销毁

共享区域随着虚拟机的启动而创建 , 随着虚拟机的关闭而销毁

1.程序计数器

可以看做是当前线程所执行字节码的行号指示器 , 用来指向下一个将要执行的指令代码 , 有执行引擎来读取下一条指令 , 是唯一一个没有内存溢出的区域

一个线程的执行 , 就是通过字节码解释器来改变当前计数器的值 , 来获取下一条将要执行的字节码指令 , 从而保证线程的正常执行.

2.虚拟机栈

就是一个执行java方法的内存模型 , 每个方法在执行的同时都会创建一个栈帧 , 栈帧中有:

     局部变量表(存储一些方法内部的变量) , 操作栈(用于计算方法内部的值) , 动态链接(有时候要用到类中的一些变量 , 所以需要一个引用地址指向运行时常量) , 返回地址(用来返回之前调用它的方法)

栈帧就是用来记录方法执行的过程 , 方法执行的过程中 , 虚拟机会创建一个与之对应的栈帧 , 方法的执行与返回对于栈帧在虚拟机中的入栈和出栈

线程1在cpu1上面运行,线程2在cpu2上面运行,在cpu资源不足时其他线程将处于等待状态。在线程内部,每个方法的执行和返回都对应一个栈帧的入栈和出栈,每个运行中的线程当前只有一个栈帧处于活动状态

3.本地方法栈

用来管理本地方法的调用

4.堆

       在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是线程共享的内存区域,也是垃圾收集器进行垃圾回收最主要的内存区域。

解决程序的运行问题,即程序如何执行,或者说如何处理数据.

解决的是数据存储的问题,即数据怎么放,放在哪儿

一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的核心区域

java8 及之后堆内存分为:新生区(新生代)+老年区(老年代)

新生区分为 Eden(伊甸园)区和 Survivor(幸存者)区

 为什么要进行分区(分代)?

1.将对象根据存活的概率进行分类 , 对存货时间长的对象 , 放到老年区 , 从而减少扫描垃圾的时间以及GC频率

2.针对分类进行不同的垃圾回收算法 , 可以扬长避短

分代的收集思想Minor GC、Major GC、Full GC

JVM 在进行 GC 时,并非每次都新生区和老年区一起回收的,大部分时候回收的都是指新生区.针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类型:一种是部分收集,一种是整堆收集.

部分收集:不是完整收集整个 java 堆的垃圾收集.其中又分为:

新生区收集(Minor GC/Yong GC):只是新生区(Eden,S0,S1)的垃圾收集.

老年区收集(Major GC / Old GC):只是老年区的垃圾收集.

整堆收集(Full GC):收集整个 java 堆和方法区的垃圾收集.

整堆收集出现的情况:

     1.调用System.gc()时会进行full GC

     2.当堆空间中对象的数量已经达到了预设的阈值 

     3.当堆空间中的对象已经被分配到了连续的内存区域,无法再分配更多的内存时。

在进行Full GC时 , java虚拟机会停止掉所有的正在运行的线程 , 而且,Full GC的执行时间通常比局部回收(Partial GC)要长得多,因为它需要扫描整个堆空间以确定哪些对象可以被回收。因此,应该尽可能地避免Full GC的发生,以减少应用程序的停顿时间。

5.方法区

方法区也是被共享的 , 用于存储已被虚拟机加载的类的信息 , 常量 , 静态变量 , 即时编译器编译后的代码 , 还包括一个特殊的区域"运行时常量池"

方法区看做是一块独立于 java 堆的内存空间

常量池的好处?

常量池避免了频繁的创建和销毁对象从而影响了系统的性能 , 其也实现了对象的共享

Integer常量池:

            Integer 的 valueOf 方法很简单,它判断变量是否在 IntegerCache 的最小值(-128)和最大值(127)之间,如果在,则返回常量池中的内容,否则 new 一个 Integer 对象。

字符串常量池 :

            第一种使用 new 创建的对象,存放在堆中。每次调用都会创建一个新的对象。

            第二种:先在栈上创建一个 String 类的对象引用变量 str,然后通过符号引用去字符串常量池中找有没有 “abcd”,如果没有,则将“abcd”存放到字符串常量池中,并将栈上的 str 变量引用指向常量池中的“abcd”。如果常量池中已经有“abcd”了,则不会再常量池中创建“abcd”,而是直接将 str 引用指向常量池中的“abcd”。

JDK7 及以后的版本中将字符串常量池放到了堆空间中。因为方法区的回收效率很低,我们开发中会有大量的字符串被创建, 回收效率低。放到堆里,能及时回收内存

为什么方法区的回收效率低呢?

        方法区的回收在 Full GC 的时候才会执行永久代的垃圾回收,而 Full GC 是老年代的空间不足、方法区不足时才会触发。这就导致字符串常量池回收效率不高

2.垃圾标记算法

     就是用来判断该对象是否是垃圾 , 是否有引用指向该对象

(1)引用计数算法

    给堆内存中的每一个对象记录一个引用个数 , 引用个数为0的认为是垃圾 , 但是无法解决循环引用的问题.

   

(2)可达性分析算法

 在内存中 , 从根对象(GC root , 一组活跃的对象) 向下一直找引用 , 找到的对象就不是垃圾, 没找到的就是垃圾.

哪些可以作为GC root呢?

     在虚拟机栈中 , 当前栈顶的栈帧是活跃的 , 当前栈帧里面的局部变量锁指向堆内存中对象的就可以作为GC root

 对象的finalization机制

   只要记住对象在被回收之前会调用一次finalize方法, 它就是用来执行一些清理操作 , 比如释放资源 , 但是它的执行时间是不固定的, 也不能保证一定会被执行。这是因为垃圾收集器的行为是不可预测的,可能会出现一些意外的情况,例如在finalize()方法执行过程中发生了异常等。因此,程序员不能依赖finalize()方法来进行资源的释放和清理操作,而应该使用try-with-resources或finally块等机制来确保及时释放资源。

垃圾回收算法

1.标记清除算法

   最简单残暴的算法, 直接将垃圾干掉 , 但存在内存碎片的问题

内存碎片:可能有10M的空余内存,但程序申请9M内存空间却申请不下来(10M的内存空间是垃圾清除后的,不连续的)。

2.标记复制算法

 把存活的对象复制这样没有了内存碎片的问到另一块空间 , 复制完成后 , 把原有的整块空间干掉, 题 , 但是内存的利用率很低 , 得有一块新的区域给复制过去

3.标记压缩算法

 把存活的对象一道一边, 垃圾移到另一边 , 再将垃圾一起清理掉

stop the world(应用停止访问)

回收垃圾的时候 , 程序有短暂的时间是不能正常运行的 , 又由于垃圾回收是会导致stop the world,所以分代的一个原因是为了使stop the world持续的时间尽可能短。

年轻代特点:区域相对老年代较小,对象生命周期短、存活率低,回收频繁,适合标记复制算法。

老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。这种情况存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记- 清除或者是标记-清除与标记-整理的混合实现。

总结:不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法, 以提高垃圾回收的效率,同时使stop the world持续的时间尽可能短。

垃圾回收器

回答思路:单线程-->多线程-->CMS-->G1

单线程缺点(新生代Serial,老年代Serial Old):只有一个线程进行垃圾回收,效率低

多线程(新生代Paraller,老年代Paraller Old):多线程垃圾回收器内部提供多个线程进行垃圾回收,在多 cpu 情况下大大提升垃圾回收效率。

两者的共同缺点:会暂停其他用户线程,也就是Stop the World

所以垃圾回收器的优化思路就是停顿的时间能不能尽可能的短,用户线程和 GC 线程能不能去并发执行,让垃圾收集过程中用户也不会感到明显的卡顿。

1.CMS

老年代推出了一款垃圾回收器CMS,采用的是标记清除算法

1.初始标记:Stop The World,仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。

2.并发标记:垃圾回收线程,与用户线程并发执行。此标记阶段标记出间接关联的对象。

3.重新标记:Stop The World,使用多条标记线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来。

4.并发清除:只使用一条 GC 线程,与用户线程并发执行,清除刚才标记的对象。 这个过程非常耗时。

所以整个思路就是:将耗时时间长的阶段与用户线程一起并发工作,因此,总体上说,CMS 收集器的内存回收过程是与用户线程一起并发执行的。

CMS中的问题:

1、CPU敏感(在多核的情况下使用最好,如果没有多少核使用这个没有多少意义,还要不停的上下文切换)

2、浮动垃圾和内存碎片:有可能重写标记阶段完后,又产生了一些垃圾,但是这些垃圾只能等下次垃圾回收在清除,同时标记清除算法的缺点也非常明显,会产生内存碎片,申请大对象(例如数组要求连续空间)可能申请不下来。

2.G1

G1垃圾回收器是在 Java7 之后引入的一个新的垃圾回收器,对老年代和新生代都可以进行回收。

以前学习堆的分区时候,了解到堆的分区主要为:伊甸园、幸存者区以及老年区的知识。

学习G1之前要忘记之前堆的分区的相关知识,因为 G1 把堆内存分割为很多不相关的区域(Region)

使用不同的 Region 来表示 Eden、幸存者 0 区,幸存者 1 区,老年代等。G1 GC 有计划地避免在整个 Java 堆中进行全区域的垃圾收集。G1 跟踪各 个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时 间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。  

  • 并行与并发:G1能充分利用多CPU,多核环境下的硬件优势。GC线程并行执行,与用户线程并发执行

  • 分代收集:能够采用不同的方式去处理新创建的对象和已经存活了一段时间的对象,不需要与其他收集器进行合作。

  • 空间整合:G1从整体上来看基于“标记-整理”算法实现的收集器,从局部上看是基于复制算法实现的(也就是一个Region是标记清除,两个region之间是复制),因此G1运行期间不会产生空间碎片。

    优点:能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配,不会产生内存碎片,有利于长时间运行。同时可以根据用户所期望的 GC 停顿时间来指定回收计划,尽可能的满足用户期望。

排查JVM的问题

 对于还在正常运行的系统来说

  1.可以使用jmap来查看JVM中各个区域的使用情况

  2.可以使用jstack来查看线程运行的情况 , 比如哪些线程阻塞 , 是否出现了死锁

  3.可以通过jstat命令来查看垃圾回收的情况 , 特别是full GC , 如果发现full GC比较频繁 , 那么就得进行调优

对于已经发生了OOM的系统

1.⼀般生产系统中都会设置当系统发⽣了OOM时,生成当时的dump文件

2.我们可以利⽤jsisualvm等⼯具来分析dump⽂件

3.根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码

4.然后再进行详细的分析和调试

一个独享从进入到JVM , 再到GC清除的经历

1.首先把字节码文件内容加载到方法区

2.根据类的信息在堆中创建对象

3.对象首先会在堆中的Eden区 , 经过一次Minor GC后 , 对象如果存活 , 就会进入到Suvivor区 , 在后续的每次的MinorGC中 , 如果对象一直存活 , 就会在Suvivor区来回移动 , 每移动一次年龄就会增长1

4.当年龄超过15后 , 如果对象仍然存活 , 就会进入到老年代

5.如果经过Full GC , 被标记为垃圾对象 , 那么就会被GC清除掉.

           

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

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

相关文章

距离6月18日DAMA-CDGA/CDGP认证考试还有31天,报名从速

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中! 考试开放地区:北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证班进行中,报名从速! DAMA认证为数据管…

crow-han(基于go-micro框架的微服务脚手架)-02-添加一个微服务实战(已k8s为例)

文章目录 1. 目录结构2. 添加自己的微服务2.1 配置文件和必要的函数2.1.1 配置文件2.1.2 连接k8s 2.2 k8s服务相关代码2.3 proto2.3.1 创建proto文件2.3.2 编译proto 2.4 handler2.5 k8s的main函数 3. gateway调用3.1 调用微服务3.2 router 4. 添加文档 swagger5. 调试5.1 启动…

自学黑客(网络安全)有哪些技巧——初学者篇

很多人说,要想学好黑客技术,首先你得真正热爱它。 热爱,听着多么让人激情澎湃,甚至热泪盈眶。 但很可惜,“热爱”这个词对还没入门的小白完全不管用。 如果一个人还没了解过你就说爱你,不是骗财就是骗色…

第三十四章 使用react-redux进一步管理状态

react-redux和redux是两个不同的概念。 redux是一个重要的数据管理库。redux的作用是帮助处理应用程序中复杂的数据管理和状态管理,它可以让你的应用程序更加可维护和可扩展。 react-redux是一个react库,它可以帮助react开发者在react应用程序中集成re…

Java经典笔试题—day11

Java经典笔试题—day11 🔎选择题🔎编程题🥝最近公共祖先🥝最大连续bit数 🔎结尾 🔎选择题 (1)下面哪个标识符是合法的? A.9HelloWorld B._Hello World C.Hello*World D.Hello$World D Java中标…

操作符讲解1---C语言

目录 前言: 1.什么是操作符 2.算术操作符 3.移位操作符 4.位操作符 5.逻辑操作符 5.1逻辑与 5.2逻辑或 5.3练习 5.4逻辑非 前言: 博主这几天都在积累知识,俗话说:”只有多输入才能有输出”。在写博客之前,也…

一文1000字从0到1实现Jenkins+Allure+Pytest的持续集成

一、配置 allure 环境变量 1、下载 allure是一个命令行工具,可以去 github 下载最新版:https://github.com/allure-framework/allure2/releases 2、解压到本地 3、配置环境变量 复制路径如:F:\allure-2.13.7\bin 环境变量、Path、添加 F:\…

【硬核】C语言指针是什么?深入浅出带你掌握C语言指针!

指针与底层硬件联系紧密,使用指针可操作数据的地址,实现数据的间接访问,本文章内容如下 1、C语言指针的作用 2、计算机的存储机制 3、如何定义指针 4、如何操作指针 5、数组与指针的关系 6、指针使用中的一些注意事项 1、C语言指针有什么作用…

企业级架构设计原则(含架构管理原则、业务架构设计原则、应用架构设计原则、数据架构设计原则、技术架构设计原则)

Togaf中的架构原则是一组用于指导企业架构设计和决策的基本准则。这些原则旨在支持组织的目标、价值观和战略,并提供一致性、可持续性和可扩展性的架构方案。 Togaf中提供了一些常见的架构原则,比如:保持一致性:确保架构与组织的目…

MySQL 性能调优及生产实战篇(二)

前言数据结构HASHBinary Search Trees、AVL TreesRed/Black TreesB TreesB Trees 数据存储InnoDBMyISAM 索引优化索引匹配方式哈希索引组合索引聚簇、非聚簇索引覆盖索引 优化细节(important)数据库勿做计算尽量主键查询前缀索引索引扫描排序子查询范围列…

干货满满---90条简单实用的Python编程技巧

对于Python,想必大家都不陌生,自从它问世以来得到了广大编程爱好者的追捧和喜爱,但是再好的东西都需要讲究技巧和策略方法,才能达到事半功倍的效果,下面是我近几年的学习心得和总结,希望能对大家带来一定帮…

不懂就要问,现在的物联卡还有人用吗?

很多朋友私信小编,现在的物联卡还能买吗? 当然,对于企业设备来讲,物联卡是一直可以使用的,而且非常稳定。 如果是用在个人手机上面,可以说也是可以用的,只不过是使用时间长短的问题。 ​ 下面…

ChatGPT为企业应用赋能

chatgpt-on-wechat和bot-on-anything两个项目都支持企业微信部署,其中前者功能比较丰富,推荐! 如需帮助,可以搜索wx:Youngerer 找到我! 功能展示: ![在这里插入图片描述](https://img-blog.csd…

【Linux升级之路】3_Linux进程概念

🌟hello,各位读者大大们你们好呀🌟 🍭🍭系列专栏:【Linux升级之路】 ✒️✒️本篇内容:认识冯诺依曼系统,操作系统概念与定位,深入理解进程概念(了解PCB&…

C语言函数大全-- m 开头的函数(2)

C语言函数大全 本篇介绍C语言函数大全-- m 开头的函数 1. mkdirat 1.1 函数说明 函数声明函数功能int mkdirat(int dirfd, const char *pathname, mode_t mode);它是一个 Linux 系统下的系统调用函数,用于在指定目录下创建新的子目录 参数: dirfd &a…

推荐一个一键AI抠图网站

一键去除图片背景 在这个数字化的世界里,我们经常需要处理各种图片,无论是用于个人的社交媒体,还是用于商业的广告设计。 然而,图片处理往往需要专业的技能和复杂的软件,这对许多人来说可能是个挑战。但现在&#xf…

3. Python字符串

文章目录 一、修改字符串大小写1.1 将字符串中每个单词的首字母改为大写1.2 将字符串中所有的字母改为大写1.3 将字符串中所有的字母改为小写 二、拼接字符串三、添加空白3.1 使用制表符添加空白3.2 使用换行符添加空白3.3 制表符和换行符同时使用 四、删除空白4.1 仅去掉字符串…

redis单机安装

1. 安装gcc 2.下载并编译redis wget http://download.redis.io/releases/redis-7.0.4.tar.gz 直接下载到虚拟机中解压 编译 安装redis 执行命令: make install PREFIX/usr/local/redis/ ,会将redis安装到指定目录下,在这个目录下会生产bin目录 在安…

《花雕学AI》人类推理能力对AI来说是什么?用ChatGPT来检验一下

”这里有一本书、九个鸡蛋、一台笔记本电脑、一个瓶子和一个钉子,请告诉我如何把它们稳定地堆叠在一起?“ 这是去年提出的一道测试推理能力的题目,当微软的计算机科学家开始试验一种新的AI系统时,他们要求AI解决这个难题&#xf…

【Java 并发编程】CAS 原理解析

CAS 原理解析 1. 什么是 CAS?1.1 悲观锁与乐观锁1.2 CAS 是什么? 2. CAS 核心源码3. CAS 实现原子操作的三大问题3.1 ABA 问题3.2 循环性能开销3.3 只能保证一个变量的原子操作 4. synchronized、volatile、CAS 比较 1. 什么是 CAS? 1.1 悲观…