vm thread 机制

概念

vmthread是jvm内部的核心守护线程非Java线程,在jvm启动时由thread:create_vm()初始化并启动

作用

串行任务执行引擎

安全点控制器

任务调度中心

核心职责

监听vm opreation_q队列里的任务请求

触发安全点并执行任务

管理任务生命周期

vm opreation分类

整体逻辑

源码解析

取任务

任务超时了,也会进入safepoint

begin 即Safepoint实现机制

线程本地轮询,改的变量应该是在cpu 1,2,3级缓存

所以说让线程停止下来是可以的

等待所有线程进入安全点

是一个cas操作

java thread和vm thread的协同

可数循环问题和VMthread的关联

特别的:jit优化,有个东西叫做内联,它就是如果你调用多个方法比较多次,它会把你的方法直接变成机器码,然后还会把方法给展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
main(){


save()
}
save(){
iiiii
jjjj
jjjj
}

内联之后:
main(){
iiiii
jjjj
jjjj
}

那么这里的方法返回之前的安全点也会失效哦

Java8 和Java11 vmthread的差异

1
2
3
4
5
6
7
tomcat线程池
for(int i:10000000){
for(int i:10000000){
//xxxx
}
}
//可能才有Safepoint

int 循环里是没有Safepoint,也就是它会一直执行下去,在parallel那一章我们都知道vmthread每隔1000ms就会让线程进入Safepoint进行stw,stw得等所有线程都进入Safepoint,才能继续往下执行,也就是你这一个大循环线程就会拖累所有线程,你妈的连jstack都进不去

小米的HBASE出过这么一个事故

解决方案:换成long,因为long会有Safepoint

内存分配时,会尝试先去线程的私有区域(tlb)分,不够再去堆上分

内存分配

垃圾回收器 parallell源码

首先,大对象体积超过一定阈值时会直接进入老年代吗?

这个对于parNew是适用的,通过参数配置阈值。但parallel就一样的,它是从

yong_gen直接分配

年轻代:eden form to

老年代:

parallel的内存分配

//第一次从年轻代分配,分配成功返回Result,分配失败进行第二次

//第二次从年轻代分配,分配成功返回Result,分配失败尝试从老年代分配

//第二次从老年代分配,分配成功返回Result

//if(result == null) Vmthread:excute 垃圾回收内存了

//Paralllel:invoke() 先尝试yong gc

垃圾回收

jvm的vmthread主要职责是什么,和gc及Safepoint的关系是什么

vmthread的loop()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
loop(){
//死循环,等待其他操作唤醒
while(true){
// wait for vm operation

//......


//每隔1000ms就触发
if(VMthread:no_op_safepoint_needed(true)){
safepointSyn:begion() //暂停所有线程
safepointSyn:end()//恢复所有线程
}
}
}

begin:该标志位,通知其他线程暂停下来,而且得等所有线程都听下来,才能继续执行begin方法剩余的逻

内存不够了不一定会触发fullgc,而是会先根据你新生代回收是否成功以及老年代的大小来决定的

新生代的垃圾回收逻辑

yong gc不会stw?

只要执行 Vm:excute 就会stw

安全点 Safe point:

安全点是 JVM 在字节码执行中预设的“安全停车点”,只有线程到达这些位置才能被安全地暂停,用于 GC 等全局操作,保证程序状态一致性

初次标记–>并发标记—>重新标记

字面量:

字面量指由字母、数字等构成的字符串或者数字常量,如1,“abc”,10这些都是字面量

变量:

定义出一个符号,这个符号在某个范围内,就代表一个变化的量。要注意两点:1. 变量必须先定义才可以使用。2. 变量如果先被赋值或者初始化才可以使用。如 int a = 1,a就是变量;String s = “abc”,s也是变量

常量:

final修饰的变量

静态变量:

static修饰的变量,只能是类的成员变量,不能定义在方法中

符号引用:

符号引用就是一个类中(当然不仅是类,还包括类的其他部分,比如方法,字段等)引入了其他的类,可是JVM并不知道引入的其他类在哪里,所以就用唯一符号来代替,等到类加载器去解析的时候,就通过符号引用找到那个引用类的地址(如果找不到说明引用类还没加载,那就直接把引用类也加载了),这个地址也就是直接引用。

在编译时,java类并不知道所引用的类的实际地址(只是javac编译,JVM还没运行呢,哪知道地址?),因此只能使用符号引用来代替

符号引用包括以下常量:

  • 被模块导出或者开放的包
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

直接引用:

直接引用是可以直接指向目标的指针、相对偏移量 或者是一个能间接定位到目标的句柄。直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。

常量池:

每个class文件独有一份,常量池中有字面量(数量值、字符串值)和符号引用(类符号引用、字段符号引用、方法符号引用),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型

运行时常量池:

当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,经过解析(resolve)之后,也就是把符号引用替换为直接引用

字节码中,lambda表达式处会出现个invokedynamic,这个指令在运行时会动态链接到 LambdaMetafactory.metafactory。

字节码会多出个方法,比如在main方法使用了lambda表达式,就会多出个lambda$main$0,里面是lambda要执行的代码

lambda相比于匿名内部类来说更高效,因为它避免了每次使用lambda时都生成一个新类的开销,后面会复用生成的lambda方法

lambda中可以使用到外部的变量,但是不能在表达式中对这些变量进行修改,其原理就是,lambda表达式会被动态生成一个方法,这个方法是私有的、静态的,然后外部参数会被这个方法参数传进来。

String

  1. substring(i,j) [ i , j )
  2. isLetterOrDigit(char c) 判断是数字还是字符
  3. toLowerCase(char c) 转成小写
  4. int indexOf(String s) 从父字符串index = 0开始找s,并返回最早出现的index
  5. int indexOf(String s, int t) 从父字符串index = t开始找,并返回最早出现的index
  6. boolean startWith(String s,int t) 判断父字符串从startIndex开始的子串是否以word开头

Array

  1. Array.copyOfRange(int [],i,j) 复制某区间的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Arrays;

public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};

// 复制下标 1 到 3 的元素(不包含下标3)
int[] subArr = Arrays.copyOfRange(arr, 1, 3);

System.out.println(Arrays.toString(subArr)); // 输出 [2, 3]
}
}

Collection

  1. Collection -> Array:
    xxx.toArray( int []); eg:temp.toArray(new int[temp.size()][2]);

Map

StringBuilder

1. 追加

1
2
3
sb.append("world");     // 追加字符串
sb.append(123); // 追加数字
sb.append(true); // 追加布尔值

2. 插入

1
sb.insert(0, "Hi ");    // 在下标0插入

3. 删除

1
2
sb.delete(0, 2);        // 删除 [0,2) 区间的字符
sb.deleteCharAt(3); // 删除指定位置的字符

4. 替换

1
sb.replace(0, 2, "Hey");  // 替换 [0,2) 的内容

5. 反转

1
sb.reverse();           // 反转整个字符串

6. 修改

1
sb.setCharAt(0, 'H');   // 修改指定位置的字符

7. 长度 & 容量

1
2
3
sb.length();            // 当前字符长度
sb.capacity(); // 底层 char[] 容量
sb.ensureCapacity(100); // 确保容量至少为100

8. 获取子串

1
String sub = sb.substring(0, 5);  // [0,5) 的子串

9. 查找

1
2
sb.indexOf("lo");        // 找到子串首次出现的位置
sb.lastIndexOf("o"); // 找到子串最后出现的位置

10. 转为 String

1
String str = sb.toString();

Integer

  • Integer.parseInt(x,进制) 可指定进制

只出现一次的数字[🔥49]

思路:

利用异或运算^

1. a^a =0
2. a^0=a

然后运算满足交换率

a^b^a=a^a^b

最后就可以得出 b是单身狗

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int singleNumber(int[] nums) {
int x = 0;
System.out.println(12^1);
for (int num : nums){
// 1. 遍历 nums 执行异或运算
x ^= num;
System.out.println(x);

}
return x; // 2. 返回出现一次的数字 x
}
}

变种:字节后端,变成有序数组,需要O(logN)复杂度。 思路:二分查找,如果nums[mid]出现了两次,则左边或右边肯定有一边剩余了奇数个数字,朝奇数个的方向走。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int singleNonDuplicate(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] == nums[mid ^ 1]) {
l = mid + 1; // 单身狗在右边
} else {
r = mid; // 单身狗在左边或就是mid
}
}
return nums[l];
}
}

![](/images/3617fe7504c0cae01de7b87b4d19720a.png)

寻找重复数[34]

1

汉明距离

思路:

  1. 调用函数 Integer.bitCount(x) 统计x中 为1的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int hammingDistance(int x, int y) {
//bitCount 用于统计 二进制中 1的数量
// x ^ y特性:x和y相同 则x ^ y为0,x = 0 或者 y = 0 则x ^ y = 非0数
System.out.println(0 ^ 6); // 6
System.out.println(1 ^ 6); // 无规则
// 001
// 110
// 111 = 7
System.out.println(6 ^ 6); // 0
return Integer.bitCount(x ^ y);
}
}