• JVM与垃圾回收


    JVM内存管理

    JVM是一种规范。JVM的跨平台性。JVM的语言无关性。

    常见的JVM实现:Hotspot(Oracle公司)、Zing(C4垃圾回收算法)、毕昇(华为公司)

    JVM运行时数据区域

    运行时数据区域分为线程共享区和线程私有区

    线程共享区包含:方法区、堆。运行时常量池在方法区中

    线程私有区包含:虚拟机栈、本地方法栈、程序计数器

    方法区(存储类加载Class类、静态常量(基本数据类型)、常量(基本数据类型)、static静态代码块、运行时常量池)

    成员变量指向(对象)在类加载的时候不会执行,运行时成员变量会存储在堆中,

    堆(存储对象实例)

    栈(存储局部变量、引用对象)先进后出

    运行时数据区外还存在一个直接内存,又称为堆外内存。

    虚拟机栈 (先进后出)

    虚拟机栈存放的方法的栈帧

    虚拟机栈存储当前线程运行java方法所需的数据、指令、返回地址

    -Xss 256K 限制大小

    栈溢出一般是出现了死递归

    栈帧

    栈帧包含:局部变量表、操作数栈(不会被回收,会被复用)、动态连接、完成出口(返回地址)

    程序计数器

    程序计数器记录的是字节码运行的行号,记录的是偏移量

    直接内存

    Java中直接内存(堆外内存)有哪些:使用Unsafe操作本地内存、JNI或者JNA程序操作了本地内存

    //直接分配128的直接内存
    ByteBuffer buffer=ByteBuffer.allocateDiret(128*1024*1024);
    
    • 1
    • 2

    直接内存的申请绕过了JVM的垃圾回收。好处是速度会稍微快点。缺点就是容易忘记回收造成内存泄漏。多线程申请内存会造成覆盖。

    JVM内存处理全流程
    1. JVM申请内存
    2. 初始化运行数据区
    3. 类加载 会把类,静态变量和常量以及加了static的东西放入方法区
    4. 执行方法 运行方法创建栈,创建虚拟机栈。执行main方法的栈帧
    5. 创建对象

    JVM编译:

    1. .java文件经过javac指令编译成.class文件

    2. 类加载把.class文件加载到方法区交由JVM执行引擎去执行

    3. jvm把字节码翻译成机器码

    堆空间分代划分

    分为老生代、新生代(Eden、From、To)。老生代存放的是经过多次GC垃圾回收没有回收掉的对象。

    新生代经过15次GC后会晋升为老生代。对象经历一次垃圾回收,没有被回收掉。age+1。age达到15 晋级老生代。底层记录对象的年龄是用4位二进制,最大值位1111也就是15。

    JHSDB工具查看内存。

    JVM哪些区域会发生内存溢出:堆、虚拟机栈、方法区

    对象的分配

    JVM中对象的创建过程

    类加载----》

    检查加载(JVM遇到一条字节码new指令)-----》

    分配内存(划分内存的方式:指针碰撞、空闲列表。解决并发安全:CAS加失败重试、本地线程分配缓冲)----》

    内存空间初始化(“零”值)—》

    设置(对象头)----》

    对象初始化(构造方法)

    使用指针碰撞内存必须是规整的。如果内存不规整,或者存在内存碎片就需要使用空闲列表方式。

    CAS:是CPU的一个指令。多线程使用时使用CAS比较和交换。

    本地线程分配缓冲:预先给每个线程划分一块内存区域(java8以后使用此种方式,禁止以后使用的就是CAS的方式)

    对象分为:

    1、对象头(Mark Word存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳)、类型指针、若为对象数组,还应有记录数组长度的数据)

    2、实例数据

    3、对象填充(非必须)

    对象的访问定位:使用句柄或者直接指针的方式。目前主流的JVM使用的是直接指针方式

    垃圾回收

    垃圾回收GC,需要判断对象的存货

    判断对象的存活 :
    • 引用计数算法 没法解决循环引用的关系

    • 可达性分析(根可达)

    • 经过判断对象存活后还会有一个Finalize 缓冲期。Finalize是类的自我拯救。因为是另起了一个线程,所有优先级比较低 ,不推荐使用。

    GC roots:

    从根找可达 根有 静态变量----》线程栈变量-----》常量池----》JNI(指针)—》内部引用(class对象、异常对象Exception。类加载器)—》同步锁----》内部对象----》临时对象(跨代引用)

    class对象要回收的条件。比较苛刻,要满足所有条件:
    1. class new 出的所有对象都要回收

    2. 对应的类加载器 也要回收

    3. 类 Java.lang.class对象,

    4. 任何地方没有引用,并且无法通过反射调用这个类的方法

    5. 参数控制

    各种引用
    • 强引用 = 不会被回收
    • 软引用 SoftReference 发生垃圾回收,可能会回收
    • 弱引用 WeakReference 只要垃圾回收,就会被回收
    • 虚引用 PhantomReference 随时都会被垃圾回收回收
    对象的分配策略

    几乎所有的对象都在堆中进行分配,对象触发逃逸分析会把对象会分配到栈中(java 10000次)

    对象的分配原则
    • 对象优先在Eden分配 (常用)

    • 空间分配担保

    • 大对象直接进入老年代

    • 长期存活的对象进入老年代 (常用)

    • 动态对象年龄判定

    虚拟机的优化技术
    • 逃逸分析+触发JIT(热点数据) 对象会分配到栈中

    • 本地线程分配缓冲

    垃圾回收算法
    • 复制算法 加强版(Appel式的复制回收算法)用于新生代垃圾回收

    新生代内存分配规则:Eden:form:to=8:1:1

    特点:实现简单、运行高效。没有内存碎片。空间利用率只有一半

    • 标记-清除算法 用于老生代

    根据可达性分析标记引用的对象,然后清除没有引用的对象

    特点:位置不连续,产生碎片。可以不暂停。

    • 标记-整理算法

    根据可达性分析标记引用的对象,先标记,后整理内存空间。 最后整体清理

    特点:没有内存碎片。指针需要移动。

    垃圾回收器
    • 单线程垃圾回收器
    • 多线程并行垃圾回收器
    • 并发垃圾垃圾回收器
    举例常见的垃圾回收器
    • Serial 回收新生代 复制算法 回收器类型属于单线程

    • Serical Old 回收老年代 标记整理算法 回收器类型属于单线程

    • Paraller Scavenge 回收新生代 复制算法 回收器类型属于多线程并行垃圾回收器

    • Paraller Old 回收老年代 标记整理算法 回收器类型属于多线程并行垃圾回收器

    • PaeNew 回收新生代 复制算法 回收器类型属于多线程并行垃圾回收器

    • CMS 回收老年代 标记清除算法 回收器类型属于并发的多线程回收器

    • G1 跨新生代和老年代 标记整理+化整为零算法 回收器类型属于并发的多线程回收器

    CMS的标记清除算法的过程减少了暂停卡顿:初始标记 并发标记 重新标记 并发清除

    CMS中的问题:CPU敏感 浮动垃圾 内存碎片

    面试题

    常量池(方法区):

    • Class常量池(静态常量池)

    • 运行时常量池

    • 字符串常量池

  • 相关阅读:
    C++指针
    天宇优配|百股涨停再现,沪指盘中创近两个月新高
    Excel中的HLOOKUP、VLOOKUP、XLOOKUP函数
    算法学习:LeetCode-593. 有效的正方形
    operator=中自我赋值和异常安全问题
    Harbor(V2.8+) 登录时报错 net/http: TLS handshake timeout
    【Linux】进度条和git命令行
    容器安全是什么?
    深拷贝和浅拷贝
    【音视频基础】视频基础理论
  • 原文地址:https://blog.csdn.net/qq_31309689/article/details/126404298