侧边栏壁纸
博主头像
Haenu的Blog 博主等级

坚持学习,慢慢进步!

  • 累计撰写 35 篇文章
  • 累计创建 10 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

JVM

Haenu
2024-06-24 / 0 评论 / 0 点赞 / 37 阅读 / 0 字

数据区域

组成部分:堆、方法区、栈、本地方法栈、程序计数器

1、堆解决的是对象实例存储的问题,垃圾回收器管理的主要区域。

2、方法区可以认为是堆的一部分,用于存储已被虚拟机加载的信息,常量、静态变量、即时编译器编译后的代码。

3、栈解决的是程序运行的问题,栈里面存的是栈帧,栈帧里面存的是局部变量表、操作数栈、动态链接、方法出口等信息。

4、本地方法栈与栈功能相同,本地方法栈执行的是本地方法,一个Java调用非Java代码的接口。

5、程序计数器(PC寄存器)程序计数器中存放的是当前线程所执行的字节码的行数。JVM工作时就是通过改变这个计数器的值来选取下一个需要执行的字节码指令。

方法区,永久代和元空间的区别

在JDK8之前 我们是永久代作为方法区的实现 用的是JVM内存

在JDK8以后 我们是元空间作为方法区的实现 他是用的本地内存 不占用jvm内存 有自己的垃圾回收机制

JDK8为什么要调整方法区的位置?

元空间仍旧可能溢出,但是比原来出现的几率会更小

堆主要保存对象实例 数组等

虚拟机栈

堆栈的区别是什么?

1、栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。

2、栈内存是线程私有的,而堆内存是线程共有的。

3,、两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。

对象一定分配在堆中吗?有没有了解逃逸分析技术?

不一定
有方法逃逸 和线程逃逸

通俗点讲,当一个对象被new出来之后,它可能被外部所调用,如果是作为参数传递到外部了,就称之为方法逃逸。-xx: -DoEscapeAnalysis

除此之外,如果对象还有可能被外部线程访问到,例如赋值给可以在其它线程中访问的实例变量,这种就被称为线程逃逸。

逃逸分析的好处

●栈上分配
如果确定一个对象不会逃逸到线程之外,那么久可以考虑将这个对象在栈上分配,对象占用的内存随着栈帧出栈而销毁,这样一来,垃圾收集的压力就降低很多。
●同步消除
线程同步本身是一个相对耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。

●标量替换
如果一个数据是基本数据类型,不可拆分,它就被称之为标量。把一个Java对象拆散,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。假如逃逸分析能够证明一个对象不会被方法外部访问,并且这个对象可以被拆散,那么可以不创建对象,直接用创建若干个成员变量代替,可以让对象的成员变量在栈上分配和读写。

什么是双亲委派模型?

如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就返回成功;只有父类加载器无法完成此加载任务时,才由下一级去加载。

JVM为什么采用双亲委派机制

通过双亲委派机制可以避免某一个类被重复加载,当父类已经加载后则无需重复加载,保证唯一性。

说一下类加载的执行过程?

加载 验证 准备 解析 初始化 使用 卸载

加载:将硬盘上的Java二进制文件(class文件)转为内存中的Class对象

验证:类是否符合JVM规范,安全性检查

准备:类变量分配内存并设置类变量初始值

解析:把类中的符号引用转换为直接引用

初始化:对类的静态变量,静态代码块执行初始化操作

使用:使用new关键字为其创建对象实例

卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存

对象创建的过程

虚拟机收到new指令触发。
类加载检查:如果类没有被类加载器加载,则执行类加载流程(将class信息加载到JVM的运行时数据区的过程),对象所需内存大小在类加载完后可以完全确定。
对象分配内存:从堆中划分出一块确定大小的内存。
内存空间初始化:内存分配完后,虚拟机需要将分配到的内存空间初始化为零值(如:int值为0,boolean值为false等),保证了对象的实例字段在Java代码中可以直接使用。
为对象进行必要的设置:虚拟机为对象进行设置,如设置对象属于哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象头中。
从虚拟机的角度来看,一个新的对象已经创建完毕。但从Java程序的角度来看,对象创建才刚开始,所有的字段还是零值,所以需要程序员进行初始化操作,这样一个真正可用的对象才算完全产生出来。
init是对对象级别的变量或非静态代码块进行初始化的
clinit静态变量或者静态代码块谁来初始化呢

对象内存分配方式

分配方式的选择 取决于 Java堆内存是否规整:

●指针碰撞方式:
○堆内存绝对规整。
○分配过程:将已使用内存和为使用内存之间放一个分界点的指针,分配内存时,指针会向未使用内存方向移动,移动一段与对象大小相等的距离。


●空闲列表:
○堆内存不规整。
○分配过程:虚拟机内部维护了一个记录可用内存块的列表,在分配时从列表找一块足够大的空间划分给对象实例,并更新列表上的记录。
Java堆是否规整 由所采用的垃圾收集器是否带有压缩整理功能决定

JVM 里 new 对象时,堆会发生抢占吗?JVM是怎么设计来保证线程安全的?


●采用CAS分配重试的方式来保证更新操作的原子性
每个线程在Java堆中预先分配一小块内存,也就是本地线程分配缓冲(Thread Local AllocationBuffer,TLAB),要分配内存的线程,先在本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。-XX:+UseTLAB

垃圾回收

如何判断对象仍然存活?

如果一个或多个对象没有任何的引用指向它了,那么这个对象现在就是垃圾,如果定位了垃圾,则有可能会被垃圾回收器回收。

reference count(引用计数)

可达性分析算法

根对象(root)的类型

根对象不仅仅包括我们上面所说的 main 方法里面的对象,属于根对象的还有以下这些:
可以作为GC Roots的主要有四种对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象
○比如:各个线程被调用的方法中使用到的参数、局部变量等(局部变量表)。
方法区中类静态属性引用的对象
○比如: Java类的引用类型静态变量
方法区中常量引用的对象
○比如:字符串常量池(String Table)里的引用
●所有被同步锁synchroni zed持有的对象

垃圾回收算法

标记清除算法

标记整理算法

复制算法

三色标记算法

1.用于垃圾回收器升级,将STW变为并发标记。STW就是在标记垃圾的时候,必须暂停程序,而使用并发标记,就是程序一边运行,一边标记垃圾。
2.避免重复扫描对象,提升标记阶段的效率

过程:

假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:
初始时,所有对象都在【白色集合】中;
将 GC Roots直接引用到的对象挪到【灰色集合】中;
从灰色集合中获取对象:
3.1. 将本对象引用到的其他对象全部挪到【灰色集合】中;
3.2. 将本对象挪到【黑色集合】里面。
重复步骤3,直至【灰色集合】为空时结束。
结束后,仍在【白色集合】的对象即为GC Roots不可达,可以进行回收。

分代回收算法

创建个对象放的eden区 然后eden区满了 他会标记eden和from区 标记完存货对象发到to区 如果eden一会又满了 标记eden和to区 放到from区 然后存活时间长的 最多15次回收 还没回收掉的 放到老年区

Minor GC/Young GC、Major GC/Old GC、Mixed GC、Full GC 都是什么意思?

部分收集(Partial GC):指目标不是完整收集整个 Java 堆的垃圾收集,其中又分为:

  • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。

  • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS 收集器会有单独收集老年代的行为。

  • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有 G1 收集器会有这种行为。

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

什么时候会触发 Full GC?

  • Young GC 之前检查老年代:在要进行 Young GC 的时候,发现老年代可用的连续内存空间 < 新生代历次Young GC后升入老年代的对象总和的平均大小,说明本次 Young GC 后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间,那就会触发 Full GC。

  • Young GC 之后老年代空间不足:执行 Young GC 之后有一批对象需要放入老年代,此时老年代就是没有足够的内存空间存放这些对象了,此时必须立即触发一次 Full GC

  • 老年代空间不足,老年代内存使用率过高,达到一定比例,也会触发 Full GC。

  • 空间分配担保失败( Promotion Failure),新生代的 To 区放不下从 Eden 和 From 拷贝过来对象,或者新生代对象 GC 年龄到达阈值需要晋升这两种情况,老年代如果放不下的话都会触发 Full GC。

  • 方法区内存空间不足:如果方法区由永久代实现,永久代空间不足 Full GC。

  • System.gc()等命令触发:System.gc()、jmap -dump 等命令会触发 full gc。

有了 CMS,为什么还要引入 G1?

优点:CMS 最主要的优点在名字上已经体现出来——并发收集、低停顿。

缺点:CMS 同样有三个明显的缺点。

  • Mark Sweep 算法会导致内存碎片比较多

  • CMS 的并发能力比较依赖于 CPU 资源,并发回收时垃圾收集线程可能会抢占用户线程的资源,导致用户程序性能下降。

  • 并发清除阶段,用户线程依然在运行,会产生所谓的理“浮动垃圾”(Floating Garbage),本次垃圾收集无法处理浮动垃圾,必须到下一次垃圾收集才能处理。如果浮动垃圾太多,会触发新的垃圾回收,导致性能降低。

G1 主要解决了内存碎片过多的问题。

垃圾回收器

串行垃圾收集器

用得少 不卡

并行垃圾收集器

CMS并发垃圾回收器

  • 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;

  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

  • 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

G1垃圾回收器

JDK9后 默认

  • 应用于新生代和老年代,在JDK9之后默认使用G1

  • 划分成多个区域,每个区域都可以充当 eden,survivor,old, humongous,其中 humongous 专为大对象准备

  • 采用复制算法

  • 响应时间与吞吐量兼顾

  • 分成三个阶段:新生代回收、并发标记、混合收集

  • 如果并发失败(即回收速度赶不上创建新对象速度),会触发 Full GC

CMS和G1的区别

CMS

流程

总体流程为:初始标记=> 并发标记=> 重新标记=> 并发清理。

详细流程如下:

  1. 初始标记(Initial Mark)(STW

  1. 此阶段会暂停虚拟机(STW),由根对象扫描出所有的关联对象,并做出标记。此过程只会导致JVM短暂暂停。

  1. 并发标记(Concurrent Marking)

  1. 恢复所有暂停的线程对象,并且对之前标记过的对象进行扫描,取得所有跟标记对象有关联的对象。

  1. 并发预清理(Concurrent Precleaning)

  1. 査找所有在并发标记阶段新进入老年代的对象(例如一些对象可能从新生代晋升到老年代,或者有一些对象被分配到老年代)通过重新扫描,减少下一阶段的工作。

  1. 重新标记(Remark)(STW

  1. 此阶段会暂停虚拟机(STW),对在并发标记阶段被改变的引用或新创建的对象进行标记。

  1. 并发清理(Concurrent Sweeping)

  1. 恢复所有暂停的应用线程,对所有未标记的垃圾对象进行清理,并且尽量将己回收对象的空间重新拼凑为一个整体。在此阶段收集器线程和应用程序线程并发执行。

  1. 并发重置(Concurrent Reset)

  1. 重置CMS收集器的数据结构,等待下一次垃圾回收。


老年代的GC流程

G1的老年代GC操作流程与CMS类似,并且在整个回收过程中依然会产生短暂的停顿。

流程为:初始标记(STW)=> 并发标记=> 重新标记(STW)=> 并行清理(STW)

  1. 初始标记(STW

  2. 根区域(Region)扫描

  1. 在初始标记的存活区扫描对老年代的引用,并且对相关引用对象进行标记,该阶段与其他应用线程(非STW)同时运行。只有完成该阶段后,才能开始下一次STW年轻代垃圾回收。

  1. 并发标记

  1. 在堆内存中进行并发标记(与其他应用线程同时运行),在此过程中有可能被年轻代GC打断。

  2. 在此阶段,如果发现某一区域内全部为垃圾对象,那么会立即回收此区域的内存空间。而在此阶段也会计算每个区域的对象活跃度(该区域中存活对象的比例)。

  1. 重新标记(STW

  1. 此阶段主要是用于收集并发标记阶段产生的垃圾空间产生短暂停顿(STW)。

  2. G1收集器对该阶段使用了比CMS更高效的初始快照算法SATB(Snapshot-At-The-Beginning)

  1. 并行清理(STW

  1. 清理所有标记的垃圾内存空间,此阶段会产生短暂停顿(STW)。

  2. 此阶段会清除记录集合(RememberSets)并将空白区域重置。

  1. 复制阶段

  1. 将回收区域的存活对象复制到没有使用过的新区域(Region)。

强引用、软引用、弱引用、虚引用的区别?

强引用: 只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。

软引用:

  • 当内存不足时,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用包裹的对象给干掉。

弱引用:

  • 不管内存是否足够,只要发生GC,弱引用就会被回收。

虚引用:

  • 虚引用必须与ReferenceQueue一起使用。当GC准备回收一个对象时,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。

Java–内存泄露的原因及解决方案(大全)

堆内存中一个对象不再使用时,垃圾回收器却无法从内存中删除他们,导致内存泄露。

0

评论区