学习笔记:JVM

本文仅作为自己的学习资料使用,如果有问题请各位大佬指正。

JVM主要组成部分

在这里插入图片描述

类加载器子系统

类的加载

  • 1、加载:将类的class文件读入内存,并为之创建一个java.lang.class对象

  • 2、连接:把类的二进制数据合并到JRE中

    • a 验证:检验被加载的类是否有正确的内部结构,并和其他类协调一致
    • b 准备:为类的类变量分配内存并初始化为默认值
    • c 解析:将类的二进制数据中的符号引用替换为直接引用
  • 3、 初始化:对类变量进行初始化

  • 类加载器
    作用:加载Class文件
    加载:启动类加载器 扩展类加载器 应用程序类加载器 遵循双亲委派机制加载类文件
    好处:防止内存中出现多份同样的字节码(安全性角度)

  • 类的加载:指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装在方法区内的数据结构

运行时数据区

方法区(Method Area)

  • 线程共享
  • 线程安全:两个线程同时加载一个类,只能有一个线程被允许去加载这个类,另一个必须等待
  • 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
  • 方法区的实现
    Java8之前永久代(PermGen)
    Java8之后元空间(Metaspace):元空间存储的是类的元数据信息、元空间不在虚拟机中,而是使用本地内存
  • 替换的好处:
    一、字符串存在永久代中,容易出现性能问题和内存溢出
    二、永久代会为GC带来不必要的复杂度,并且回收效率偏低

堆(Heap)

  • 线程共享 堆的物理地址是不连续的。因此性能慢些。所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
  • 主要存放对象实例(类、方法、常量、实例变量)和数组 当Java创建一个类的实例对象或数组时,都在堆中为新的对象分配内存

虚拟机栈(Stack)

  • 线程私有,生命周期和线程一致
  • 每个方法执行同时会创建一个栈帧:
    • 局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和 returnAddress 类型(指向了一条字节码指令的地址)
    • 操作数栈:存放方法执行过程中产生的中间结果
    • 动态链接:
    • 方法出口:
      退出方式一:执行引擎遇到任意一个方法返回的字节码指令
      退出方式二:方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。

本地方法栈

  • 线程私有,调用本地方法接口时启用(native):带native关键字,说明java的作用范围达不到了,会去调用底层C语言的库,进入本地方法栈调用Java本地接口JNI
  • JNI作用:提供了若干的API实现了Java和其他语言的通信

程序计数器

  • 每条线程都会有一个独立的程序计数器
  • 当前线程所执行的虚拟机字节码的行号指示器
  • 唯一一个没有规定OutOfMemoryError(内存不足错误)的区域

GC垃圾回收

识别垃圾

  • 引用计数法
    对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为 0),则此对象可回收
    缺点:无法解决循环引用

可达性算法

一、现代虚拟机基本都是采用这种算法来判断对象是否存活,可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点…(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」,会被 GC 回收。
二、当对象不可达(可回收),当发生GC时,会先判断对象是否执行了 finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收!
注意: finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收。

三、可作为GCRoot的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态变量和常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

垃圾回收算法

标记清除算法

1.先根据可达性算法标记出相应的可回收对象
2.对可回收的对象进行回收
缺点:内存碎片

复制算法

1.把堆等分成两块区域, A 和 B,区域 A 负责分配对象,区域 B 不分配, 对区域 A 使用以上所说的标记法把存活的对象标记出来
2.把区域 A 中存活的对象都复制到区域 B(存活对象都依次紧邻排列)最后把 A 区对象全部清理掉释放出空间
缺点:效率低

标记整理法

前面两步和标记清除法一样,不同的是它在标记清除法的基础上添加了一个整理的过程 ,即将所有的存活对象都往一端移动,紧邻排列,再清理掉另一端的所有区域
缺点:效率低

分代收集

根据对象存活周期的不同将堆分成新生代老生代(Java8以前还有个永久代),默认比例为 1 : 2,新生代又分为 Eden 区, from Survivor 区(简称S0),to Survivor 区(简称 S1),三者的比例为 8: 1 : 1,这样就可以根据新老生代的特点选择最合适的垃圾回收算法,我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC),老年代发生的 GC 称为 Old GC(也称为 Full GC)。
工作原理:
1.对象在新生代的分配与回收
对象一般分配在 Eden 区,当 Eden 区将满时,触发 Minor GC
大部分对象在短时间内都会被回收, 所以经过 Minor GC 后只有少部分对象会存活,它们会被移到 S0 区,同时对象年龄加一(对象的年龄即发生 Minor GC 的次数),最后把 Eden 区对象全部清理以释放出空间
当触发下一次 Minor GC 时,会把 Eden 区的存活对象和 S0(或S1) 中的存活对象(S0 或 S1 中的存活对象经过每次 Minor GC 都可能被回收)一起移到 S1(Eden 和 S0 的存活对象年龄+1), 同时清空 Eden 和 S0 的空间。
若再触发下一次 Minor GC,则重复上一步,只不过此时变成了 从 Eden,S1 区将存活对象复制到 S0 区,每次垃圾回收,都是从 Eden ,S0(或S1) 将存活对象移动到 S1(或S0)。也就是说在 Eden 区的垃圾回收我们采用的是复制算法,因为在 Eden 区分配的对象大部分在 Minor GC 后都消亡了,只剩下极少部分存活对象(这也是为啥 Eden:S0:S1 默认为 8:1:1 的原因),S0,S1 区域也比较小,所以最大限度地降低了复制算法造成的对象频繁拷贝带来的开销。
2.对象何时晋升老年代

  • 当对象的年龄达到了我们设定的阈值,则会从S0(或S1)晋升到老年代
  • 当某个对象分配需要大量的连续内存时,此时对象的创建不会分配在 Eden 区,会直接分配在老年代,因为如果把大对象分配在 Eden 区, Minor GC 后再移动到 S0,S1 会有很大的开销(对象比较大,复制会比较慢,也占空间),也很快会占满 S0,S1 区,所以干脆就直接移到老年代.
  • 在 S0(或S1) 区相同年龄的对象大小之和大于 S0(或S1)空间一半以上时,则年龄大于等于该年龄的对象也会晋升到老年代。
    3.空间分配担保
    在发生 MinorGC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,那么Minor GC 可以确保是安全的,如果不大于,那么虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则进行 Minor GC,否则可能进行一次 Full GC。
    4.Stop The World
    如果老年代满了,会触发 Full GC, Full GC 会同时回收新生代和老年代(即对整个堆进行GC),它会导致 Stop The World(简称 STW:在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。),造成挺大的性能开销。
    5.Safe Point:发起GC的合适的时间点
  • 循环的末尾
  • 方法返回前
  • 调用方法的call之后
  • 抛出异常的位置

垃圾收集器种类

在这里插入图片描述

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

  • Parallel Old收集器 (标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

  • G1(Garbage First)收集器 (标记-整理算法):Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

JVM调优

常用调优工具

jconsole:用于对 JVM 中的内存、线程和类等进行监控;
jvisualvm:JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。

常用调优参数

-Xms2g:初始化推大小为 2g;

-Xmx2g:堆最大内存为 2g;

-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;

-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;

–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;

-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;

-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;

-XX:+PrintGC:开启打印 gc 信息;

-XX:+PrintGCDetails:打印 gc 详细信息。

参考文章:
Java虚拟机(JVM)面试题大集合
学习JVM是如何从入门到放弃的?(修订版)
【JVM故事】了解JVM的结构,好在面试时吹牛
看完这篇垃圾回收,和面试官扯皮没问题了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值