jvm
2020-05-19 10:03:05 0 举报
AI智能生成
jvm及涉及到jvm底层的线程问题
作者其他创作
大纲/内容
自由主题
volatile
强制把线程本地内存中的变量刷新到主内存(main memory)->可见性
包含了禁止重排序的语义->有序性
不保证有序性
JMM实现底层是加入内存屏障,特定操作下阻止可能发生的重排序的操作
自由主题
Random在多线程下的效率
java内存模型(JMM)零碎的知识点
JMM是共享内存的并发模型,线程之间通过读-写来完成通讯
抽象内存模型
子主题
happens-before规则
JMM保证happens-before规则
happens-before规则约束了编译器、处理器重排序
只要不改变执行结果,编译器和处理器如何进行优化都行
运用happens-before规则,可以分析数据的竞争关系,可能发生的问题
8种原子操作
lock\unlock\read\load\use\assign\store\write
自由主题
原子性
有序性
可见性
问题
程序计数器只记录java方法的当前执行到的地址,没有记录native方法的地址,如何在程序中断时,恢复native方法的执行到的地址
类加载过程
加载
通过全类名获取定义这个class需要的信息(字节流),不一定非得从.class文件获取,也可以是网路、ZIP包(JAR包)、运行时计算生成的代理类之类的
类加载器
启动类加载器
扩展类加载器
应用类加载器
双亲委派模型
隔离、不重复加载、保证核心api的安全
连接
验证
文件格式、元数据信息、字节码、符号引用
符号引用 VS 直接引用
准备
把变量(静态变量)写入常量池
解析
类/接口、字段、方法
NoSuchFieldError、NoSuchMethodError
初始化
clinit,执行静态代码块等
保证加锁,防止同一个类被多次加载
线程安全的实现方法
1、互斥同步
Synchronized
底层为monitorenter和monitorexit,是否获取到了对象的监视器
保证了原子性、有序性、可见性’
Lock
属于悲观锁
锁的优化
1、锁自旋
为了避免线程的切换,设定一定次数的循环尝试、直到获取锁
如果自旋最后能拿到锁,确实可以降低消耗,但是如果自旋花费的时间太长、会白白消耗处理器资源
2、适应性锁自旋
根据自旋成功几率,设定循环的次数
3、锁消除
如果编译器发现不可能有多线程访问,认为是私有的不再加锁
StringBuffer的默认加锁
逃逸分析技术
4、锁粗化
如果有连续的操作同一个锁的时候,扩展到操作序列的外部
如StringBuffer的例子
5、轻量级锁
MarkWord标记,同时MarkWord储存指向到栈帧的Lock Record,如果有另外的线程请求时,检查对象是否指向当前线程的栈帧,如果是、直接进入同步快,如果否、自旋,自旋失败后、膨胀为重量级锁,当前线程阻塞
6、偏向锁
MarkWord标记、储存线程id,如果另外线程访问时,发现指向的线程id已经消亡、改为自己的偏向锁,如果没有消亡、膨胀为轻量级锁
锁膨胀的过程
无锁->轻量级锁->重量级锁
2、非阻塞同步
CAS
compare and swap
属于乐观锁
例如Atomic类
3、无同步
ThreadLocal
AQS,AbstractQueuedSynchronizer抽象队列化同步器
state
0是无锁
加锁线程的引用(当前获得锁的线程)
线程队列
OOM (Out of Memory)
内存耗尽
内存耗尽
OOM测试
分析的工具
MemoryAnalyzer
OOM
内存溢出
当前的内存分配已经不能满足用户需求
内存泄漏
程序异常导致的无用内存未被回收
堆溢出
测试方法:创建大量对象,放入list
栈溢出
测试方法:1、递归调用;2、定义大量本地变量
异常
不允许动态扩展的情况:
允许动态扩展的情况:
运行时常量池
jdk1.6之前可以通过创建String的Set来测试
方法区
由于方法区存有对象类型等信息,通过CgLib动态代理来动态创建大量代理类来测试
发生OOM之后
jvm kill掉线程,进行GC快速回收发生OOM的线程的内存,其他线程不受影响
java对象
java创建对象的过程
1、检查类是否已经加载
类+类加载器 确定了唯一一个类
2、分配内存
分配方式
指针碰撞
指针向未分配的一端移动
适用于标记-复制、标记-整理的GC
空闲列表
维护一个空闲列表
适用于标记-清除的GC,如CMS
线程安全问题的解决方式
乐观锁CAS(Compare And Swap)+ 失败重试
TLAB(Thread Local Allocation Buffer)
线程各自独自划分一块缓存区域,避免冲突,当缓存区域满了之后,再同步锁定分配下一块区域;
-XX:+/-UserLTAB
3、初始化为零值
基本数据类型为0
引用类型为null
4、设置对象头
类型信息、hashCode(真正调用时产生)、mark word 分代信息
5、对象的构造方法init
对象的访问
句柄
reference持有句柄的地址,句柄持有实例的指针
好处是移动的时候,只需要改变句柄持有的指针,不需要改变reference引用
指针
reference直接持有指针
好处是访问快,不需要句柄转换
对象分配的位置
刚创建时:Eden
survivor From和To 即保留区
每次GC eden和from区的回收到to区,之后to区变为from区、from区变为to区
对象经过在survivor中,GC达到阈值(默认15次)之后,进入老年代
大对象直接进入老年代
和GC的选用是紧密结合的
jvm内存结构
堆
配置
参数:-Xms -Xmx
配置不当的情况:
可能抛出的异常:OOM
垃圾收集
堆的分类和选择的垃圾收集器有关
细分的目的只是为了方便垃圾收集
G1、ZGC这种新的GC不采用分代设计
方法区
运行时常量池
虚拟机栈
配置:-Xss128k
可能抛出的异常:SOF、OOM
程序计数器Process Counter Register
垃圾收集(Garbage Collcetion ,GC)
基础假说:
弱分代假说
强分代假说
跨代引用假说
记忆集Remember Set是如何被设计出来的
1、哪些内存需要收集
引用计数算法
被引用的对象计数+1,如果是0说明没有引用,可以回收
缺点:循环引用的两个对象计数都不为0,永远没有办法被回收
可达性分析算法
GC Roots对象作为起始节点,无法到达的对象进行回收
GC Roots对象包括VM栈引用的对象、方法区静态属性、常量引用的对象、被同步锁Synchronized持有的对象
2、什么时候收集
根据引用的类型
3、如何收集
GC算法
标记-清除算法
缺点
1、效率不稳定,如果有大量对象需要回收,标记+清除的操作耗时巨大
2、产生内存碎片(不连续的内存空间)
标记-复制算法
优点
1、一次性清除原半区的内存,效率高
2、有连续的内存空间
缺点
需要耗费保留空间,如果是老年代这种大量对象会存活的情况、会有大量复制操作效率不高且耗费更多保留空间
改进:基于弱分代假说,不需要完全按照1:1比例设立保留区,但是需要老年代来保底如果保留区不够的情况
标记-整理算法
优点
让存活的内存向一端移动,保证了内存连续
缺点
需要停止用户的内存访问
方法区的回收
条件苛刻、如果有大量使用反射、动态代理会自定加载类的计数时,需要考虑jvm类型卸载的能力,避免内存压力
具体的收集器
Serial
算法模型:标记-复制
回收对象:新生代
优点:简单
缺点:Stop The World会暂停用户的使用
目的倾向:
ParNew
标记-复制
新生代
Serial的多线程版本
Parallel Scavenge
标记-复制
新生代
目的倾向:吞吐量,设定GCRatio
Serial Old
标记-复制
老年代
配合Serial一起使用
Parallel Old
标记-整理
目的倾向:吞吐量
CMS(Concurrent Mark Sweep)
标记-清除
老年代
初始标记(标记GC Roots对象)->并发标记(从GC Roots开始遍历对象图)->重新标记(补回上一步过程中新增的对象)->并发清理
缺点:1、会产生浮动垃圾、需要等下一次GC;2、基于清除算法,会产生内存碎片,当不足以分配大对象时,会提前触发FullGC
G1(Garbage First)
简述
把内存分成相等大小的region,计算每个region的回收效率,从高到低进行回收直到满足配置要求的停顿时间
从整体看是标记-整理,从region角度看是标记-复制
不区分新生代老年代回收
目的倾向:延迟可控的情况下,尽量保证吞吐量
STW
评估收集器的指标
低延迟
高吞吐
公式
占用的内存
如何选择GC
数据分析、科学计算类,保证大吞吐量
SLA应用,保证延迟时间短
jvm优化方向
1、关闭验证
-Xverify:none
关闭类加载过程中的验证阶段,缩短jvm类加载的时间
用MAT分析dump日志
排查哪个类占用的内存较多,检查代码是否有异常
dump日志占用空间很大,结合阿里oss
直接内存
不属于jvm的内存,java操作Native函数直接分配堆外内存(如NIO,为了省去java堆与内存的数据交互,直接操作堆外内存而不是操作java堆)
机器内存到达上线,也会报OOM
ReentrantLock
ReentantLock的底层实现是AQS
流程
1、线程检查state是不是0,如果是0就CAS设置state为1
2、如果不是0就检查加锁线程是不是自己的线程,如果是,state基础上再+1
3、如果不是自己,就把自己线程加入到线程队列里
4、线程释放锁时,state-1,减到0时,无锁
SOF (Stack Overflow)
栈溢出
引用
强引用
即时导致了OOM报错,都不进行回收
软引用
要发生OOM时才进行回收
弱引用
垃圾收集时,一旦扫描到就进行回收
虚引用
Thread
基本状态
NEW
RUNNUABLE
WAITING
TIME_WAITING
TERMINATED
基本操作
interrupt
标识位清零?
join
sleep
sleep和Object.wait()方法的区别
yield
线程安全问题的来源
1、各个线程工作内存和主内存不一致
2、重排序
编译器指令的重排序
处理器指令的重排序
0 条评论
下一页