JVM快速阅读笔记

JVM

这篇笔记写给自己在实习之前看的,有什么不对地方请广而告知,我会持续更新修改。

本片文章字数较多,适合快速浏览,有不详细的请另行百度


运行时数据区域

  • 程序计数器: 指示执行某条指令
  • 虚拟机栈(栈): 每个栈帧对应一个方法包括局部变量表、操作数栈、运行时的常量池、
  • 本地方法栈:为使用到的本地操作系统方法服务
  • java堆:线程共享的一块内存区域,对象实例和数组都在这里分配内存
  • 方法区(hotspot包含运行时常量池):已经加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • *直接内存

Object o = new Object();

  • 对象创建过程:

    new->检查这个指令的参数是否能在常量池中定位到一个类的符号引用->检查这个符号引用代表的类是否已被加载、解析和初始化->若无则类加载,有则进行分配内存(指针碰撞/空闲列表)-> 方法执行初始化字段。

  • 对象的内存布局:对象头、实例数据、对齐填充
  • 对象访问
    • 句柄池:两个指针,对象数据地址、对象类型地址。优点:当对象被移动时(垃圾回收时),只改变句柄中的指针,实例不变。
    • 直接指针:实例存储了类型数据的地址;优点:速度快,少一次指针定位的时间;

新生代和老生代

  • 新生代GC(Minor):对象大多都朝生熄灭
  • 老生代GC(Major):老年代经常伴随一次MinorGC,比Minor慢10倍

OOM (OutOfMemoryError) & SOFE(StackOverFlowError)

  • 内存泄漏:分配出去的内存没有回收,失去了对该区域的控制,因而造成了资源浪费,虽然有GC,但是还是会有泄漏。
  • 内存溢出:程序所需要的内存超出了系统所能分配的内存的上限;

GC

  • 判断对象是否可回收
    • 引用技术算法为什么不好?(对象之间循环引用问题):

      若被引用一次,计数器+1,引用失效-1,为0就回收。

    • 跟搜索算法(GC Roots)

      GCRoots可以是:虚拟机栈的引用对象、方法去的类静态属性引用的对象、方法区常量引用的对象、本地方发栈中JNI引用的对象;

      判断对象到GCRoots是否可达,不可达则回收;

    • 强引用:直接通过new来创建的对象

    • 软引用:SoftReference创建的,内存不足(溢出)则回收
    • 弱引用:WeakReference创建的,下一次垃圾收集就会回收
    • 虚引用:PhatomReference创建的,总会回收,主要用来当回收时获取一下系统通知;
  • finalize()方法与GC二次标记

    第一次标记筛选:没有必要执行finalize()方法:没有覆盖finalize()/已经被调用过,直接回收

    自救:对象覆盖finalize(),把自己赋值给某个类变量或对象成员变量;第二次标记时他会被移除

  • 回收方法区(永生代回收1.废弃常量2.无用的类)

    • 无用的类:
      该类所有实例已经被回收,java堆中不存在该类的实例

      加载该类的ClassLoader被回收了

      对应的Class对象没有任何地方引用,无法反射访问该类

    在大量使用反射、动态代理、CGLib等框架的场景,需要具备类卸载的功能,保证永久代不会溢出。

  • 垃圾收集算法
    • 标记-清除算法(标记,清理)
    • 复制算法(分一半内存,存活的复制到另一块内存/新生代、老生代90%)
    • 标记-整理算法(标记,存活对象向一端移动,再清理)
    • 分代收集算法(新生代用复制,老生代用标记-清理或整理)

垃圾收集器

并行: 多条垃圾收集线程并行工作,用户线程暂停

并发: 用户线程和垃圾收集线程同时执行,垃圾收集线程运行再另一CPU上.

  • Serial :单线程收集暂停其他所有线程
  • ParNew :多线程收集暂停其他所有线程
  • Parallel Scavenge :吞吐量优先,自适应调节策略
  • Serial Old:Serial老年代版本,采用‘标记-整理’清理老年代;Parallel Old
  • CMS :以获取最短回收停顿时间为目标,采用‘标记-清除’

    初始标记、并发标记、重新标记、并发清理、重置线程

    缺点:采用标记-清除,

  • G1 :采用‘标记-整理’,优先回收垃圾最多的区域(已分成大小固定的块),局部看是基于复制算法实现两个Region之间

    初始标记-并发标记-最终标记-筛选回收

    初始标记和最终标记的区别:初始标记是单线程,最终标记是并行的

内存分配和回收策略

  • 对象优先再Eden分配
  • 大对象直接进入老年代:
    大量连续内存空间的java对象
  • 长期存活的对象将进入老年代:
    每个对象都有年龄计数器,每熬过一次MinorGC兵器Survivor空间能够容纳,则年龄+1,默认超过15岁就晋升
  • 动态对象年龄判定:
    Survivor空间中相同年龄的所有对象大小大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代;
  • 空间分配担保
    在发生MinorGC前,检测之前每次晋升为老生代的平均大小是否大于老年代的剩余空间,大于则进行FullGC,否则查看HandlPromotionFailure设置是否允许担保失败,允许则还是MinorGC,不允许则FullGC;

虚拟机执行子

  • Class文件结构:8个字节数据流、
    • 魔数:头4个字节,确定是否能被虚拟机接受
    • Class版本号:魔数后的4个字节,前两个是次版本,后面是主版本
    • 常量池:开头u2数据类型,从1开始计数;主要放字面量(类和接口全限定名、字段名称描述符和方法名称描述符)和符号引用;省略很多..
    • 访问标志:识别类和接口的访问信息
    • 类索引、父类索引和接口索引:类和父类都是一个u2类型,接口是一组u2;用来确定类的继承关系
    • 字段表集合:描述接口或类中声明的变量
    • 方法表集合
    • 属性表集合:Code;Exceptions;Line Number Table;Local Variable Table;Source File;Constant Value;InnerClasses;Deprecated和Synthetic;
  • 类加载机制

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

    连接:验证-准备-解析

    • 过程
      • 四种情况必须立即进行“初始化”(加载、验证、准备先开始):

        • 遇到new、getstatic、putstatic或invoke static字节码指令时
        • 反射调用时
        • 初始化一个类,父类没初始化时
        • 需要制定一个要执行的主类
      • 加载:1.通过全限定名获取定义此类的二进制字节流 2.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构 3. 生成代表该类的Class对象

        全限定名:包名.类名

      • 验证:文件格式、元数据、字节码(最复杂,保证安全)、符号引用;此阶段可省略

      • 准备:为类变量(static修饰的)分配内存并设置变量初始值0
      • 解析:将常量池内的符号引用替换为直接引用;

        (符号引用就是一组符号来描述所引用的目标)直接引用就是指针、相对偏移量、一个能简介定位到目标的句柄;

      • 初始化:类加载的最后一步,开始执行类中定义的Java程序代码

        ()方法:所有类变量的赋值动作和静态语句块的语句合并产生。必须回调用父类的();如果没有静态语句块,也没有对变量的赋值操作,那么可以没有()方法;接口不需要执行父类的(),除非父类定义的变量使用时;虚拟机回保证一个类的()方法在多线程同步;

      • 使用

      • 卸载

      加载和连接阶段可交叉运行,加载未完成,连接可开始,

    • 类加载器
      • 双亲委派模型:启动类<-扩展类<-应用程序<-自定义类

方法重载和方法重写

  • (重载)静态分派:静态类型变化,实际类型变化
  • (重写)动态分派:多态性的表现

编译优化技术

  • 公共子表达式消除
  • 数组边界检查消除
  • 方法内联
  • 逃逸分析(主要):分析对象动态作用域

    方法逃逸:当一个对象在方法中定义后,可能被外部方法引用,例如作为参数传递到其他方法中;
    线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量。

内存模型

作用:用来屏蔽掉各种硬件和操作系统的内存访问差异,实现java跨平台访问内存;

  • 模型

    • 主内存:存储所有变量;对应Java堆中对象实例数据部分
    • 工作内存:每条线程都有自己的工作内存,保存变量的主内存副本拷贝;对应虚拟机栈中部分区域
  • volatile
    • 可见性
    • 禁止指令重排序
  • 原子性、可见性、有序性
    • 原子性:除了lock\unlock之外的6种保证原子性,
    • 可见性:一个线程修改了共享变量的值,其他线程立即知道(Volatile、Synchronize、final)
    • 有序性:本线程观察是有序的,观察另一个线程时无序的

线程

  • 内核线程
  • 用户线程
  • 用户线程+轻量级进程混合

  • 线程调度
    • 协同式:线程完成后切换到其他线程
    • 抢占式
  • 状态转换
    • 新建
    • 运行
    • 无期限等待
  • 线程安全和锁优化

    • 线程安全
      • 不可变:使用final
      • 绝对线程安全
      • 相对线程安全:通常而言,一般不需要额外做保障措施
      • 线程兼容:本身不是线程安全,但可以正确使用同步手段
      • 线程对立:不管怎么采取同步措施,都无法并发使用

      • 实现方法
        • 互斥同步:使用SynChronize,在同步区前后生成monitorenter和monitorexit字节码指令;

          1. 在执行monitorenter时,首先尝试获取对象的锁,如果没锁,或者线程已经拥有该对象的锁,所得计数器+1,相反,在执行monitorexit时,就-1,当计数器为0,锁就释放了;
          2. 如果获取对象锁失败了,当前线程就会阻塞等待,直到另一个线程释放

          也可以使用(重入锁)ReentrantLock来实现

        • 非阻塞同步
        • 无同步方案
          • 可重入代码
          • 线程本地存储
    • 锁优化
      • 自旋锁/自适应自旋
      • 锁消除:消除不存在共享数据竞争的锁
      • 锁粗化:扩大锁的范围
      • 轻量级锁:无竞争时使用CAS消除同步使用的互斥量
      • 偏向锁:无竞争的情况下把同步都消除掉,不做CAS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值