JVM虚拟机

一、jvm内存模型

1、内存结构

五大部分:虚拟机栈本地方法栈方法区程序计数器

执行引擎、本地接口

image-20210805214339143

(1)程序计数器(Program Counter Register)

  1. 当前线程所执行字节码的行号指示器指向下一个将要执行的指令代码

  2. 每个线程都有一个独立的程序计数器程序计数器线程私有

  3. 如果线程执行 Java 方法,则记录正在执行的虚拟机字节码指令的地址执行Native 方法(C++代码)时,计数器值为空Undefined

  4. 程序计数器不会发生内存溢出OutOfMemoryErrorOOM)问题。

  5. 是一个非常小的内存空间,几乎可以忽略不计

(2)栈(jvm

JVM 中的栈包括 虚拟机栈本地方法栈,两者的区别就是,Java 虚拟机栈为 JVM 执行 Java 方法服务,本地方法栈则为 JVM 使用到的 Native 方法(java调用非java代码的接口)服务。两者作用是极其相似的

image-20210806104158848

堆中对象的一个方法对应一个栈帧。每个栈帧包括局部变量操作数栈方法返回地址动态链接

  1. 栈是用完了就弹出,所以不产生垃圾
  2. 栈是私有的
  3. 放局部变量
  4. 动态链接里面放 方法 对应的 它在方法区里面解析后入口的地址,通过这个找到方法的具体代码,可以理解为方法索引
  5. 方法出口:返回调用自己的方法的那一行,不至于又从第一行代码开始执行

(3)方法区

image-20210806104241110

  1. 用于存储类信息。
  2. 被所有线程共享
  3. 方法区是一个特殊的堆,JVM调优99%的情况都是对方法区和堆进行调优而绝大部分都是对堆调优
  4. 所有字段方法字节码特殊方法(构造函数)接口代码在此定义。所有定义的方法的信息都保存在该区域
  5. 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是,和方法区无关static final, Class,常量池

(4)堆内存

image-20210806104334405

  1. 是最大的一块内存。用于存储对象实例。
  2. 实例变量存在堆内存中
  3. 堆内存又细分为新生代老年代
  4. 年轻代分为了三部分:1个Eden区2个Survivor区(分别叫from和to)。默认比例为8:1:1
①年轻代步入老年代过程

新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。

  • 15次存活进入老年代
  • 大对象直接进入老年代
  • 对象动态年龄判断:Eden过来的对象超过了幸存者区的1/2,直接进入老年代。
②Survivor存在的意义?
翻译:幸存者
意义:减少对象直接进入老年代的机会,减少Full GC的频率,从而尽可能减少GC停顿的发生;
③为什么需要划分两个Survivor出来?
     由于新生代采用复制算法决定的。因为新生代一般采用复制算法来回收内存,每次内存回收总是需要另一块空的区域(s0或s1,s0、s1轮流被使用)来存放GC存活下来的对象。

二、类加载器

类是模板,对象是具体的

1、加载类

  • 启动类(根)加载器
  • 扩展类加载器
  • 应用程序加载器

先在应用程序加载器找,再去扩展类加载器找,层层往上,当返回NULL的时候有两种情况:

  1. 不存在
  2. Java程序获取不到(c++写的程序Java当然找不到)

2、双亲委派机制

目的: 为了保证安全

查找顺序(从右至左开始找):

APP(应用程序加载器)->EXC(扩展类加载器)->BOOT(根加载器,最终执行)

若Boot里面没有这个类->EXC找,EXC里面也没有这个类->APP里面找

3、沙箱安全机制

类似于linux的权限保护

4、全盘负责委托机制

当个 ClassLoader加载一个类的时候,除非显示的使用另一个 Classloader,该类所依赖和引用的类也由这个 ClassLoader载入

5、分配担保机制

就是大于新生代50%的对象直接进入老年代

6、Native

调用C++方法

三、GC回收算法

1、引用计数法

给对象添加一个引用计数器,每当有一个地方引用,计数器就加1,当引用失效,计数器就减1。

为0就清理掉

这个方法实现简单、效率高,但是目前主流的虚拟机中没有选择这个算法来管理内存,因为如果两个对象互相引用对方,导致它们的引用计数器都不为0,就无法回收

2、复制算法

复制算法保证to永远是空的(谁空谁是To)

清理GC的时候将Eden里面存活的对象放入幸存者To区,然后将from区的对象也放入To区,这时候To区就变成了From区,原来的From就变成了To区(原来的对象复制到了To区,自己就没有对象了,谁空谁是To)

好处

​ 没有内存的碎片

坏处

​ 浪费了内存空间:to永远是空。假设对象100%存活(极端情况)复制算法

最佳使用场景

​ 对象存活度较低的时候;新生区;

3、标记清除算法

优点

​ 不需要额外的空间

缺点

​ 两次扫描,严重浪费时间,会产生内存碎片(那些缺口不好补)。

4、标记压缩算法

压缩实际上就是移动

先标记清除,再移动

5、可行性分析算法

通过一系列的称为GC Roots的对象作为起点,从这些节点开始向下搜索,节点所走过的路当一个对象到==GC Roots没有任何引用链相连的话==,则证明此对象时不可用的。
GC Roots根节点:

类加载器Thread虚拟机栈的局部变量表static成员常量引用本地方法栈的变量等等

image-20210806110922618

四、+GC收集器

JVM GC收集器成员

0、GC收集器三指标:

  • 占用的内存(Capacity)
  • 延迟(Latency)
  • 吞吐量(Throughput)

发展历程:Serial->Parallel->CMS->G1目前

1、Serial收集器

  1. Serial 收集器是最基本、发展历史最悠久的收集器,jkd1.3之前的唯一收集器
  2. 单线程
  3. 重点在它垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束

2、ParNew收集器

  • 新生代收集器,

  • CMS默认搭配,

  • 默认开启的线程数等于cpu数。

  • Serial的多线程版本。

  • ==并行==收集:多条垃圾收集器同时工作,用户线程等待

  • GC复制算法。

3、吞吐量优先收集器

  • 又称:Prallel Scavenge收集器

  • 新生代收集器

  • JDK8默认parallel Scavenge收集器

  • ==并行==收集**:多条垃圾收集器同时工作,**用户线程等待

  • GC复制算法

  • 特点:以吞吐量优先

    • 吞吐量 = 代码运行时间 / (代码运行时间 + 垃圾收集时间)
  • 高吞吐量垃圾收集时间小)可以最高效率利用CPU时间,适合在后台运算,而不是太多交互的任务

  • (其不适合需要与用户交互的程序,良好的响应速度能提升用户的体验,此种场景CMS效果更好)。

4、Serial Old收集器

  • 老年代收集器
  • 单线程
  • 标记清理算法
  • 用途:
    1. jdk1.5及以前与Prallel Scavenge搭配使用
    2. 作为CMS备用方案
    3. 用于Client模式(客户机模式)

5、Parallel Old收集器

  • JDK1.6 出现
  • 老年代收集器
  • 多线程
  • 标记清理算法
  • 用途
    • 代替Serial Old收集器;
    • Server模式,多CPU 的情况下
    • 注重吞吐量时采用**Prallel Scavenge**和它组合使用

6、CMS收集器

  • 全称:Concurrent Mark Sweep 并发低停顿收集器

  • ==并发==收集:用户线程垃圾收集器线程同时工作

  • 最低停顿时间【也就是指Stop The World的时间】为目的

    • Stop The World:暂停所有线程
  • 标记清理算法

  • 缺点:

    1. CMS收集器对CPU资源非常敏感
    2. 无法处理浮动垃圾(运行过程中产生的垃圾)
    3. 大量空间碎片产生
  • 整个收集过程

    1. :【初始标记】只标记没有跟GC ROOT 相连的对象 --> STW
    2. :【并发标记CMS收集器一边收集一边标记
    3. :【并发预先清除
    4. :【并发可能失败的预先清除
    5. :【最终重新标记】将所有垃圾进行标记,确保万无一失 --> STW
    6. :【并发清除】用户线程可以进行,同时清理垃圾
    7. :【并发重置】重新回到第一步

7、G1收集器

  • server模式( 面向服务器)
  • 满足小停顿大吞吐
  • 官方推荐
  • 引入分区的思路
  • 标记清除算法
    • 初始标记
    • 并发标记
    • 最终标记
    • 筛选回收

G1执行过程

将内存平分成差不多大的多个区域,一个对象来的时候,如果有一个空白区域,直接把你的新对象放入进去,然后这一个区域变成了新生代,然后这个新生代满了以后也会进行回收,回收完后进行转移,这个对象大小超过了老年代的45%,它会把这个对象直接放入Humongous(巨大)区域,因为新生代装不下,在老年代满了之后通过fullGC来释放

G1的进化特征

(1)并发:

G1能充分利用CPU多核来缩短STW时间,而且G1并发(让java程序继续执行)

(2)分代收集

​ 虽然G1不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。

(3)空间整合:

​ 与CMS的”标记-清理"算法不同,G1从整体是"标记整理"算法,但是从局部看是基于"复制"算法

(4)可预测的停顿:

​ 这是G1相对于CMS的另一个大优势,降低停顿时间是G1CMS共同的关注点,但G1除了这个,能让用户明确指定在一个长度为M毫秒的时间片段内。

​ 如果只设置10ms,那么当然10ms是收集不完的,它会优先回收价值最大的Region(区域)

G1收集器出现了GC一般是Mix GC,如果发生了FULL GC,那么一般是你的项目出现了问题

缺点

垃圾回收的次数变多,硬件要求高

三、G1回收步骤

(1)名词解析
  • Minor GC 从年轻代空间(包括 EdenSurvivor 区域)回收内存。
  • Major GC 是清理老年代。
  • Mixed GC 是在G1收集器中独有的,用于收集整个young gen以及部分old genGC
  • Full GC 是清理整个堆空间—包括年轻代、老年代及永久代(元数据空间)。
(2)回收步骤详解
G1 Young GC(STW)
  1. eden数据满了,则触发g1 YGC
  2. 并行的执行:
    • YGC 将 eden region 中存活的对象拷贝到survivor,或者直接晋升到Old Region中;将Survivor Regin中存活的对象拷贝到新的Survivor或者晋升old region。
  3. 计算下一次YGC edenSurvivor的尺寸
G1 Mix GC

G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为五个步骤:

  1. 初始标记(initial markSTW
    • 在此阶段,G1 GC 对根进行标记。
  2. 根区域扫描(root region scan
    • G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。
  3. 并发标记(Concurrent Marking
    • G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断
  4. 最终标记(RemarkSTW
    • 该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。
  5. 清除垃圾(CleanupSTW
    • 在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

8、怎么选择垃圾收集器

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100m,使用串行收集器
  3. 如果是单核,并且没有停顿时间的要求,串行或MM自己选择
  4. 如果允许停顿时间超过1秒,选择并行或者MM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器

官方推荐G1,性能高

五、STW

stop Thread Work,暂停一切线程;

轻GC和重GC的时候会暂停线程,防止在找垃圾的过程中产生新的垃圾(一开始找的时候不是垃圾,找到一半变成垃圾了,又不能重新从头找)

六、JVM调优

1、调优概念

JVM调优主要是为了减少GC,特别是full GC(重GC)

调整指标
  • 内存大小
  • 延迟:由于垃圾收集而引起的程序停顿时间。
  • 吞吐量:=运行代码时间/(运行代码时间+垃圾收集时间))

2、JVM调优工具

1、JVM自带的调优工具:Jvisualvm

2、阿里开发的调优工具:Arthas

七、类的生命周期

类中的静态加载顺序

静态变量/静态代码块(看谁写在前) > 构造方法之外的其他代码块 > 构造方法

image-20220331211335070image-20220331211230540

截图

1、加载

.class文件从磁盘读到内存【通过权限定名(包名+类名)读取的】

2、连接

(1)验证

验证字节码文件的正确性

​ 比如说格式元数据字节码符号引用验证

(2)准备

给类的静态变量分配内存,并赋予默认值

(3)解析

把常量池中的符号引用(class文件格式)替换成直接引用(指针指向地址

3、初始化

为类的静态变量赋予正确的初始值

八、强/软/弱/虚引用

img

1、强引用

​ 只要能够通过GC Root的引用链找到就不会被垃圾回收,对象未被强引用就被回收

2、软引用

跟缓存一样

java中使用SoftRefence来表示软引用,对象软引用内存不足才释放

​ 如果某个对象与软引用关联,那么JVM只会在内存不足的情况下回收该对象

3、弱引用

任何情况必被释放

4、虚引用

​ 就和没有引用一样,任何情况必被释放

​ 创建的时候会关联一个引用队列,这样子用户就可以决定要不要释放它

九、判断常量是否废弃

常量池中字符串abc,如果当前无任何引用,就说明常量abc就是废弃常量,如果这时发生内存回收而且有必要的话,“abc"会被系统清理出常量池

十、怎样让一个对象自救

就是说 释放后再复活

一个类在被回收之前,他是没有任何引用的,但是你想让他复活,就是GC回收的时候又持有一个引用,就可以在类里面定义一个Finalize方法,但是这个方法只能自救一次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值