jvm
2020-05-19 10:03:05 0 举报
AI智能生成
jvm及涉及到jvm底层的线程问题
作者其他创作
大纲/内容
jvm
自由主题
volatile
强制把线程本地内存中的变量刷新到主内存(main memory)->可见性
包含了禁止重排序的语义->有序性
不保证有序性
JMM实现底层是加入内存屏障,特定操作下阻止可能发生的重排序的操作
Random在多线程下的效率
java内存模型(JMM)零碎的知识点
JMM是共享内存的并发模型,线程之间通过读-写来完成通讯
抽象内存模型
子主题
happens-before规则
JMM保证happens-before规则
happens-before规则约束了编译器、处理器重排序
只要不改变执行结果,编译器和处理器如何进行优化都行
运用happens-before规则,可以分析数据的竞争关系,可能发生的问题
8种原子操作
lock\\unlock\ead\\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
基础假说:
弱分代假说
强分代假说
跨代引用假说
记忆集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 条评论
回复 删除
下一页