Java虚拟机一些问题整理
文章目录
一、制造各种溢出的场景
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对
十二、锁优化
- 自旋锁:(挂起和恢复线程消耗大)等待锁的请求做一个忙循环,期待占有锁的线程很快释放。通过”自适应“变得更加智能。
- 锁消除:(堆上所有数据不会被其他线程访问到)根据逃逸分析判断,可以无视掉代码里要求的同步
- 锁粗化:(一串连续动作反复加锁解锁)锁粗化到操作序列的外部
- 轻量级锁:如果同步周期内存在锁竞争,反而消耗更大
- 偏向锁:偏向于第一个获得锁的线程。如果大多数锁被多个不同线程访问,则偏向锁反而不好。