JMM(Java Memory Model):
- java内存模型定义了共享内存系统中,多线程程序读写操作行为规范,从而解决并发问题
– eg: synchronized 、Lock 保证原子性
– volatile 保证 可见性、有序性
JVM内存结构:
常见垃圾回收器 (Yong 年轻代,old 老年代,蓝色代表支持内存,红色代表STW时间:
- Yong : Serial (串行)(m)、ParNew (G)、Parallel Scavenge (P开头并行)
- old :CMS ( concurrentMarkSweep ) (200ms)、Seraold 、Prallel old
- 不区分: G1(逻辑分代)(分区region)(garbage first) (10ms)(百G)、ZGC (1ms) (4T - 2^24)、Shenandoah (4T)
- debug :EpSilon
JDK版本对应
- JDK 1.3.1 - 1.7 ,Serial + Serial old (新生代 + 老年代 + 永久代)
- JDK 1.8 , Parallel Scavenge + Prallel old (默认 ParallelGC)(新生代 + 老年代 + 元空间 metaspace ) 、G1 不区分,概念分代。
- JDK 1.9 ,CMS 以废弃.
- JDK 11 以后,ZGC
算法理解总结:
- 最初:引用计数法,给每个对象分配一个引用计数器,引用指向一次+1,反之-1,为0则为垃圾,效率很低
- 复制算法:新生代中把eden、from 区存活对象,复制到 to区(空)中,然后清除剩下垃圾
–缺:浪费内存空间(因为需要一个空的内存区域)
–优:不会存在内存碎片
–场景:适用对象存活率低的时候,新生代- 标记清除:从根对象遍历查找标记活着的对象,然后清除没有任何引用指向的对象
– 缺:两个扫描存在浪费时间、会产生内存碎片
– 优:不要浪费额外空间- 标记清除压缩:对标记清除再优化,再次扫描存活的对象,移动至连续内存区域
– 缺:3次扫描,以及对象移动成本
– 优:内存连续,没有碎片
– 场景:老年代
Java 垃圾处理 (图片截图来自 --马士兵老师讲JVM)
- 垃圾: 没有任何引用指向的一个对象,或者一个循环引用对象
定位垃圾:
- 引用计数 (reference count) - 问题:不能解决循环引用垃圾对象。
- 根搜索算法 (Root Searching)
- 根对象: JVM stack (jvm栈里)、nataive method (本地方法栈)、run-time contant pool,(运行时常量池)、references in menthod area(静态引用)、Clazz,( load在内存的class对象)
垃圾回收算法:
- 标记清除(Mark - Sweep):
标记:从根对象开始遍历,并对从跟对象可以访问到的对象打上一个标识
清除: 对堆内存从跟对象开始循环遍历,回收没有标记的对象
问题: 位置内存不连续,产生碎片
拷贝算法(copying): 内存一分为二,留一半把另一半存活对象复制过来。- 将内存划分为两个区间,在任意时间点,所有动态分配的对象都只能分配在其中一个区间(称为活动区间),而另外一个区间(称为空闲区间)则是空闲的,当有效内存空间耗尽时,JVM将暂停程序运行,开启复制算法GC线程。接下来GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址。
问题: 浪费内存。
标记压缩 (Mark - Compcat): 可用对象移动覆盖垃圾对象,在进行压缩。
堆内存逻辑分区:
- 分代 ↓
- 新生代 :老年代 = 1:2 , eden :S0 :S1 = 8 :1: 1
- 第一次YGC (MinorGC),把存活对象拷贝进S0
- 再次YGC,eden ,S0 存活对象拷贝进入S1
- 再次YGC,eden . S1 存活对象拷贝进入S0
- 存活年龄足够进入old区 (经历一次GC没被回收调 age +1)(CMS age:6 ,ParallelGC :15)
- 老年代满了后触发一次FGC (Major GC)(YGC+ old gc) ,产生 STW (stop the wordl) 停顿现象
- G1以后开始分区 (Copying 算法):
- 优先清理垃圾最多的小块
三色标记 (CMS):
定义: 当垃圾回收器去标记对象的时候,把对象逻辑上分成三种颜色。
- 白色:找到对象,还未识别是否是垃圾
- 灰色: 识别对象不是垃圾,还未开始标记该对象成员变量指向
- 黑色:识别对象不是垃圾,并且该成员变量继续标记完成了
情况分析:
- 1 、浮动垃圾 (处理,下一轮GC清除)
2、漏标 :在运行中,灰色对象取消了对白色对象的引用,同时黑色对象指向了白色对象,然后黑色对象已近标记完成,白色对象就不会再被扫描,从而导致白色对象被回收,产生漏标导致内存泄漏。
cms解决方案,Incremental Update - 累加式更新: 把 A 处理成灰色,改变标志位。但是在并发标记时,还是会漏标:在并发标记时,线程一处理A时(变成灰色)线程二,又把A 标记完成标记为黑色,又变回去了。
G1解决方案(SATB - Snapsnot At the Begining):
当B指向D的消失的时候,把这个指针保存到GC的堆栈,下一轮扫描时看堆栈有没有被放入的指针,如果有拿出来在把对象D扫描一次看有没有对象指向D,如果有就不是垃圾。
ZGC解决方案: 颜色指针, 把剩余的4位用来做状态区分.
java对象:
对象的创建过程:
- 检查类是否已经被加载
- 为对象分配内存空间
- 为对象的字段赋默认值;
- 设置对象头;
- 执行实例的初始化方法lint
- 执行构造方法。
分代模型 -对象的分配:
- 当我们new一个对象时,首先在栈上分配 (在栈上分配的对象,不会牵扯GC,结束后直接弹出)
- 当栈上分配不了时,根据对象的大小分配。大对象 → 进入 old 区,进过FGC后 被回收
- 不是够大的对象,进入线程本地分配缓存区(Thread Local Alloction Buffer TLAB),在eden区为每一个线程分配一小块空间(解决线程竞争),后进入eden区
- 在eden区经过一次GC后,如果被回收就结束,没有就进入 S0,在进行一次GC,被回收就结束,没有就继续,当 回收age满足条件后进入 old区,在FGC后被回收结束
对象在内存上中的存储布局
注:一个对象默认是
16bytes
,所在字节不能被8
整除会补齐
对象头主要包括:hashcode
,GC信息
,锁信息
public static void main(String[] args) throws InterruptedException {
Student o = new Student();
o.setA(1);
o.setB(1);
o.setC(1);
o.setD("1");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
8(markword)+4(classpointer)+12(int*3)+4(String)+4(padding)=32 bytes
//默认开启,指针压缩,对象压缩(引用指向压缩)
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266183360 -XX:MaxHeapSize=4258933760 -XX:+PrintCommandLineFlags -
XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
对象定位
- 1.直接指针 : t → T (类型指针)→ 方法区 T.class ,直接指针速度快 ,在-GC回收时,会产生对象复制,t 指针会产生变化
- 2.句柄池 : t -> 实例数据指针 | 类型数据指针 → 分别指向 , t 不会变化 , GC回收比较稳定
volatile 作用:
- 1.线程可见性
- 2.禁指重排 (防止指令重排序)
- 3.不保证原子性
public class VolatileTest {
/**
* 线程停止
* 有 volatile 修饰时,就会通知所有访问此变量的线程,重新load值到本地内存
*/
private static volatile boolean volatileFlag=true;
/**
* 线程不停止
* 没有 volatile 修饰时,就一直访问的 线程本地内存,一直为true
*/
private static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (volatileFlag){
}
System.out.println("end");
},"sever").start();
Thread.sleep(1000);
volatileFlag=false;
}
}
public class VolatileTest2 {
private static int x=0,y=0;
private static int a=0,b=0;
/**
* 正常情况执行顺序,
* a=1
* x=b
* b=1
* y=a
* 不会出现 x=0,y=0;的情况
* 如果指令重排:↓
* x=b; 默认 b=0;a=0;
* y = a;
* b = 1;
* a =1;
* 就出现 x=0,y=0;的情况
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int i=0;
for(;;){
i++;
x=0;y=0;
a=0;b=0;
Thread one = new Thread(()-> {
a=1;
x=b;
});
Thread other = new Thread(()-> {
b = 1;
y = a;
});
one.start();other.start();
one.join();other.join();
String res="第"+i+"次(" + x + "," + y + ")";
if (x==0 && y==0){
System.out.println(res);
break;
}
}
}
}
JVM 调优
- 常见命令
nohup java -Xmx256m -Xms256m -Xmn128M -Xss256k -jar epay-check-account-0.0.4.jar
-Xms<size> set initial Java heap size
默认物理内存64/1
-Xmx<size> set maximum Java heap size
默认物理内存4/1
-Xss
规定了每个线程堆栈的大小
top
、jps
jvisualvm.exe
arthas -阿里 jvm监测
在线文档:https://arthas.aliyun.com/doc/install-detail.html
hlep
查询指令说明 ;
thread -help
, 具体命令用法说明
NAME | DESCRIPTION |
---|---|
dashboard | Overview of target jvm’s thread, memory, gc, vm, tomcat info |
jvm | Display the target JVM information |
thread | Display thread info, thread stack |
redefine | Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition…) |
jvm | Display the target JVM information |
redefine : 重新编译,载入到内存。 (应急)
redefine /root/TT.class