JVM 笔记
类装载子系统,运行时数据区,执行引擎
运行时数据区
方法区 (虚拟机规范)
方法区具体实现:
1.永久代 1.8以前,由jvm管理。
2. 元空间 1.8以后,操作系统管理(OS)
为什么1.8以后要用元空间取代永久代?
答:1.硬件发展 1.8以前大部分运行32位机,OS最大内存4G(2^32),内核2G,应用2G。如果不放JVM管理,很可能会影响操作系统其他程序执行。64位机,最大内存256G。64位只有48位在用。2^48-》256G。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。
2.业务发展-》系统复杂度提升。并发。spring吃内存,等等。
内存调优:jvm元空间(20.75m-无限)-XX:+PrintFlagsFinal 查看jvm运行参数命令。
最大最小内存不一致,导致内存抖动(这块程序内存忽大忽小,影响程序运行)
一般方法区大小设成物理内存的1/32(论坛和大牛的建议)
虚拟机栈(java栈)
栈帧
1. 局部变量表 局部变量占2个slot(槽)。赋值完的变量放进来
2.操作数栈 存变量的值
3.动态链接
4.返回地址 做的事情-》 1.局部变量表指针重置,2.操作数栈指针重置,3.返回值压栈,4.add方法占用的栈帧内存回收,5.程序计数器的数值重置
本地方法栈
运行native方法的栈
程序计数器
记录程序要执行的行号
堆
新生代与老年代1:2的问题
新生代 分代+复制算法
伊甸园,from,to区。8:1:1,经大公司测试,90-95%的对象在第一次被gc。所以第一次gc后有10%(取最大值)的对象存活,所以设置成1。一开始是9:1,后来变8:1:1。因为程序产生的对象被回收,会产生很多内存碎片。
内存碎片导致程序(需要分配连续内存空间)要分配内存分不到。标记整理算法,存在问题:非常复杂,空间回收-》合并-》打标记的指针进行移动-》对数据进行移动。很多地方要做stw(stop the world)。
分代复制算法效率相对高很多。
老年代
1.新生代15次gc没释放就进入老年代
2.大对象(大于伊甸园区的对象) 占满伊甸园区,要复制移动到老年代消耗性能多,空间分配担保(老年代作为担保)
栈对堆得引用,栈变量指向堆得对象
堆对方法区的引用,对象的类型指针指向方法区的class对象
方法区对堆得引用,静态变量
多线程
1.6以前syncronize 没获得cpu的线程会阻塞进队列等待
原子操作类AtomicInteger优化
CAS:无锁,自旋锁,乐观锁,轻量级锁 compareandset
hotspot源码
https://www.bilibili.com/video/BV1o54y167pa?p=2
1原子性问题:lock cmpxchgq(汇编指令)缓存行锁(硬件级别加锁是针对一个缓存行(64位))/总线锁(超过缓存行大小在总线加锁)
2 ABA问题:加版本号version AtomicStampedReference
volatile
增加一个#Lock在汇编指令,相当于内存屏蔽。
可以实现可见性(共享变量修改后强制写入主存,通过总线嗅探(也可以说是监听),其他cpu当监听到总线的共享变量修改后,就让自己的共享变量失效,会重新从主存中读取),有序性(确保重排序时不会把其后面的指令排到内存屏蔽之前的位置,也不会把之前的指令排到内存屏蔽之后的位置),不能保证原子性。
有序性例子:对象初始化
可以看到下面对象初始化的执行命令,cpu会重排序。下图21是调构造方法,24是把对象赋值给变量instance。如果21,24重排序换位置,就可能出现版构造
内存屏障
storeload ->fence
最后调了汇编命令lock
锁优化 jdk1.6开始
锁升级
1.偏向锁
在对象头记录线程id
2.CAS 轻量级锁。如果大量线程争抢cpu,会导致cpu空转。
3.重量级锁。线程很多,不如用重量级锁
优化轻量级锁到重量级锁 LongAdder.java 分段CAS