文章目录
  1. 1. 一、制造各种溢出的场景
  2. 2. 二、回收方法
  3. 3. 三、HotSpot实现
  4. 4. 四、内存分配与回收策略
  5. 5. 五、类加载机制
  6. 6. 六、类加载器
  7. 7. 七、虚拟机字节码执行引擎
  8. 8. 八、Java语法糖
  9. 9. 九、先行发生
  10. 10. 十、线程安全
  11. 11. 十一、线程安全实现
  12. 12. 十二、锁优化

一、制造各种溢出的场景

  • Java堆溢出:

    • 操作:不停向List中add数据
    • 参数:-Xmx -Xms
  • 虚拟机和本地方法栈溢出:

    • 操作:无限递归
    • 参数:-Xss
    • Tips:单线程情况下内存无法分配都提示StackOverflowError
    • 如果是建立多线程导致内存溢出,在线程数量无法减少的情况下,可以减少最大堆容量和栈容量来优化
  • 方法区和运行时常量池溢出:

    • 操作:无限String.intern
    • 参数:-XX:PermSize
    • Tips:JDK1.7中,intern方法不会再复制实例,只在常量池中记录首次出现的实例。对于Str.intern()==Str的结果,可能为true(首次出现)或者false(非首次出现时)
  • 本机直接内存溢出:

    • 操作:递归调用unsafe.allocateMemory(x)
    • 参数:-XX:MaxDirectMemorySize

二、回收方法

  • 堆回收:可达性分析(从GC Roots到这个对象不可达)
  • 哪些是GC roots:

    • 虚拟机栈中局部变量表中的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • JNI引用的对象
  • 引用类型的种类:

    • 强引用:普遍存在,回收不了
    • 软引用:快溢出异常时会回收一波
    • 弱引用:一旦发生GC就干掉
    • 虚引用:有虚引用的话回收有个通知,最弱
  • 方法区回收:

    • 废弃常量:当前没有对象引用的常量
    • 无用的类:
      • 该类的所有实例都已经被回收
      • 加载该类的ClassLoader已经被回收
      • 该类的java.lang.Class没有在任何地方被使用(反射)
    • 特例:-127到127的int类型,缓存在其中

三、HotSpot实现

  • 准确性GC:使用一组OopMap得知哪些地方存放对象引用,加快了GC Roots的枚举
  • SafePoint:程序只有在到达安全点时才能开始GC

    • 抢先式中断:中断所有线程,如果有不在安全点的线程,恢复之让其到达安全点
    • 主动式中断:设置中断标志,各个线程主动轮询
  • SafeRegion:安全区域,解决挂起线程的中断问题

  • Serial收集器:简单高效,适合Client
  • ParNew收集器:多线程并发的收集器
  • CMS垃圾收集器:

    • 初始标记:需要STW,速度快
    • 并发标记:时间较久
    • 重新标记:需要STW,修正并发标记期间的一些变化
    • 并发清除:真正进行垃圾收集
    • 缺点:CPU资源敏感;浮动垃圾无法处理;基于标记清除,会产生内存碎片
  • G1垃圾收集器

四、内存分配与回收策略

  • 对象优先在Eden区分配

    • MinorGC:新生代的较为频繁的GC,时间短
    • MajorGC:老年代GC
  • 大对象直接进入老年代

    • 避免Eden和Survivor之间发生大量复制
    • 代码编写时应避免短命的大对象
  • 长期存活的对象进入老年代

    • 新生代中熬过一次MinorGC增加年龄一次
    • 动态年龄判断:相同年龄所有对象大小超过Survivor的一半
  • 空间分配担保

    • 处理MinorGC后剩余过多,其中一个Survivor放不下的问题

五、类加载机制

  • 加载:

    • 通过类的全限定名得到二进制流
    • 将字节流代表的静态存储结构转为方法区的运行时数据结构
    • 内存中生成一个java.lang.Class对象,作为方法区类的数据的访问入口
  • 验证:确认字节流信息符合虚拟机要求

    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证
  • 准备:为类变量分配内存,设定初始值

  • 解析:符号引用
  • 初始化:
    • 静态语句块只能访问定义在块前的变量,定义在之后的变量,在静态语句块中只能定义不能访问
    • 父类静态-子类静态-父类赋值-子类赋值

六、类加载器

  • 类的唯一性:需要类和加载类的加载器一同确定
  • 双亲委派模型:除了顶层类加载器,其他都需要有自己的父类加载器(非继承,而是组合)

    • 启动类加载器:\lib目录下且虚拟机识别的类库
    • 扩展类加载器:\lib\ext目录
    • 应用程序类加载器
    • 工作过程:尽可能委派给父类加载器完成,父加载器反馈无法完成时再自己动手
    • 好处:给类赋予了层次关系,例如保证了Object类的稳定
  • 破坏双亲委派模型

    • JNDI接口:线程上下文类加载器
    • OSGi环境:网状结构

七、虚拟机字节码执行引擎

  • 栈帧:

    • 局部变量表:基本单位为变量槽Slot
    • 操作数栈:LIFO后入先出
    • 动态连接
    • 返回地址:保存PC计数器的值
  • 方法重载(静态分派)

    • 如果没有合适的方法,则向上转型
    • 变长参数的优先级最低
  • 方法重写(动态分派)

  • (目前)静态多分派,动态单分派

八、Java语法糖

  • 泛型:在Java中的实现实际上会出现类型擦除
  • 泛型与重载:

    • 返回值相同时无法重载(原因:类型擦除)
    • 返回值不同时可勉强重载(描述符不是完全一致则可以共存),大部分编译器选择拒绝
  • 自动装箱/拆箱

    • 包装类重写了equals方法,需要类型和内容都一致
    • Integer使用一个内部静态类中的一个静态数组保存了-128-127范围内的数据,两个同值的Integer对象做==操作,可能为true或者false
  • 条件编译:只能使用条件为常量的if语句

九、先行发生

  • 时间先后与先行发生并没有关联
  • 改写使线程安全:
    • 添加volatile适用volatile变量规则
    • 添加synchronized适用管程锁定规则

十、线程安全

  • 定义:多线程访问一个对象,不需要进行额外的协调和同步,可以获得正确的结果。也就是说代码本身封装了保障的手段,调用方无须关心线程问题,也不用自己额外采取措施来进行保障
  • 安全程度:
    • 不可变:final修饰
    • 绝对线程安全:需要完全满足定义,很困难
    • 相对线程安全:线程安全类Vector在两个线程中分别get和delete时,仍然需要同步
    • 线程兼容:常见的HashMap等集合
    • 线程对立:无论是否采取同步措施都无法在多线程环境下并发使用

十一、线程安全实现

  • 互斥同步:加锁、synchronized
  • 非阻塞同步:CAS操作等
  • 无同步:可重入代码(任何时候中断,转而执行其他代码,控制权返回后不会出错)一定是线程安全的,线程安全不一定可重入
  • 线程本地存储:ThreadLocal类实现。每个线程的Thread对象中都有一个ThreadLocalMap对象,存储了一组以threadLocalHashCode为键,本地变量为值的K-V对

十二、锁优化

  • 自旋锁:(挂起和恢复线程消耗大)等待锁的请求做一个忙循环,期待占有锁的线程很快释放。通过”自适应“变得更加智能。
  • 锁消除:(堆上所有数据不会被其他线程访问到)根据逃逸分析判断,可以无视掉代码里要求的同步
  • 锁粗化:(一串连续动作反复加锁解锁)锁粗化到操作序列的外部
  • 轻量级锁:如果同步周期内存在锁竞争,反而消耗更大
  • 偏向锁:偏向于第一个获得锁的线程。如果大多数锁被多个不同线程访问,则偏向锁反而不好。
文章目录
  1. 1. 一、制造各种溢出的场景
  2. 2. 二、回收方法
  3. 3. 三、HotSpot实现
  4. 4. 四、内存分配与回收策略
  5. 5. 五、类加载机制
  6. 6. 六、类加载器
  7. 7. 七、虚拟机字节码执行引擎
  8. 8. 八、Java语法糖
  9. 9. 九、先行发生
  10. 10. 十、线程安全
  11. 11. 十一、线程安全实现
  12. 12. 十二、锁优化