note · 3,915

JVM Java 虚拟机

JVM:类加载、双亲委派、ThreadLocal、垃圾回收机制与收集器。

JavaJVM笔记

JVM Java虚拟机

元空间是方法区的实现。

  • 线程私有区域
    • 程序计数器(PC Register)
    • Java虚拟机栈(JVM Stack) 存储栈帧 当程序调用函数的时候 形成栈帧 入栈
    • 本地方法栈(Native Method Stack)
  • 线程共享区域
    • 堆(Heap) 字符串常量池在这 实例对象和数组都在堆
    • 方法区(Method Area)(元空间实现 ) 运行时常量池在这 类的 “元数据” 和相关静态资源提供存储空间 已被 JVM 加载的类信息、常量、静态变量、即时编译器

什么是程序计数器

线程私有的,每个线程有一份,内部保存的字节码的行号。核心作用存放下一条将要执行的指令的内存地址

类加载机制

家宴准备了西式菜 加载 验证 准备 解析 初始化

加载 -> 连接 -> 初始化

  • 加载 读取Class文件,将其转化为某种静态数据结构存储在方法区,并在堆中生成一个便于用户调用的java.lang.Class类型的对象的过程。
  • 连接
    • 验证 确保class文件中的字节流包含的信息,符合当前虚拟机的要求,保证这个被加载的class类的正确性,不会危害到虚拟机的安全。
    • 准备 为类中的静态变量分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了。
    • 解析 解析阶段是虚拟机将常量池的「符号引用」直接替换为「直接引用」的过程。Java通过 动态解析(运行的时候来解析)实现后期绑定和多态。
  • 初始化 不是构造函数 而是class层面 JVM 会执行类的 <clinit>() 方法(类构造器方法) 如静态变量赋值 执行静态代码块

双亲委派

向上委托,向下加载

  • Bootstrap ClassLoader
  • Extension ClassLoader
  • Application ClassLoader

作用:

  • 避免重复加载 (当向上委托的时候会先查看自己有没有加载过)
  • 保证Java核心类库绝对安全
Tomcat如何打破双亲委派的

WebAppClassLoader 重写了 ClassLoaderloadClass() 方法,并实现了与标准双亲委派相反的加载顺序,先在本地缓存中查找,再委派给ExtClassLoader看是不是基础安全类,如果不是则尝试自己加载。如果加载类是核心类就不会自己加载。

为了实现多个 Web 应用之间的应用隔离热部署功能。

深拷贝和浅拷贝是什么

  • 浅拷贝并不是真的拷贝,只是复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象

深拷贝如何实现

  1. 使用“拷贝构造函数” (Copy Constructor) 这是一种设计模式,你为你的类提供一个“拷贝构造函数”,它接收一个同类型的对象,然后手动(Manually)复制所有字段。
public User(User other) {
        this.name = other.name;
        // 关键:不是 this.address = other.address;
        // 而是为 address 创建一个新副本
        this.address = new Address(other.address);
}
  1. 使用 JSON 序列化 (推荐的实用方案) 这是目前最流行、最简单的“偷懒”方式,利用第三方库(如 Jackson, Gson, Fastjson)将对象序列化为 JSON 字符串,然后再反序列化为新对象。
  2. 使用 Java 原生序列化 (Serializable) 不推荐
  3. 重写 clone() 方法 (不推荐)

介绍一下四种引用

  • 强引用
    • 垃圾回收器不会回收被引用的对象,哪怕内存不足时,JVM 也会直接抛出 OutOfMemoryError,除非赋值为 null。
  • 软引用
    • 软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。
  • 弱引用
    • 弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
  • 虚引用
    • 虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收

ThreadLocal

privatestatic ThreadLocal<String> threadLocal = new ThreadLocal<>();

每一个Thread内部有一个ThreadLocalMap ThreadLocalMap<ThreadLocal,T>

  • Key :ThreadLocal本身 为一个弱引用
  • Value :手动Set的Value 为强引用

ThreadLocal使用不当会造成内存溢出问题

ThreadLocal tl = new ThreadLocal() 这句代码所在的方法执行完毕,tl 这个栈引用被销-毁了),在下一次垃圾回收(GC)发生时,这个 ThreadLocal 对象就会被回收。

在使用完 ThreadLocal 后,务必手动调用其remove()方法

  • 如果 Key 是强引用: 只要线程不死(比如线程池),那么 ThreadLocal 对象本身永远无法被回收,Value永远无法被回收。这是 100% 的、绝对的、永久的内存泄漏。
  • 因为 Key 是弱引用: 当外部不再使用 ThreadLocal 时,下次 GC 至少能把 ThreadLocal 对象本身给回收掉。虽然 Value 暂时留下了,但泄漏的范围缩小了。

*垃圾回收

  • minorGC 新生代垃圾回收
  • majorGC 老年代垃圾回收 由于majorGC发生时通常伴随着minorGC,因此也被称为fullGC

搜集算法 判断垃圾的方法有哪些

  • 引用计数法 已淘汰
    • 每一个对象有一个引用计数器
    • 缺点显著,两个对象可能相互引用
  • 可达性分析算法 实际采用的方法
    • 从根对象GC Roots触发,向下搜素,走过的路径就叫引用链。

哪些是对象是GC Roots

  1. 虚拟机栈 本地方法栈 中的对象
  2. 方法区中的 static 对象 final 对象
  3. 被同步锁持有的对象
  4. JVM内部引用 如类加载器
  5. 正在活跃的线程

回收算法

  • 标记清除法 先标记哪些是垃圾再统一回收
    • 会产生大量碎片
  • 标记复制算法 两块区域 将原取余的非垃圾复制到新取余,清除原区域。
    • 会浪费一半的内存空间 两个幸存者区就是这样
  • 标记整理算法 FullGC时候会执行碎片整理 将非垃圾往前挪
    • 性能较差 适合老年代
  • 分代回收算法:分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的 GC 次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是 15,可以通过参数 -XX:MaxTenuringThreshold 来设定)后,如果对象还存活,那么该对象会进入老年代。

垃圾回收器

堆是如何分布的

  • 新生代
    • Eden区
    • Survivor区
  • 老年代

CMS - 老年代垃圾回收器

Concurrent Mark Sweep 内存小 对延迟极其敏感

核心:它是以获取最短回收停顿时间(STW)为目标的收集器。

当老年代内存不足的时候触发

  1. 初始化标记 将整个年轻代都认为是GC Roots(广义的GC Root) STW
  2. 并发标记(递归标记 三色标记法) GC线程和用户线程并发
    1. 这时候新产生的或发生变化的对象会被标记为Dirt Card
  3. 重新标记 标记Dirty Card STW
  4. 并发清除 重新初始化CMS中的数据 为下一次CMS做处理

CPU处理核数 = (处理器核心数+3)/ 4 至少占用25%

致命缺陷:

  • CPU使用频率高
  • 标记-清除算法,会产生大量内存碎片
  • 会产生浮动垃圾:在“并发清除”阶段,业务线程还在运行,这期间产生的新垃圾,CMS 这次是收不掉的,只能等下次 GC,这就是浮动垃圾
  • 停顿时间有非常苛刻的要求
  • 堆内存不够

G1 - Gabage-First - JDK9开始使用

用户可以指定期望的 GC 停顿时间,G1 会根据这个时间,优先回收那些垃圾最多、收益最大的 Region(这就是 Garbage-First 名字的由来)。

采用标记复制算法

把堆分成2048个Region

还是分了Egen、S0、S1、老年代,额外的Humongous大对象区

  1. 初始标记 从GC Root出发 MaxGCPauseMillis STW
  2. 并发标记
  3. 最终标记 SATB(Snapshot-At-The-Beginning)算法 STW
  4. 筛选回收 STW

G1垃圾回收器 1.9后的默认回收器

  • 大内存
  • 内存碎片敏感
  • 性能平衡 停顿时间和吞吐量平衡