-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 146 KB
/
content.json
1
{"meta":{"title":"Hexo","subtitle":"","description":"","author":"John Doe","url":"http://www.010101.cc","root":"/"},"pages":[{"title":"","date":"2021-11-16T12:45:25.497Z","updated":"2021-11-16T12:45:25.497Z","comments":true,"path":"index.html","permalink":"http://www.010101.cc/index.html","excerpt":"","text":"叫我021吧! 请叫我021吧 Web Developer & Designer Shenzhen, China × Toggle navigation Home Archives Categories Tags Repository Books Links About Board 欢迎交流与我分享您的经验! Archive November 20212 Recent Posts test 2021-11-08 Hello World 2021-11-08 test Nov 8 Comments Hello World Nov 8 Comments © 2021 021 All rights reserved. window.jQuery || document.write('') (function (window) { var INSIGHT_CONFIG = { TRANSLATION: { POSTS: 'Posts', PAGES: 'Pages', CATEGORIES: 'Categories', TAGS: 'Tags', UNTITLED: '(Untitled)', }, ROOT_URL: '/', CONTENT_URL: '/content.json', }; window.INSIGHT_CONFIG = INSIGHT_CONFIG; })(window);"},{"title":"标签","date":"2021-11-09T06:45:29.327Z","updated":"2021-11-09T06:45:29.327Z","comments":false,"path":"tags/index.html","permalink":"http://www.010101.cc/tags/index.html","excerpt":"","text":""},{"title":"个人简介","date":"2021-11-10T11:41:34.587Z","updated":"2021-11-10T11:41:34.587Z","comments":false,"path":"about/index.html","permalink":"http://www.010101.cc/about/index.html","excerpt":"","text":"感谢访问,联系我poker0325@me.com(麻烦注明来意).我向您Salute!!! :)"},{"title":"分类","date":"2021-11-09T06:49:57.349Z","updated":"2021-11-09T06:49:57.349Z","comments":false,"path":"categories/index.html","permalink":"http://www.010101.cc/categories/index.html","excerpt":"","text":""},{"title":"友情链接","date":"2021-11-09T06:50:41.947Z","updated":"2021-11-09T06:50:41.947Z","comments":true,"path":"links/index.html","permalink":"http://www.010101.cc/links/index.html","excerpt":"","text":""}],"posts":[{"title":"高并发之Redis秒杀的本质","slug":"秒杀的本质","date":"2021-11-26T11:28:30.000Z","updated":"2021-11-26T13:03:36.683Z","comments":true,"path":"2021/11/26/秒杀的本质/","link":"","permalink":"http://www.010101.cc/2021/11/26/%E7%A7%92%E6%9D%80%E7%9A%84%E6%9C%AC%E8%B4%A8/","excerpt":"","text":"Redis秒杀的本质Wirte by 021. Leave a message if i messed up ! : ) 秒杀场景与特点场景 秒杀活动,到时间点后,用户会对商品进行购买 特点 秒杀场景下,瞬时的并发会比较高 商品的数量是有限的,不能超买超卖 每个用户最多只能抢购一件商品 整体架构设计 核心思想 幂等性 任意时间同一用户IP(默认一个用户一个客户端)无论操作多少次最多只能抢到资源一次. 原子性 同一时间只有一个线程扣查询库存并且扣库存成功,不能被打断. 一致性 在扣除库存成功后,能通知到其他集群节点,防止数据脏读. 顺序性 有序的更新数据库库存,温柔的像个绅士. Redis 的 Lua脚本脚本的原子性(官方介绍) Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed. However this also means that executing slow scripts is not a good idea. It is not hard to create fast scripts, as the script overhead is very low, but if you are going to use slow scripts you should be aware that while the script is running no other client can execute commands. Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行: 当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。 这和使用 MULTI / EXEC 包围的事务很类似。 在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。 另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难, 因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心, 因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。 123456789101112131415161718192021222324252627282930313233343536373839404142-- KEYS [goods]-- ARGV [uid]-- return -1-库存不足 0-重复购买 1-成功local goods = KEYS[1]local activity = ARGV[1]local uid = ARGV[2]local goodsuids = goods .. ':' .. activity .. ':uids'local goodsstock = goods .. ':' .. activity .. ':stock'local stock = redis.call('GET', goodsstock)if not stock or tonumber(stock) <= 0 then return -1endlocal isin = redis.call('SISMEMBER', goodsuids, uid)if isin > 0 then return 0endredis.call('DECR', goodsstock)redis.call('SADD', goodsuids, uid)return 1#从参数中获取goods编号,activity编号,uid。1.首先判断库存#使用GET命令,查询“goods:activity:stock”获取库存,判断是否还有库存,库存小于等于0,返回-12.判断是否已经购买过#使用SISMEMBER命令,判断“goods:activity:uids”set中,是否有uid,有表示参加过秒杀,返回03.减库存#使用DECR命令,对“goods:activity:stock”减一,扣减库存.4.更新参与秒杀记录#使用SADD命令添加“goods:activity:uids”用户uid,返回1","categories":[{"name":"Redis","slug":"Redis","permalink":"http://www.010101.cc/categories/Redis/"}],"tags":[{"name":"高并发原子锁","slug":"高并发原子锁","permalink":"http://www.010101.cc/tags/%E9%AB%98%E5%B9%B6%E5%8F%91%E5%8E%9F%E5%AD%90%E9%94%81/"}]},{"title":"MacBook Pro intel日常使用问题记录","slug":"MBP日常使用问题记录","date":"2021-11-19T19:12:21.000Z","updated":"2021-11-19T19:25:54.161Z","comments":true,"path":"2021/11/20/MBP日常使用问题记录/","link":"","permalink":"http://www.010101.cc/2021/11/20/MBP%E6%97%A5%E5%B8%B8%E4%BD%BF%E7%94%A8%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/","excerpt":"","text":"MacBook Pro 蓝牙模块修复Wirte by 021. Leave a message if i messed up ! : ) 步骤: 按住键盘 ⌥ Option + ⇧ Shift 的同时,点击菜单栏中的 Bluetooth图标 在弹出的下拉菜单中选择「还原蓝牙模块」 确认继续操作. MacBook Pro 快捷键截屏 Shift+Command+3 / Shift+Command+4","categories":[{"name":"工具使用","slug":"工具使用","permalink":"http://www.010101.cc/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"}],"tags":[{"name":"工具使用问题","slug":"工具使用问题","permalink":"http://www.010101.cc/tags/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E9%97%AE%E9%A2%98/"}]},{"title":"C语言基础之麻辣香锅","slug":"C语言之麻辣香锅","date":"2021-11-19T19:08:02.000Z","updated":"2021-11-22T07:03:31.558Z","comments":true,"path":"2021/11/20/C语言之麻辣香锅/","link":"","permalink":"http://www.010101.cc/2021/11/20/C%E8%AF%AD%E8%A8%80%E4%B9%8B%E9%BA%BB%E8%BE%A3%E9%A6%99%E9%94%85/","excerpt":"","text":"C语言基础之麻辣香锅Wirte by 021. Leave a message if i messed up ! : ) 家常菜之 — C基础语法123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608//// main.c// Assembly//// Created by 021 on 2020/11/14.//#include <stdio.h>#include<string.h>#include <stdlib.h> //找包含文件目录#include "add.cpp" //找当前目录,//int myOperation(int a, int b);int mySwitch(int a){ switch(a){ case 1: printf("Monday\\n"); break; case 2: printf("Tuesday\\n"); break; case 3: printf("Wednesday\\n"); break; case 4: printf("Thursday\\n"); break; case 5: printf("Friday\\n"); break; case 6: printf("Saturday\\n"); break; case 7: printf("Sunday\\n"); break; case 8: printf("8\\n"); break; case 9: printf("9\\n"); break; case 10: printf("10\\n"); break; default:printf("error\\n"); break; } return 0;}int myGoto(int a){ int b = 1; A : // 0x00001234 内存地址 b++; printf("b =%u\\n",b); if(b<a) { goto A; // jmp 0x00001234; 直接跳转,重复执行 } printf("goto循环结束 =%u\\n",b); return b;}int myWhile(int a){ int b = 1; while(b<a) { printf("while循环中 b =%u\\n",b); b++; if(a==2) { continue; printf("a=2 被continue跳过了,不会打印",b); } } printf("while循环结束 =%u\\n",b); return b;}int myDoWhile(int a){ int b = 1; do // 0x0000 0001; { printf("do while循环开始 =%u\\n",b); b++; } while (b<=a); // 判断 , 条件符合 jmp 0x0000 0001; printf("do while循环结束 =%u\\n",b); return b;}int myFor(int a){ int b = 1; int c; int d; //for(表达式1不要求有返回值;表达2要求有返回值,不是0就成立;表达式3不要有返回值) for(c = 0,d =2;b<a;b++,c++,d++) { printf("for循环 =%u\\n",b); } printf("for循环结束 =%u\\n",b); return b;}void arrayOverStackTest(){ for(;;) { printf("arrayOverStackTest"); }}int myArrayOverStack(int array[]){ array[6]=(int)&arrayOverStackTest; return 0;}int methodAgrTest(int a){ return a+1;}int methodArrAgrTest(int a[]){ return a;}void arrayTest(int a[],int size){ for(int i =0; i<size;i++) { printf("数组传参传的是数组第一个元素的地址,遍历元素值= %p\\n",&a[i]); printf("数组传参传的是数组第一个元素的地址,遍历元素值= %p\\n",*(&a[i])); }}void arrayPointTest(int* a,int size){ for(int i =0; i<size;i++) { printf("数组指针传参传的是数组第一个元素的地址,遍历元素值= %d\\n",*(a+i)); }}int __cdecl my_cdecl(int a,int b){ return a+b;};int __stdcall my_stdcall(int a,int b){ return a+b;};int __fastcall my_fastcall(int a,int b){ return a+b;};struct myStruct{ char c; short s; int i;};struct my8byteStruct{ char c; short s; double i;};struct myPoint{ char a; };struct myPointSize{ double b; double b1; double b2; };//自定义数据宽度对齐#pragma pack(2)struct myPragmaStruct{ char c; int i; double d;};#pragma pack()#pragma pack(2)struct person{ char age; char name[0x14];};#pragma pack()#define name1 "021"#define true 1#define false 0#define hello1 "你好,"#define mystr strcpy(hello1,name1)int main(int argc, const char * argv[]) { //整型 //-128 --- signed --- 0 --- unsigned --- 127; char c = 'a'; //1byet 0xff short s = 0xffff; // 2byte 0xffff int i = 0xffffffff; //4byte long l = 0xffffffff; //4byte printf("char : %c %d\\n",c,c); printf("short : %c %d\\n",s,s); printf("int i 无符号: %u int i 有符号: %d\\n",i,i); /** * *符号表示 , 编译器只关注数值存储,取数时有无符号有区别;默认有符号; *大部分计算机只存补码, 符号位将影响取值和判断. */ signed char sc = -1; // 1111 1111 0xff; 0000 0001 %u = 4294967295 unsigned char usc = -1; // 补码+1 = 1111 1111 , 0xff unsigned char usc1 = -2; // 0000 0010 -> 1111 1101 + 1 -> 1111 1110 -> 0xfe; printf("sc有符号=%d,转换成无符号=%u\\n",sc,(unsigned char)sc); printf("sc 无符号数(扩展错误数值) : %u\\n",sc); //此时取值可能进行了扩展 ,%u = 4294967295 //有无符号之间转换 printf("无符号usc=%u,无符号转有符号usc=%d\\n",usc,(char)usc); /** *数据宽度扩展:低位为数据位, 高位为符号位 */ //将有符号8位char类型放入32位int类型, int i1 = sc; // 1111 1111 -> 0xffffff + 1111 1111 = 0xffffffff; printf("有符号sc数据扩展int=%u,无符号转有符号=%d\\n",i1,(int)i1); /** *数据宽度溢出 *溢出的值为正的:实际值为:溢出后的值 - 该类型能表示的立即数 *溢出的值为负的:实际值为:溢出后的值 + 该类型能表示的立即数 *其中-0的原码是: 1000 0000 补码是:1 0000 0000 = 128的由来,+0 -0有点无厘头 */ //默认有符号,0xff的数据区间:有符号最小值:1111 1111 = -0x7f=-127; 最大值:0111 1111 = 0x7f =127; char sc1 = 256; //将256存入 -127 — 127数据宽度区间 发生溢出 printf("数据宽度溢出sc1=%u\\n",sc1); //256- 1111 1111 = 0; char sc2 = -258; printf("数据宽度溢出sc2=%d\\n",sc2); //-257 + 1111 1111 = -2; /** *浮点类型 *float 1位(符号位) 8位(整数位) 23位(尾数位) *double 1位(符号位) 11位(整数位) 52位(尾数位) */ float f = 123.25f; // 0xffffffff; double d = 22.22; // 0xffffffff ffffffff;; printf("浮点数f=%f\\n",f); //-257 + 1111 1111 = -2; printf("有符号sc数据扩展int=%u",0xffffffac); //Switch mySwitch(6); //循环goto myGoto(3); //循环while myWhile(3); //循环doWhile myDoWhile(5); //循环for myFor(5); //数组 int array[5]; // 不赋值,堆栈也默认会提升; int array1[5] = {1,2,3,4,5}; // mov dword [ebp+4], 0x01; char charArray[10] ; //本机宽度 ,内存对齐, 32位 4byte对齐, 64位 8byte对齐; short shortArray[10] ; //本机宽度 ,内存是对齐, //数组越界 myArrayOverStack(array1); /** *多维数组 , 一个班2个组,一个组5个人 //内存分布与一维数组一样 *内存中数据被打平存储 : 1,2,3,4,5, 5,4,3,2,1 *索引 array[2][3] = array[(2-1)*5+3] 算法 */ int array2[2][5] = { { 1,2,3,4,5 }, { 5,4,3,2,1 } }; //结构体/自定义类型, 未赋值之前不占用任何内存空间. //结构体为全局变量时占用空间 struct myStruct ms = {1,2,3}; printf("结构体属性值 c=%u\\n",ms.c); /** * ---------------- 字节对齐 --------------- * 全局变量, char c; int z; 变量内存地址是变量类型宽度的整数倍; * 以类型宽度最大的为准,上述以int为准,int 类型4byte,那么内存地址是4的整数倍; * */ printf("结构体属性c数据宽度=%u\\n",sizeof(ms.c)); printf("结构体属性s数据宽度=%u\\n",sizeof(ms.s)); printf("结构体数据宽度=%u\\n",sizeof(ms)); struct my8byteStruct m8s = {1,2,3}; //大尺寸对象 printf("结构体m8s属性c数据宽度=%u\\n",sizeof(m8s.c)); printf("结构体m8s属性s数据宽度=%u\\n",sizeof(m8s.s)); printf("结构体m8s数据宽度=%u\\n",sizeof(m8s)); /** *改变结构体数据宽度对齐 */ struct myPragmaStruct mps = {1,2,3}; // 压缩结构体尺寸, printf("结构体mps属性c数据宽度=%u\\n",sizeof(mps.c)); printf("结构体mps属性i数据宽度=%u\\n",sizeof(mps.c)); printf("结构体mps属性s数据宽度=%u\\n",sizeof(mps.d)); printf("结构体mps数据宽度=%u\\n",sizeof(mps)); //结构体数组 struct person p[2]; p[0].age = 0x12; stpcpy(p[0].name,"021"); printf("结构体person属性c数据值= %d\\n",p[0].age); printf("结构体person属性name数据值= %s\\n",p[0].name); /** *指针类型 : *多星的时候恒定数据宽度为4= char** cp ,一星的 char* cp 数据宽度=指针类型的宽度; *指针类型运算时以 指针类型的数据宽度为基数自增 *指针无符号 * */ char* cp ; //char******* cp ; int* ip ; short* sp ; cp = (char*)100; ip = (int*)100; sp = (short*)100; struct person* p1; printf("指针类型person数据值= %p\\n",p1); printf("指针类型cp数据值= %d\\n",cp); cp++; ip++; sp++; //指针类型不能做乘法和除法 //sp*1; printf("char指针类型cp自增后数据值= %d, 数据宽度=%d\\n",cp,sizeof(cp)); printf("int指针类型ip自增后数据值= %d\\, 数据宽度=%d\\n",ip,sizeof(ip)); printf("short指针类型sp自增后数据值= %d\\, 数据宽度=%d\\n",sp,sizeof(sp)); //指针类型比较 if(ip>cp) { printf("指针类型比较后数据值= %d\\n",ip); } /** * 取地址符 & , 只能和变量用 * lea指令=将局部变量地址获取 */ printf("取地址符 '&' ,sp地址值= %p\\n",&sp); //类型不匹配 能过编译,执行不生效 int ip1 = &cp; printf("取地址符 '&' ,ip1地址值= %p cp=%p\\n",&ip1,&cp); char* cp1 = &cp; printf("取地址符 '&' ,cp1地址值= %p cp=%p\\n",&cp1,&cp); /** * 取值运算符: '*', * 指针取地址值符 * , 只能指针类型用 * int*** a ; *a取值会变成 = int** a; * */ int* ip2 = &ip; printf("指针取值运算符 '*' ,指针cp1存储的数据值= %d\\n",*cp1); printf("指针取值运算符 '*' ,指针ip2存储的数据值= %d\\n",*ip2); int i2 = 1; int* ip3 = &i2; printf("指针地址取值'&' 地址值=%p ,指针取值运算符 '*' ,指针cp1存储的数据值= %d\\n",&i2,*ip3); /** * 参数传递,是地址传参,还是值传参, */ int methodAgr = 1; int methodAgrTestValue = methodAgrTest(methodAgr); methodAgrTest(methodAgr); printf("值传参还是地址传参 答案是值传递数据值= %d\\n",methodAgr); printf("值传参还是地址传参 答案是值传递数据值= %d\\n",methodAgrTestValue); /** *数组传参 *传递的是数组首地址第一个元素 int array[2] = {1,2} ; * mov eax , &array[1]; // 数组第一个元素的地址 * array[2] = lea dword ds:[eax+1*4] ; // 获取第二个元素是使用 第一个元素地址偏移量*4; * */ int arrayTestArg[2] = {1,2}; arrayTest(arrayTestArg,2); arrayPointTest(&arrayTestArg[0],2); /** * 字符串函数 * */ char str[3] ={'0','2','1',0}; //需要手动补0; printf("字符串str= %s\\n",str); //先在常量区 创建'021', 再把'021'复制到数组str1[]中,进行操作; char str1[4] = "021"; //编译器自动补0; printf("字符串str1= %s\\n",str1); printf("字符串str1= %s\\n",*(&str1)); char str2[9] = "021中国"; printf("strlen函数字符串str1长度= %d,值=%s\\n",strlen(str2),&str2); char cp3[6] ; strcpy(cp3, "china"); printf("strcpy复制字符串cp3= %s\\n",cp3); char love[10] ; char you[10] ; strcpy(love, "l love"); strcpy(you, "you"); strcat(love,you); printf("strcat拼接字符串= %s\\n",love); char love1[9]; char love2[9]; strcpy(love1, "l love"); strcpy(love2, "l love"); int stat =strcmp(love1,love2); //0相同,非0不相同; if(!stat) { printf("strcmp判断字符串love1与love2相同\\n"); } /** * 一级指针与多级指针 * 外包装*加1; * 拆包装*减1; */ //包装 int ip4 = 1; int* ip5 = &ip4; int** ip6 = &ip5; int*** ip7 = &ip6; //解包装 int ip8 = *(*(*(ip7))); printf("多级指针 ip8=%d\\n",ip8); printf("多级指针 ip7=%d\\n",***ip7); /** * 结构体指针 数据宽度恒定4字节 * */ struct myPoint mp; struct myPoint* mp1 = &mp; //类型对齐 printf("结构体指针数据宽度 mp1=%lu\\n",sizeof(mp1)); //赋值 mp1->a = 21; printf("结构体指针获取数据值mp1.a=%d\\n",mp1->a); //数组指针指向结构体指针 数据宽度恒定4字节 int arr[3] ={1,2,3}; struct myPoint* mp2 = (struct myPoint*)&arr; //指针类型强转 printf("结构体指针获取数据值mp2.a=%d\\n",mp2->a); /** * 指针数组与数组指针 */ //指针数组 数据宽度恒定4字节 char arr2[10];//20byte char* arr3[10]; //40byte 10指针(10* char*); struct myPoint* arr4[10]; //40byte 10指针(10* myPoint*); char* c2 = "china"; //将china字符串常量内存地址赋值给C1; char* c1 = "hello"; //将hello字符串常量内存地址赋值给C1; char* c3[10] = {c1,c2}; printf("结构体指针获取数据值c3=%s\\n",c3[0]); //数组指针 数据宽度恒定4字节 , cpu位数不同指针宽度可能不同 int arr5[3] = {0,1,2}; int* arrPoint = &arr5; //数组指针,取数组里第一个元素的地址 int* arrPoint1 = ++arrPoint; printf("结构体指针获取数据值arrPoint=%p ,指针值=%d \\n",&arrPoint1,*arrPoint); int* arrPoint2 = ++arrPoint; printf("结构体指针获取数据值arrPoint=%p ,指针值=%d \\n",&arrPoint2,*arrPoint); /** *函数指针—— 按照自定义方式调用目标函数; * 使用别人写好的函数 */ //定义函数指针,模拟自己写好的int __cdecl my_cdecl(int a,int b)函数 //自己写的函数 int result1 = my_cdecl(2,2); printf("函数指针调用被调用函数返回结果值=%d 返回值地址=%p\\n",result1,&result1); int (__cdecl *my_cdeclCall)(int,int); //赋值 ; 函数指针赋值执行地址,0x100002830为my_cdecl函数调用地址,此处为硬编码 //0x1000033c9 <+2873>: callq 0x100002830 ; my_cdecl at main.c:145 //my_cdeclCall = (int (__cdecl *)(int,int))0x1000027d0; //调用函数指针 //int result = my_cdeclCall(1,2); //结果为3; //printf("函数指针调用返回结果值=%d \\n",result); /** * 调用公约 * _cdecl - 从右到左压栈,调用者平栈(外平栈) * _stdcall - 从右到左压栈,函数自身平栈(内平栈) * _fastcall - ecx edx 传送前两个参数,剩下的从右到左入栈,函数自身平栈(内平栈) */ int arg = 0; int arg1 = 1; my_cdecl(0,1); my_stdcall(0,1); my_fastcall(0,1); /** * 预处理与自定义宏 * 逻辑少的时候用宏 * 逻辑多的时候用函数 */ // #define name1 "021" // #define true 1 // #define false 0 // #define hello1 "你好," // #define mystr strcpy(hello,name) printf("自定义宏调用返回结果值=%s \\n",name1); /** * 条件编译与文件包含 * 条件编译主要用于调试与测试代码的管理,把测试代码不编译到源文件 * * #undef debug //取消之前定义debug宏 * #define debug 0 //重新定义 * * #if debug * printf("编译后可以生成反汇编."); * #endif * * #if debug * printf("编译后不能生成反汇编."); * #endif * * #if define b //如果b定义了编译 * printf("编译后可以生成反汇编."); * #elif define a //如果a定义了编译 * printf("编译后可以生成反汇编."); * #else define c //如果c定义了编译 * printf("编译后可以生成反汇编."); * #endif * * * */ //使用包含文件函数 int fileResult = myAdd(1,2); printf("使用包含文件函数,返回结果=%d \\n",fileResult); return 0;} 厨师长特调之 —– 浮点数 精度概要 对于float型浮点数,尾数部分23位,换算成十进制就是2^23=8388608,所以十进制精度只有6 ~ 7位; 对于double型浮点数,尾数部分52位,换算成十进制就是2^52 = 4503599627370496,所以十进制精度只有15 ~ 16位 * **前景概要与演练** 123456789101112131415161718192021222324252627// 求十进制浮点数 ,求转换 12.341 * 10¹ + 2*1(10零次方) + 3 * 1/10(10的负一次方)+ 4 * 1/10*10(10的负二次方)浮点数的规范后: 1.234 * 10¹ ; 指数为1;// 二进制浮点转十进制 ,求转换 1100.11 1 * 2*2*2(三次方) + 1 * 2*2(二次方) + 0 * 2(一次方) + 0 * 1(2的0次方) + 1 * 1/2¹ + 1 * 1/2*2(二次方)= 8 + 4 + 0 + 0 + 1/2 + 1/4= 12.75浮点数的规范后: 2.475 * 10¹; 指数为1;求 1010.01 的十进制,二进制浮点数规范后= 1.01001 * 2*2*2 指数: 31 * 2*2*2 + 0 * 2*2 + 1 * 2 + 0 * 2(零次方) + 0 * 1/2¹ + 1 * 1/2²= 8 + 0 + 2 + 0 + 1/2 + 1/4= 10.75 10进制浮点数的规范后: 1.075 * 10¹; 指数为1;求 10101.01 的十进制,二进制浮点数规范后= 1.010101 * 2*2*2*2 指数: 41 * 2*2*2*2 + 0 * 2*2*2 + 1 * 2*2 + 0 * 2 + 1 * 1(2的零次方)+ 0 * 1/2 + 1 * /2*2= 16 + 0 + 4 + 0 + 1 + 0 + 1/4= 21.25 浮点数二进制存储表示 1,确定符号位 2,确定指数偏移量 3,对齐小数位并补0(总共23位) 符号位 0 正 1 负 指数位 按照127+指数偏移量得出二进制,填充进指数位,填充8位 小数位 去掉 浮点规范后的 整数位,低位补0,补齐23位 12345678910求 1.125的小数位:0.125×2=0.25 取整数位00.25×2=0.5 取整数位00.5×2=1 取整数位1小数部分所得结果为 001;为什么这么做, 当尾数*2=1的时候代表有进位,才能标记. 那我们来测试一下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445简单点以5结尾的小数;求 123.25 的内存存储1. 首先求出二进制数(小规律,先求数据宽度区间最大数和最小数,然后依次取最大数填充,这样就不用计算器了)123 = 100 + 20 + 3 = 1 1 1 1 1 1 1 1 = 01?? + ?1(确定最大最小数) 128 64 32 16 8 4 2 1 = 64 + 3 ,还差56,继续填充 = 01 1(32) 1(16) 1(8)011 = 0111 1011; 最终整数部分;小数部分:结果01推出:0.25 * 2 ; 00.50 * 2 ; 1整合: 0111 1011.01 , 浮点数规范 : 1.11 1011 01 指数为8; (错误)整合: 0111 1011.01 , 浮点数规范 : 1.111011 01 指数为6; (正确)2,定义符号位:正数 = 0;3,定义指数位:127 + 8 = 135 = 1000 0111(错误)127 + 6 = 133 = 1000 0101 (正确)4,定义尾数部分 ,去掉整数部分,低位补齐0,23位; 1.11101101 = 1110 1101;5,整合:0 + 1000 0111 + 1110 1101 = 0100 0011 1111 0110 1000 0000 0000 0000 = 0x43f68000; (错误)//纠错0 + 1000 0101 + 1110 1101 = 0100 0010 1111 0110 1000 0000 0000 0000 = 0x42f68000;(正确)// 结果 和 程序运行有误差 ... 0x42F68000 这个才是正确的,找一下问题:整合: 0111 1011.01 , 浮点数规范 : 1.11 1011 01 指数为8; (错误) 这里位移脑子抽了,计算失误. 还是计算机好...//到此 结果正确,0 + 1000 0101 + 1110 1101 = 0100 0010 1111 0110 1000 0000 0000 0000 = 0x42f68000;(正确) 厨师长特调之 —– Switch算法(参考数组存储) 在特定区间连续条件下,switch算法会进行优化 switch会维护一张地址表,编译器优化 1234567891011121314151617181920212223242526272829303132333435模拟代码逻辑:int minCase = 1; 最小的条件语句int maxCase = 9; 最大int myCondition = 1;//最小值处理,如果小于最小值直接不符合case区间,也会直接走default;if(myCondition < minCase) 如果大于最大值;直接跳默认值{ // Jcc跳转 jmp default;}最大值处理,如果大于最大值直接不符合case区间,直接走default;if(myCondition > maxCase) 如果大于最大值;直接跳默认值{ // Jcc跳转 jmp default;}//连续case区间逻辑跳转,参数数组内存存储,编译器优化连续case,利用偏移量找位置if((myCondition - minCase)*4的内存地址) { 如果条件为1 ; 1-1 = 0 ; 0 * 4 = 0 ;跳转到索引表第0个,一次类推,这个表的算法是编译器优化的, jmp addresstable;} 在case区间大的时候 switch 无法优化","categories":[{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/categories/C%E8%AF%AD%E8%A8%80/"}],"tags":[{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/tags/C%E8%AF%AD%E8%A8%80/"}]},{"title":"Kill someone who wirting C++&Java!","slug":"C++的花拳绣腿","date":"2021-11-19T19:08:02.000Z","updated":"2021-11-22T10:08:33.714Z","comments":true,"path":"2021/11/20/C++的花拳绣腿/","link":"","permalink":"http://www.010101.cc/2021/11/20/C++%E7%9A%84%E8%8A%B1%E6%8B%B3%E7%BB%A3%E8%85%BF/","excerpt":"","text":"杀了那个写C++和Java的人!Wirte by 021. Leave a message if i messed up ! : ) 封装&继承&多态123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146//// myMain.cpp// Assembly//// Created by jiankang on 2020/11/22.//#include <stdio.h>#include<string.h>#include <stdlib.h>struct person2{public: int age; char name; char* heapObject; void printThis() { printf("this当前指针=%p\\n",&(this->name)); } person2(int age,char name) { this->age = age; this->name = name; //heapObject = (char*)malloc(1024); printf("有参构造函数执行了,成员变量赋值初始化 \\n"); } person2() { printf("空参构造函数执行了 \\n"); } //析构函数 负责清理工作,Java的finalize ~person2() { printf("析构函数执行了,对象要销毁了 \\n"); //释放内存 //free(heapObject); } virtual void print(){ printf("person2父类多态方法被调用了 \\n"); } };//继承class teacher :public person2{ private: int coachAge; virtual void print(){ printf("teacher子类多态方法被调用了 \\n"); }};void myPrint(person2* p){ p->print();}#include "myMain.hpp"person2 p1; //全局变量,堆中分配,构造函数和析构函数(进程退出之前)都会执行;int main(int argc, const char* argv[]){ //局部变量在堆栈中分配,构造函数和析构函数(方法退出之前)都会执行 person2 p; printf("this会存储在ecx寄存器中,当前指针=%p\\n",&p); p.printThis(); int* ip = (int*)&p; printf("ip当前指针=%p\\n",&ip); int* ip1 = ++ip; printf("ip当前指针=%p\\n",&ip1); //堆中分配对象,利用空参构造创建一个对象 person2* p3 = new person2(); // new 汇编 : _nh_malloc ~ 系统函数malloc ; new = malloc + 构造函数; //释放堆中对象 delete p3; //C中申请内存 int* cp = (int*)malloc(sizeof(int)*10); person2* mallocP = (person2*)malloc(sizeof(person2)*10); //不会调用构造函数与析构函数; //释放 free(cp); free(mallocP); //C++申请内存 int* cp1 = new int[10]; person2* cpp = new person2[10]; //会调用构造函数与析构函数; //释放 delete[] cp1; //delete cpp; //只删除数组首元素; delete[] cpp; //删除堆中所有对象; /** * 引用类型-当前变量的别名 ; 变量地址的引用; mov xRef , x[address] */ int x = 10; printf("当前被引用类型指针=%p value=%d \\n",&x,x); int& xRef = x; printf("xRef当前指针=%p value=%d \\n",&xRef,xRef); xRef = 20; printf("xRef当前指针=%p value=%d \\n",&xRef,xRef); int** ipp = (int**)1; int** ipRef = ipp; ipRef = (int**)2; printf("ipRef当前指针=%p value=%d \\n",&ipRef,ipRef); // 指针与引用的差异 int* xp = &x; xp++; //int* xp - 减到一颗星的宽度,再自增 ,int为4 , 指针地址自增; printf("xp++当前指针=%p value=%d \\n",&xp,xp); xRef++; // 引用运算,只运算被引用对象的值; 此时 x= 21; printf("xRef++当前指针=%p value=%d \\n",&xRef,xRef); /** * 多态 virtual关键字 * 同一方法,能体现父类与子类的相同方法的不同体现; * 间接调用; 动态调用地址; 多态应用 * 直接调用; 地址是静态的,非多态应用 * */ myPrint(&p); //person2父类多态方法被调用了 teacher* t = new teacher(); myPrint(t); // teacher子类多态方法被调用了 /** * 模板设计模式 template<class T> 对应java的泛型,编译器把不同地址编译在调用函数中. */ /** * static就是私有全局变量; */ }","categories":[{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/categories/C%E8%AF%AD%E8%A8%80/"}],"tags":[{"name":"C++","slug":"C","permalink":"http://www.010101.cc/tags/C/"}]},{"title":"epoll之redis自顶向下","slug":"epoll之redis自顶向下","date":"2021-11-17T04:16:39.000Z","updated":"2021-11-17T06:24:08.704Z","comments":true,"path":"2021/11/17/epoll之redis自顶向下/","link":"","permalink":"http://www.010101.cc/2021/11/17/epoll%E4%B9%8Bredis%E8%87%AA%E9%A1%B6%E5%90%91%E4%B8%8B/","excerpt":"","text":"Epoll 之 Redis 自行向下Wirte by 021. Leave a message if i messed up ! : ) IO 进化史BIO阻塞IO Socket阻塞伪代码 12345678910111213141516171819202122232425262728//有1000个客户端读文件描述符的需求files waitRead = read(fd)*1000;1, file = read(waitRead[0]);2, if(file != null) { //do work } //单线程阻塞, 只能使用多线程处理, 每一条线程处理一个文件描述符的读取系统调用new Thread(files waitRead = read(fd)*1000;1, file = read(waitRead[0]);2, if(file != null) { //do work } ).start(); NIO同步非阻塞 同步非阻塞伪代码模拟** 12345678910111213141516171819//有1000个读文件描述符的需求read(int fd) * 1000;for(;;){ for(1000 * fd) // 用户空间 系统调用轮询1000次, { file = read(fd1~fd1000); //轮询读取每一个fd文件描述符,不管数据有没有准备好 if(file != null) { // do work.... } }} Select(多路复用)12345678910111213141516171819202122232425262728//有1000个读文件描述符的需求read(int fd) * 1000;//select 系统调用函数, 一次性传输多个文件描述符传输给内核,返回准备好的文件描述;select(fd*1000); file fds = select(fd*1000); // 系统调用,内核返回准备好的文件的描述符 for( 0;fds; ) // 用户空间遍历内核返回的全部文件描述符, { if(fd.readAble) //检测文件可读状态 { file = read(fd); if(file != null) { // do work.... } } } epoll 模拟内核 和 用户 共享空间逻辑 12345678910111213141516171819202122232425模拟原始内存空间:0xffffffff ------------用户空间-------------0x80000000 ------------内核空间------------ 0x00000000;模拟共享空间: //用户与内核 映射 同一块物理空间0xffffffff ---------用户空间---------[0x80000000 ---共享空间--- 0x700000000 ]------内核空间----- 0x00000000;// 共享空间容器1,红黑树 - 等待读取的文件描述符 ,btree;2,链表 - 准备好的文件描述符 , readyLinkList;btree.add(epoll.creat(fd)); // 将待读取的文件描述符 放入共享空间的红黑树.file = read(readyLinkList(fd)); //读取 共享空间 链表容器 已准备好的文件描述符; redis 单进程 单线程 为什么快? 如何保证顺序性? 每个连接 sendfile 零拷贝 ","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"Redis","slug":"Redis","permalink":"http://www.010101.cc/tags/Redis/"}]},{"title":"WIN32之保护模式","slug":"WIN32保护模式","date":"2021-11-15T11:42:37.000Z","updated":"2021-11-16T03:40:44.939Z","comments":true,"path":"2021/11/15/WIN32保护模式/","link":"","permalink":"http://www.010101.cc/2021/11/15/WIN32%E4%BF%9D%E6%8A%A4%E6%A8%A1%E5%BC%8F/","excerpt":"","text":"Java编译器浅析Wirte by 021. Leave a message if i messed up ! : ) 段 寄存器 Selector Attribute Base Limit ESCSSSDSFSGSLDTRTR页","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}]},{"title":"Win32汇编基础","slug":"汇编之基础","date":"2021-11-14T02:25:43.000Z","updated":"2021-11-15T09:27:12.564Z","comments":true,"path":"2021/11/14/汇编之基础/","link":"","permalink":"http://www.010101.cc/2021/11/14/%E6%B1%87%E7%BC%96%E4%B9%8B%E5%9F%BA%E7%A1%80/","excerpt":"","text":"汇编基础Wirte by 021. Leave a message if i messed up ! : ) 寄存器32位 提供三种容器寄存器 : 8 , 16 , 32. 64位 提供4中容器寄存器 : 8 ,16 ,32 ,64. 通用寄存器32位 用户自定义使用寄存器 数据宽度为32位 多余的丢弃 EAX 返回值容器 ECX REP执行计数器 EDX EBX ESP 栈指针寄存器,栈内存起始位置到ESP指针位置为已使用内存 EBP 栈底 ESI movs 使用 EDI movs 使用 EFL 内存地址高低方向位 非通用寄存器EIP cpu下次执行时会找EIP存的值 指令 mov 操作数数据宽度需要相同 12345678910111213mov eax,1 ; 将1存进eax寄存器。mov ecx,1 ; 将1存进ecx寄存器。mov eax,ecx ; 将ecx存进eax寄存器 x64模拟器:%rax 作为函数返回值使用。%rsp 栈指针寄存器,指向栈顶%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。%rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值 add 加法 1add eax,ecx; 将ecx 与 eax 相加 存进eax寄存器; sub 减法 1sub eax,ecx; 将ecx 与 eax 相减 存进eax寄存器; AND 与运算 1AND eax,ecx; 将ecx 与 eax 与运算 存进eax寄存器; or 或运算 1or eax,ecx; 将ecx 与 eax 与或算 存进eax寄存器; xor 异或运算 1xor eax,ecx; 将ecx 与 eax 与异或算 存进eax寄存器; NOT 非运算 1not eax, 将eax取反 ,存进eax寄存器; **movs 从内存到内存 ** 每复制一次 内存地址自增长当前数据宽度位 123456Movs byte ptr es:[edi] , byte ptr ds:[esi]Movs byte ptr es:[0x00000000] , byte ptr ds:[0x00000001]ELF stos 将AI/AX/EAX的值存储到[EDI]指定的内存单元 123STOS WORD PTR ES:[edi] REP 重复执行 ,依赖ecx 寄存器的计数器 10进制,每次执行ecx值减一 JMP 寄存器/立即数/内存 修改EIP的值 123jmp dword ptr ds:[eax]; 将eax的地址值,赋给EIP,cpu下次执行的将是eax里面的指令. CALL 修改EIP值,再将ESP-4,并将栈顶值压入在ESP-4的地址中 RET 1,将当前栈顶的值放进EIP中 2,将当前ESP指针值+4 内存地址 0x0000000 1数据宽度:32位. 内存基本单元= 1byte = 8bit 往内存中写数据 mov 数据宽度 ptr ds:[内存地址] ,值 , mov byte ptr ds:[0x00000000] ,1 mov 数据宽度 ptr ds:[内存地址] ,值 , mov word ptr ds:[0x00000000] ,1 ; 单字宽度 mov 数据宽度 ptr ds:[内存地址] ,值 , mov Dword ptr ds:[0x00000000] ,1; 双字宽度 mov 数据宽度 ptr ds:[内存地址] ,值 , mov Dword ptr ds:[0x00000000] ,eax; 将寄存器写入内存 mov 数据宽度 ptr ds:[内存地址] ,值 , mov eax, Dword ptr ds:[0x00000000] ; 将内存写入寄存器 mov 数据宽度 ptr ds:[内存地址] ,值 , mov eax, Dword ptr ds:[ecx+4] ; 将ecx内存地址+4的位置值写入寄存器 存储模式 小端模式 x86 1234数据低位在低位,数据高位在高位.mov word ptr ds:[0x00000000],0x1a2c , 0x00000000[1a], 0x00000001[2c]. 大端模式 arm 1234数据低位在高位,数据高位在低位.mov word ptr ds:[0x00000000],0x1a2c , 0x00000000[2c], 0x00000001[1a]. 堆栈 PUSH压栈 push指令将数据压入栈中,并移动栈针ESP ,(eps - 数据宽度) pop弹栈 pop指令将数据弹出栈外,并移动栈针ESP ,(eps + 数据宽度) pop eax; 将栈顶的值存入eax中,esp指针 + eax值的数据宽度 函数 JMP调用函数(通常不用) CALL()调用函数** 基本函数调用流程 12345678910111213141516171819202122232425求 10 + 7 = ?10 = 0000 1010 = 0x0a; 7 = 0000 0111 = 0x07;模拟入参: 初始地址 esp : 0x00000128 ecx : 0x0000012c edx : 0x00000131 0x00000135 : null; 0x00000139 mov ecx 0x0a;0x0000013D mov edx ox07; //加0x00000142 add ecx ,edx; //移动返回值0x00000146 mov eax, ecx;0x0000014a ret;执行调用 call 0x00000139; 首先将 jmp eip ,esp; 然后 mov esp-4, esp; 再执行0x00000139位置;函数执行 139 13d 142 系列函数之后到达146 ret返回点,ret首先 jmp eip ,esp; 然后 mov esp+4 ,esp; 堆栈函数与平衡 1234567891011121314151617181920212223242526272829303132333435求 1 + 2 + 3 + 4 = ?0x0000 0117 ; push 4;0x0000 011b ; push 3;0x0000 0120 ; push 2;0x0000 0124 ; push 1;0x0000 0128 ; add eax, dword ptr ds:[esp+10], 由于压入4个立即数,堆栈地址增加了16,所以将第一个数与eax相加............. add eax, dword ptr ds:[esp+0c],............ add eax, dword ptr ds:[esp+08],............ add eax, dword ptr ds:[esp+04],............ ret;call 0x0000 0117;============================================================================================堆栈平衡: 在调用函数中 应该保证函数调用前后,堆栈的一致性不发生变化;在ret之前 应该保证堆栈指针esp 是call函数压入的esp地址;0x0000 0117 ; add eax, dword ptr ds:[0x0000 011b]0x0000 011b ; push 3; ------ 此时esp已经发生变化 返回时指向esp的地址和堆栈已经发生变化,再次执行就会出错0x0000 0120 ; push 2;0x0000 0124 ; push 1; 0x0000 0128 ; ret; ------ ret指令 :将esp值放到eip,然后将esp的值+4;怎么平衡?外平栈call 0x0000 0117; add esp , 0x0c; 将esp复位;内平栈0x0000 0128 ; ret 0x0c; 在函数内部将esp复位; ESP栈顶寻址1234567891011121314151617181920212223242526求 1 + 2 + 3 + 4 = ?//调用函数 0x0000 0113 : call 0x0000 0117; 压栈后栈顶指针esp;//执行压栈0x0000 0117 ; push 4; 0x0000 011b ; push 3;0x0000 0120 ; push 2;0x0000 0124 ; push 1;//执行相加0x0000 0128 : add eax , dword ptr ss:[esp+14] ; 执行加第一个数 1;0x0000 012c : add eax , dword ptr ss:[esp+10] ; 执行加第2个数 2;0x0000 0130 : add eax , dword ptr ss:[esp+c] ; 执行加第3个数 3;0x0000 0134 : add eax , dword ptr ss:[esp+8] ; 执行加第4个数 4;// 平栈 弹出/修正esp位置0x0000 0130 : pop 1 ;pop 1 ~ 4;0x0000 0134 : ret; EBP栈底寻址1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950求 1+2 = ?整体步骤,1: 将参数压栈2: 调用函数, 2.1 备份esp到ebp, 2.2 开辟新空间, 2.3 复位esp, 2.4弹出备份ebp; 2.5平栈初始堆栈:esp : 0x0000 0120;ebp : 0x0000 011c;//1. 参数压栈push 2; esp : 0x0000 0118; eip : 0x0000 0001;push 1; esp : 0x0000 011c; eip : 0x0000 0002;//2. 调用函数call 0x0000 0005; esp : 0x0000 0114; ( eip : 0x0000 0004; 指向下一条指令)// 下一条的指令;mov eax,1; eip : 0x0000 0004;==============================================================函数:1,备份栈底ebppush ebp; esp : 0x0000 0110; eip : 0x0000 0005;2,记录起始ebp的值mov ebp,esp; esp : 0x0000 0110; ebp : 0x0000 0110; eip : 0x0000 0006;3,开辟新空间 开辟16个字节sub esp,10; esp : 0x0000 0100; eip : 0x0000 0007;4,执行加操作add eax ,dword ptr ss:[ebp+c] eip : 0x0000 0008;add eax ,dword ptr ss:[ebp+8] 5,复位堆栈mov esp,ebp; esp : 0x0000 0110; pop ebp; esp : 0x0000 0114; ret c; esp : 0x0000 0120; JCC指令集 JCC指令根据标志寄存器值的标志位来修改EIP的,从而实现执行跳转 标志寄存器 运算相关指令都会影响标志寄存器 JCC指令 中文含义 英文原意 检查符号位 典型C应用 JZ/JE 若为0则跳转;若相等则跳转 jump if zero;jump if equal ZF=1 if (i == j);if (i == 0); JNZ/JNE 若不为0则跳转;若不相等则跳转 jump if not zero;jump if not equal ZF=0 if (i != j);if (i != 0); JS 若为负则跳转 jump if sign SF=1 if (i < 0); JNS 若为正则跳转 jump if not sign SF=0 if (i > 0); JP/JPE 若1出现次数为偶数则跳转 jump if Parity (Even) PF=1 (null) JNP/JPO 若1出现次数为奇数则跳转 jump if not parity (odd) PF=0 (null) JO 若溢出则跳转 jump if overflow OF=1 (null) JNO 若无溢出则跳转 jump if not overflow OF=0 (null) JC/JB/JNAE 若进位则跳转;若低于则跳转;若不高于等于则跳转 jump if carry;jump if below;jump if not above equal CF=1 if (i < j); JNC/JNB/JAE 若无进位则跳转;若不低于则跳转;若高于等于则跳转; jump if not carry;jump if not below;jump if above equal CF=0 if (i >= j); JBE/JNA 若低于等于则跳转;若不高于则跳转 jump if below equal;jump if not above ZF=1或CF=1 if (i <= j); JNBE/JA 若不低于等于则跳转;若高于则跳转 jump if not below equal;jump if above ZF=0或CF=0 if (i > j); JL/JNGE 若小于则跳转;若不大于等于则跳转 jump if less;jump if not greater equal jump SF != OF if (si < sj); JNL/JGE 若不小于则跳转;若大于等于则跳转; jump if not less;jump if greater equal SF = OF if (si >= sj); JLE/JNG 若小于等于则跳转;若不大于则跳转 jump if less equal;jump if not greater ZF != OF 或 ZF=1 if (si <= sj); JNLE/JG 若不小于等于则跳转;若大于则跳转 jump if not less equal;jump if greater SF=0F 且 ZF=0 if(si>sj)","categories":[{"name":"Assembly","slug":"Assembly","permalink":"http://www.010101.cc/categories/Assembly/"}],"tags":[{"name":"汇编","slug":"汇编","permalink":"http://www.010101.cc/tags/%E6%B1%87%E7%BC%96/"}]},{"title":"C语言基础之内存分布","slug":"C语言之内存分布","date":"2021-11-14T02:25:43.000Z","updated":"2021-11-18T16:46:36.633Z","comments":true,"path":"2021/11/14/C语言之内存分布/","link":"","permalink":"http://www.010101.cc/2021/11/14/C%E8%AF%AD%E8%A8%80%E4%B9%8B%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83/","excerpt":"","text":"C语言之内存分布简单分析Wirte by 021. Leave a message if i messed up ! : ) C源码 | 脑图模拟汇编Win32 win32汇编设计上还是有很多不合理,造成了很多资源浪费,比如断点C的填充,在AT&T的汇编上得到了改良 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111//减函数int sub(int c){ return c-1;}// 加函数int add(int a , int b){ int c = a + b; //函数嵌入 c = sub(c); return c;}//main函数int main(int argc, const char * argv[]) { // insert code here... int a = 20; int b = 10; int c = add(a,b); //int a = 7; //putchar(a); //printf("Hello, World! %d \\n",c); return 0;}================================模拟汇编堆栈====================================初始堆栈:esp 0x0000 02ff;ebp 0x0000 0208;eip 0x0005 0001;//预留三个参数寄存器esi ....edi ....ebx ....============================ main start =====================// 1. 提栈0x0005 0001 ------------ push ebp; | esp 0x0000 01fb; ebp 0x0000 0208[0x0000 0208]; 0x0005 0002 ------------ mov ebp, esp; | esp 0x0000 01fb; ebp 0x0000 01fb[0x0000 01fb]; 0x0005 0003 ------------ sub esp, 0x20; | esp 0x0000 01db; ebp 0x0000 01fb[0x0000 01fb]; // 2. 压栈 - 编译环境不一样,编译器优化汇编 可能有一些差异,整体逻辑是不变的, // 有的环境是直接取寄存器的值,有的直接压值,这里选择压值, // win32 压在缓冲区外,AT&T 压在缓冲区内: push 0x14 , ebp - 0x04;0x0005 0004 ------------ push 0x14; | esp 0x0000 01d7; ebp 0x0000 01fb[0x0000 01fb]; 0x0005 0005 ------------ push 0x0a; | esp 0x0000 01d3; ebp 0x0000 01fb[0x0000 01fb]; // 3. 函数调用0x0005 0006 ------------ call 0x0005 000b; | esp 0x0000 01cf[0x0005 0007]; ebp 0x0000 01fb[0x0000 01fb]; // 此时 eax = 29; esp esp 0x0000 01cf; ebp 0x0000 01fb[0x0000 0208]; eax = 29;// 4.恢复堆栈0x0005 0007 ------------ mov esp, ebp; | esp 0x0000 01fb; ebp 0x0000 01fb[0x0000 0208]; 0x0005 0008 ------------ pop ebp; | esp 0x0000 01ff; ebp 0x0000 01fb[0x0000 0208]; //由于没有选择寄存器传值,压入的两个参数此时不用处理,平栈的方式很多种,这里似乎可以直接平.//0x0005 0009 ------------ add esp,0x20; | esp 0x0000 01fb; ebp 0x0000 01fb[0x0000 0208]; 0x0005 0009 ------------ ret; | esp 0x0000 01ff; ebp 0x0000 0208; =================================== main end ===============================// 加函数============================ add start ===================== esp 0x0000 01cf0x0005 000b ------------ push ebp - 0x0c ,eax ; | esp esp 0x0000 01cb; ebp 0x0000 01fb[0x0000 0208]; 0x0005 000b ------------ mov ebp, esp ;| esp esp 0x0000 01cb; ebp 0x0000 01cb[0x0000 01cb]; // 加0x0005 000c ------------ mov eax , ebp - 0x08; | esp 0x0000 01cb; ebp 0x0000 01cb[0x0000 01cb]; 0x0005 000d ------------ add eax , ebp - 0x04; | esp 0x0000 01cb; eax = 30;//压参数 0x0005 000e ------------ push ebp - 0x0c , eax; | esp 0x0000 01c7; eax = 30;0x0005 000f ---- call 0x0005 0013 | esp esp 0x0000 01c3[0x0005 0000]; ebp 0x0000 01cb[0x0000 01cb]; // 恢复堆栈0x0005 0000 ------------ pop ebp ; | esp esp 0x0000 01c7; ebp 0x0000 01fb[0x0000 0208]; 0x0005 0011 ------------ add esp,0x08 ; | esp 0x0000 01cf; ebp 0x0000 01cb[0x0000 01cb]; 0x0005 0012 ------------ ret ; | esp 0x0000 01f9; ebp 0x0000 01fb[0x0000 0208]; =================================add end====================================// 减函数============================ sub start =====================// win32汇编并没有使用寄存器传参,此处eax应该使用寄存器,或者EBP寻址;0x0005 0013 ------------ sub ebp - 0x0c , 0x01; | esp 0x0000 01c3; ebp 0x0000 01fb[0x0000 0208]; 0x0005 0014 ------------ mov eax , ebp - 0x0c; | esp 0x0000 01c3; ebp 0x0000 01fb[0x0000 0208]; 0x0005 0015 ------------ ret; | esp 0x0000 01c3; ebp 0x0000 01fb[0x0000 0208]; eax = 29;============================ sub end =====================","categories":[{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/categories/C%E8%AF%AD%E8%A8%80/"}],"tags":[{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/tags/C%E8%AF%AD%E8%A8%80/"}]},{"title":"内存管理之页","slug":"内存管理","date":"2021-11-14T02:25:43.000Z","updated":"2021-11-26T11:16:46.277Z","comments":true,"path":"2021/11/14/内存管理/","link":"","permalink":"http://www.010101.cc/2021/11/14/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/","excerpt":"","text":"101012 分页Wirte by 021. Leave a message if i messed up ! : ) 线性地址mov eax , dword ds:[0x00000124] 其中0x00000124是有效地址 ds.base(段寄存器) + 0x0000124 是线性地址。 101012 地址拆分 **32位地址示例 0x00000124 ** 12345678910110x00000124 =将 32位 拆成 10 - 10 - 12 ;0000 0000 00 , 10 ;00 0000 0000 , 10 ;0001 0010 0100 , 12 ; CR3物理地址寄存器 CR3寄存器存储了物理地址 CR3指向了一个4KB页 第一级分页(页目录表PDT)(4kb)(第一个10) 存储的地址, 第一级分页存储了1024个第二级分页的地址 12345678第一级分页数据结构:每个PDE地址数据宽度:4个字节;4096/4 = 1024; 0地址不能读写是因为没有挂物理页,挂了之后可以读写 第二级分页(页表PTT)(第二个10) 数据为:PTE 多个PTE可以指向同一个物理页 一个PTE只能指向一个物理页,不能指向多个物理页 一个可以不指向物理页 第三级分页(物理页)(第三个12) 一个物理页的大小是4kb=4096=2的12次方,也就是需要12个bit位才能表示4kb中所有的地址. 指向物理页地址 物理地址 CPU 将线性地址转换的物理地址 R/W可读可写位 P数据有效位 U/S 权限位,0 - 特权用户,1 - 普通用户 **PS位对PDE有用,PageSize的意思,当ps=1的时候是大页,低22位直接指向物理页. ** A位,是否访问位 D位,duty, 是否被写过,0 - 没有写过,1 - 写过 页目录基址(0xc03000000) PDT页目录表的基址 访问进程内存公式 10-10-12 ; PDI(页目录项) : 10 , PTI(页表项) : 10 , 12 物理页 访问页目录的公式 0xc0300000 + PDI*4 访问页表的公式 0xc0000000 + PDI * 4096 + PTI * 4 2 - 9 - 9 - 12 分页(PAE 物理地址拓展分页) 2 : PDPTE 页目录指针表(page directory point table entry) 数据宽度2个bit, 最多拥有4个元素 **9 : PDE ** 9 : PTE 12 : 物理页 当x=1时,此数据为不可执行数据. TLB","categories":[{"name":"内存管理","slug":"内存管理","permalink":"http://www.010101.cc/categories/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"}],"tags":[{"name":"页","slug":"页","permalink":"http://www.010101.cc/tags/%E9%A1%B5/"}]},{"title":"汇编之趣味计算机","slug":"汇编趣味计算机","date":"2021-11-13T07:56:26.000Z","updated":"2021-11-15T07:55:07.558Z","comments":true,"path":"2021/11/13/汇编趣味计算机/","link":"","permalink":"http://www.010101.cc/2021/11/13/%E6%B1%87%E7%BC%96%E8%B6%A3%E5%91%B3%E8%AE%A1%E7%AE%97%E6%9C%BA/","excerpt":"","text":"汇编之有趣的计算机Wirte by 021. Leave a message if i messed up ! : ) 汇编的加减乘除:1234567891011 汇编语言 编译器 机器语言 加 INC EAX ---> 0100 0000 减 DEC EAX ---> 0100 1000乘 MUL EAX ---> 1111 0111 1110 0000除 DIV EAX ---> 1111 0111 1111 0000 进制换算1234567891011120000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 150 1 2 3 4 5 6 7 8 9 A B C D E F测试:hex : 6d 7f 008c e3 246hex to bin6 + d = 0110 1101, 7 + f = 0111 1111, 8 + c =0000 0000 1000 1100, e + 3 = 1110 0011, 2 + 4 + 6 = 0010 0100 0110. 数据宽度 超过数据宽度将被丢弃 12345678910111213141516171819202122232425262728293031323334353637383940414243位 bit: 0,最大值 1.字节 byte: 0000 0000 , 最大值 1111 1111 , 0 - ff. 0xff.字word: 0000 0000 0000 0000 ,最大值 1111 1111 1111 1111 , 0 - ffff, 0xffff.双字doubleWord: 0000 0000 0000 0000 0000 0000 0000 0000 0000 , 最大值 0xffffffff.数据宽度测试:char a = 0x1ff, 存入数据是ff 舍弃了1,因为宽度不够.Xcode 进入debug 模式, 在右边控制台或者内存值, 输出:x/1xb 0x7ffeefbff3df , 取1个字节的值,用16进制表示,表示单字节0x7ffeefbff3df: 0xff 只存了ff。x/nfu <addr> n,表示要显示的内存单元的个数f,表示显示方式, 可取如下值:x 按十六进制格式显示变量d 按十进制格式显示变量u 按十进制格式显示无符号整型o 按八进制格式显示变量t 按二进制格式显示变量a 按十六进制格式显示变量i 指令地址格式c 按字符格式显示变量f 按浮点数格式显示变量u,表示一个地址单元的长度:b 表示单字节h 表示双字节w 表示四字节g 表示八字节x/16xb self会显示 self 指针地址内容,16 个字节,16 进制。x/8cb 0x7fc359a03040会显示地址 0x7fc359a03040 地址的内容,8 个字节,按字符格式显示。 有符号 与 无符号 有符号数与无符号数正数显示结果一样,负数结果不一样 有符号 1无符号 0000 0001 就是 0000 0001, 无符号 123456789101112测试: 1 , 0x01,最高位 0, 是正数.0000 0001 最高位 1,是负数.1000 0001 , 0x81int x = 0x81000000; printf("有符号表示 %u\\n",x);printf("无符号表示 %d\\n",x);有符号表示 2164260864无符号表示 -2130706432 原码 反码 补码原码 正数存储用原码反码补码都一样 12最高位是符号位,其余位为数值的绝对值. 1 的原码 0000 0001 反码 正数的反码原码相同,负数符号位1,其余位取反 123-1原码: 1000 0001反码: 1111 1110 , 最高位不变. 补码 -!!!负数存储!!! 负数补码存储,符号位不变,正数不变,其余位反码加1, 123456789101112131415161718正码1 , 0000 0001.反码-1, 1111,1110.反码-1, 1111 1111.测试: 求 1,-1 在内存中的16进制值;1: 正数无需关心,直接转16进制 = 0x01.-1: 反码+1, 原码 1000 0001 , 反码 ,1111 1110 , 补码+1 , 1111 1111, 得出内存值 0xff.cha x = -1;输出:x/1xb 0x7ffeefbff3df0x7ffeefbff3df: 0xff 位运算与运算 **同位为1,结果才是1,否则是0. ** 并且(当2个开关都闭合灯才亮) 1234测试:C: 1 & 2 , assembly: and0000 00010000 0010结果 : 0000 0000; 结果为0; 或运算 同位只要有1,就是1 或者 (两个开关只要一个开关闭合灯就能亮) 12345测试: C: 1 | 2 , assembly : or0000 00010000 0010结果为: 0000 0011; 结果为3; 异或运算 同位相反才为1,否则为0 不一样的时候才为1 123456测试: C: 1 ^ 2 , assembly : xor0000 00010000 0010结果为: 0000 0011; 结果为3; 非运算 同位交换,单数运算 12345测试: 1 ,C: ~ , assembly : not0000 0001结果为: 1111 1110; 左移运算 各二进制位全部向左移动若干位,高位丢弃,地位补0 12345678910C:<< , assembly : shl测试 : 1,左移1位,0000 0001 ,结果: 0000 0010; = 2.左移2;0000 0001, 结果: 0000 0100; = 4.char c = 1 << 2;输出为:4 右移运算 各二进制位向右移动若干位,无符号高位补0或者有符号高位补1,低位丢弃 1234567891011121314151617C:>> , assembly : shr测试 : 1,右移1位,0000 0001 ,结果: 0000 0000; = 0.右移2;0000 0001, 结果: 0000 0000; = 0.补符号: 高位补1C:>> , assembly : sar测试数: 10;右移2位;0000 1100; 结果: 1000 0011; 负数存的是反码+1, 1111 1101, 内存值= 0xfd;1000 1010 -> 1111 0110 补码值 :0xf6 右移-> 1111 1101; fd + - * /+1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980求 10 + 7 = ?//混搭伪代码int a = 10;int b = 7;funcAdd(a,b){ //转换 0000 1010 0000 0111 //第一步先异或 int tmp = funXor(a,b) funXor(a,b); = 0000 1101 //第二步判断是否有进位 int shlTemp = funcCmp(a,b); 0000 0010; 有进位 //判断是否有进位 , if(shlTemp!=0) { //有进位需要位移,位移后的值. int shlTempValue = funcSHL(shlTemp,1); 0000 0100; //将第一次的结果与进位的结果异或 ,第一次递归调用 funcAdd(tmp,shlTempValue); //第二次异或; 0000 1101 0000 0100 = tmp = 0000 1001; 第二次与运算对比,有进位;shlTemp = 0000 0100 shlTempValue = funcSHL(shlTemp ,1) = 0000 1000; //第二次递归调用funcAdd(tmp, shlTempValue); 0000 1001 0000 1000 //第三次异或运算 tem = 0000 0001 //第三次对比 与 运算 shlTemp =0000 1000; //有进位,第三次位移 shlTempValue = 0001 0000; //第三次次递归调用funcAdd(tmp, shlTempValue) //第四次异或 0000 0001 0001 0000 tem = 0001 0001 = 17; //第四次与运算,判断是否有进位 //第四次无进位,判断执行完毕,执行后面代码 } returun tem;}//与运算funcCmp(a,b){ 0000 1010 & 0000 0111 return a&b = 0000 0010; 如果非0,作为返回值继续异或}//左移1位funcSHL(int a,int offset){ a = 0000 0010; offset = 1; return a << 1 = 0000 0100; }//异或运算funXor(a,b){ a = 0000 1010 b = 0000 0111 // 假设没进位 return a^b; 0000 1101;} -123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105求 10 - 7 = ?减法就是加法 , 10-7 = 1 + (-7); -7 是补码存储,求补码:正码: 0000 0111; 反码: 1111 1000;补码=反码+1= 1111 1001; 内存=0xf9;//混搭伪代码int a = 10;int b = -7;funcAdd(a,b){ //转换 0000 1010 1111 1001 //第一步先异或 int tmp = funXor(a,b) funXor(a,b); = 1111 0011 //第二步判断是否有进位 int shlTemp = funcCmp(a,b); 0000 1000; 有进位 //判断是否有进位 , if(shlTemp!=0) { //有进位需要位移,位移后的值. int shlTempValue = funcSHL(shlTemp,1); 0001 0000; //将第一次的结果与进位的结果异或 ,第一次递归调用 funcAdd(tmp,shlTempValue); //第二次异或; 1111 0011 0001 0000 = tmp = 1110 0011; 第二次与运算对比,有进位;shlTemp = 0001 0000 shlTempValue = funcSHL(shlTemp ,1) = 0010 0000; --------------n次递归----------------- 求 10 - 7 = 10 + (-7) 0000 1010 1111 1001 异或 tmp = 1111 0011 进位: 0000 1000 进位后的值 shlTempValue = funcSHL(000 1000 ,1) = 0001 0000 ------------------------------------- 1111 0011 0001 0000 异或 tmp = 1110 0011 进位: 0001 0000 进位后的值 shlTempValue = funcSHL(0001 0000 ,1) = 0010 0000 --------------------------------------- 1110 0011 0010 0000 tmp = 1100 0011 shlTempValue = funcSHL(0010 0000 ,1) = 0100 0000 ------------------------------- 0100 0000 1100 0011 tmp = 1000 0011 shlTempValue = funcSHL(0100 0000 ,1) = 1000 0000 --------------------------------- 1000 0000 1000 0011 tmp = 1000 0011; shlTempValue = funcSHL(0100 0000 ,1) = 0001 0000 0000 0001 0000 0000 0000 0000 0011 0001 0000 0011 ??????WTF 此时结果产生了溢出; 篇幅原因 就不展开讲了 ------------------------------------- tem = 0001 0000 0011 ; //第四次与运算,判断是否有进位 //第四次无进位,判断执行完毕,执行后面代码 } returun tem;}//与运算funcCmp(a,b){ 0000 1010 & 0000 0111 return a&b = 0000 0010; 如果非0,作为返回值继续异或}//左移1位funcSHL(int a,int offset){ a = 0000 0010; offset = 1; return a << 1 = 0000 0100; }//异或运算funXor(a,b){ a = 0000 1010 b = 0000 0111 // 假设没进位 return a^b; 0000 1101;} *123456789求 10 * 7 = ? 解: 7 个 10 相加; int a = 10;int b = 7;for (int i =0; i < 7 ; i++ ){ funcAdd(a,a);} /12345678910求 10 * 7 = ? 解: 7 个 10 相加; int a = 10;int b = 7;for (int i =0; i < 7 ; i++ ){ //转换补码负数 b = 1111 1001; funcAdd(a,b);}","categories":[{"name":"Assembly","slug":"Assembly","permalink":"http://www.010101.cc/categories/Assembly/"}],"tags":[{"name":"汇编","slug":"汇编","permalink":"http://www.010101.cc/tags/%E6%B1%87%E7%BC%96/"}]},{"title":"JVM之GC Stop The World & SafePoint","slug":"JVM之GC Stop The World & SafePoint","date":"2021-11-13T05:08:57.000Z","updated":"2021-11-13T07:06:33.524Z","comments":true,"path":"2021/11/13/JVM之GC Stop The World & SafePoint/","link":"","permalink":"http://www.010101.cc/2021/11/13/JVM%E4%B9%8BGC%20Stop%20The%20World%20&%20SafePoint/","excerpt":"","text":"GC Stop The World & SafePoint浅析Wirte by 021. Leave a message if i messed up ! : ) 1. 在哪里停止 Where is SafePoint? 什么是安全点SafePoint? 安全点的实现 2. 什么时候停 When is the Stop the world happend?3. 停下来做什么 after Stop The World?上一篇 :GC垃圾回收原理浅析:https://www.010101.cc/2021/11/11/JVM%E4%B9%8BGC%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%B5%85%E6%9E%90/","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"JVM之GC调优方法论","slug":"JVM之GC调优方法论","date":"2021-11-13T05:08:57.000Z","updated":"2021-11-13T05:56:33.512Z","comments":true,"path":"2021/11/13/JVM之GC调优方法论/","link":"","permalink":"http://www.010101.cc/2021/11/13/JVM%E4%B9%8BGC%E8%B0%83%E4%BC%98%E6%96%B9%E6%B3%95%E8%AE%BA/","excerpt":"","text":"JVM之GC调优方法论Wirte by 021. 对象头 1. GC调优目标:高吞吐型: * 快速响应型:上一篇 :GC垃圾回收原理浅析:https://www.010101.cc/2021/11/11/JVM%E4%B9%8BGC%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%B5%85%E6%9E%90/","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"JVM之Java编译器浅析","slug":"JVM之编译浅析","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-13T07:21:02.195Z","comments":true,"path":"2021/11/11/JVM之编译浅析/","link":"","permalink":"http://www.010101.cc/2021/11/11/JVM%E4%B9%8B%E7%BC%96%E8%AF%91%E6%B5%85%E6%9E%90/","excerpt":"","text":"Wirte by 021. Java编译器浅析 本地代码(native code) 是计算机编程(代码),编译用来运行一个特殊的处理器(如英特尔x86级的处理器)和它的特殊指令集,简称机器码. Win : exe Linux : elf 模式1 : 即时编译JIT编译器-Just in time. 主要用于JVM将 **class热点代码 ** 编译为本地代码. 模式2: 解释器 byteCode intepreter 在程序运行过程中,JVM将字节码再转换成本地可执行的本地代码,边解释字节码边执行,故而称为解释器。 参数设置123默认: -Xmixed 混合模式-Xint 解释模式,启动块,执行稍微慢-XComp 编译模式,启动时间慢,需要编译 编译器 — TODO C1 C2","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"JVM之GC垃圾回收器与算法深入浅出","slug":"JVM之GC垃圾回收浅析","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-15T10:01:27.105Z","comments":true,"path":"2021/11/11/JVM之GC垃圾回收浅析/","link":"","permalink":"http://www.010101.cc/2021/11/11/JVM%E4%B9%8BGC%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%B5%85%E6%9E%90/","excerpt":"","text":"JVM之GC浅析Wirte by 021. 对象头 堆特性 Eden 存活对象少 S1 S2 Old 存活对象多 对象存活晋升过程 按年龄 12345对象每经历一次Minor GC,年龄加1,达到“晋升年龄阈值”后,被放到老年代,这个过程也称为“晋升”。显然,“晋升年龄阈值”的大小直接影响着对象在新生代中的停留时间,在Serial和ParNew GC两种回收器中,“晋升年龄阈值”通过参数MaxTenuringThreshold设定,默认值为15。Paralle Scavenge 15CMS 6G1 15 动态年龄分配函数 12345678910111213141516uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { //survivor_capacity是survivor空间的大小 size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); size_t total = 0; uint age = 1; while (age < table_size) { total += sizes[age];//sizes数组是每个年龄段对象大小 if (total > desired_survivor_size) break; age++; } uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; ...}Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。 什么是垃圾?(对象引用详解) 什么是强引用Strong Reference? 1Object o = new Object(); 特点 1只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。 什么情况下会被回收? 12Object o = new Object();o = null; 什么是软引用SoftReference? 特点 1234567891011121314151617SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student()); Student student = studentSoftReference.get(); System.out.println(student); SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]); System.out.println(softReference.get()); System.gc(); System.out.println(softReference.get()); byte[] bytes = new byte[1024 * 1024 * 10]; System.out.println(softReference.get());//// 将内存调小 -Xmx20M ,输出结果:[B@11d7fff[B@11d7fffnull 什么时候会被回收? 1当JVM内存不足,执行GC后,内存依然不足,会回收软引用. 什么是弱引用WeakReference? 特点 123456789WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]); System.out.println(weakReference.get()); System.gc(); System.out.println(weakReference.get()); //输出结果: [B@11d7fffnull 什么情况下会被回收? 1只要触发GC就会被回收. 什么是虚引用PhantomReference? 特点 1NIO中,就运用了虚引用管理堆外内存,用于堆外内存的释放. 看官方源码注释 : 本大神硬核翻译!希望你能懂 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class PhantomReference<T> extends Reference<T> /** * Phantom reference objects, which are enqueued after the collector * determines that their referents may otherwise be reclaimed. Phantom * references are most often used for scheduling pre-mortem cleanup actions in * a more flexible way than is possible with the Java finalization mechanism. * 虚引用对象,在垃圾收集器决定这些引用可能被回收时入队,虚引用经常用于在“对象死亡后,对对象进行死亡讣告” 是一种可能比Java直接回收的更灵活一种方式. * <p> If the garbage collector determines at a certain point in time that the * referent of a phantom reference is <a * href="package-summary.html#reachability">phantom reachable</a>, then at that * time or at some later time it will enqueue the reference. * //此段没有太大的意义 * <p> In order to ensure that a reclaimable object remains so, the referent of * a phantom reference may not be retrieved: The <code>get</code> method of a * phantom reference always returns <code>null</code>. * // 为了保证 已确定回收对象 的规则, 虚引用不再返回该对象的引用. * <p> Unlike soft and weak references, phantom references are not * automatically cleared by the garbage collector as they are enqueued. An * object that is reachable via phantom references will remain so until all * such references are cleared or themselves become unreachable. * * @author Mark Reinhold * @since 1.2 */ //不像其他引用一样,在虚引用入队后不会自动被垃圾回收器清理, 一个可达对象被虚引用所引用后会一直保持在列队,直到这些引用被清理或者这些引用变得不可达. =================================/** * Reference queues, to which registered reference objects are appended by the * garbage collector after the appropriate reachability changes are detected. * * @author Mark Reinhold * @since 1.2 */ 当垃圾回收器在适合的时机检测到可达性发生改变时决定 将这些 引用 加入列队.public class ReferenceQueue<T> { 什么情况下会被回收? 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118class A{ @Override protected void finalize() throws Throwable { System.out.println("A对象 被回收了"); } protected void freeMem() throws Throwable { System.out.println("执行堆外内存释放"); }}class B{ @Override protected void finalize() throws Throwable { System.out.println("B对象 被回收了"); }}public class PerformanceTest { final int a = 0; final int b =10; final static int c = 0; volatile int d = 0; volatile int e = 0; public static void main(String[] args) throws InterruptedException { PerformanceTest p = new PerformanceTest(); //p.test(0,0); //打印当前对象内存分布 //System.out.println(VM.current().details()); //System.out.println(ClassLayout.parseClass(PerformanceTest.class).toPrintable()); //虚引用测试 p.phantomReferenceTest(); } private void phantomReferenceTest() throws InterruptedException { ReferenceQueue queue = new ReferenceQueue(); List<byte[]> bytes = new ArrayList<>(); //强引用 A a = new A(); B b = new B(); //虚引用 PhantomReference<A> reference = new PhantomReference<A>(new A(),queue); //弱引用 WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]); System.out.println("强引用GC回收前强引用====================="+a); System.out.println("强引用GC回收前弱引用:====================="+weakReference.get()); //此线程触发GC 设置-Xmx20M new Thread(() -> { for (int i = 0; i < 30;i++ ) { bytes.add(new byte[1024 * 1024]); } }).start(); Thread.sleep(1000); System.out.println("强引用GC回收后弱引用:====================="+weakReference.get()); new Thread(() -> { while (true) { Reference poll = queue.poll(); if (poll != null) { System.out.println("虚引用被回收了:" + poll); } if (poll == null) { System.out.println("内存中没有引虚拟引用了,被清理了"); try { new A().freeMem(); } catch (Throwable throwable) { throwable.printStackTrace(); } return; } } }).start(); System.out.println("强引用GC回收后强引用A:====================="+a); }}//输出结果: 执行结果有乱序,不影响,用序号标记解释1.强引用GC回收前强引用=====================com.mybrainsbox.performance.A@621be5d12.强引用GC回收前弱引用:=====================[[email protected]对象 被回收了4.强引用GC回收后弱引用:=====================null5.强引用GC回收后强引用A:=====================com.mybrainsbox.performance.A@621be5d16.虚引用被回收了:java.lang.ref.PhantomReference@6d39548b7.内存中没有引虚拟引用了,被清理了8.B对象 被回收了9.执行堆外内存释放10.A对象 被回收了====================================================3.A对象 被回收了 这一步输出对应 : PhantomReference<A> reference = new PhantomReference<A>(new A(),queue);一旦触发GC, 被PhantomReference引用的对象都会被设置为弱引用,并加入虚引用列队.等待回收.所以这一步在GC触发时输出.6.虚引用被回收了:java.lang.ref.PhantomReference@6d39548b这一步是 虚引用列队在GC后拿到被虚引用引用的new A()最后的回执,所有的可达性检测决定可能要回收的对象都会被加入此列队,直到poll()出全部.7.内存中没有引虚拟引用了,被清理了;这一步是poll出了所有的虚引用的对象列队;8.B对象 被回收了10.A对象 被回收了8和10是方法出栈被回收的. 怎么样找到垃圾? 引用计数器 1在对象头里,有数据位表示该对象有没有引用,当引用计数为0对象将被回收. 缺陷 :循环引用 容易引起孤岛效应—- 即 : A -> B -> C , 3个对象互相引用,计数不为0,但实际已经没有任何引用指向这三个对象. Root Searching 根可达算法 根路径 虚拟机栈(栈帧中的本地变量表)中的引用的对象。 方法区中的类静态属性引用的对象。 方法区中的常量引用的对象。 本地方法栈中JNI(即一般说的Native方法)的引用的对象。 常见GC垃圾回收算法 标记 -> 清除 特点 123451,标记过程:从GC root出发遍历所有对象,在可达对象头中的markeword进行标记. 2,清理过程:遍历堆中对象,判断对象头markword是否存活标记,进行回收. 缺陷 效率问题:需要2遍扫描,标记和清除都需要遍历,效率不高; 空间问题:标记清除后会产生大量不连续的内存水平,空间碎片太多会导致大内存对象无法生成而频繁进行 GC。 适用堆区 Old 触发GC类型 Major GC/老年代GC 标记 -> 复制 特点 123451,标记 : 将年轻代的空间一分为三,[Eden],[ s1 ] ,[ s2 ],比例是:8:1:1 ,2,复制: 当使用 s1内存块 使用率达到阈值,s2空白,将存活对象复制到s2内存空间.3,清理 : 清理s1空间块. 通过这个方式避免了碎片化空间问题. 缺陷 效率问题:标记和清除都需要遍历,效率不高; 空间问题:内存空间浪费,最大10%, 适用堆区 Eden 触发GC类型 MinorGC/年轻代GC 标记 -> 压缩(整理) 特点 12341,先标记2,压缩整理: 把可达对象往内存块偏移量折中的任意一端移动,达到空间整洁规整。 缺陷 效率问题:需要扫描2次,移动对象引用需要重新引用地址,,效率不如复制算法高效; 适用堆区 Old 触发GC类型 Major GC/老年代GC GC垃圾回收器(算法的实现)CMSSTW安全点 编译器提前编译好安全区域标志位,通过对象oopmap映射,GC通过opmap表来判断线程是否到达对象安全点. 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455HotSpot 用的就是映射表,这个表叫 OopMap。在 HotSpot 中,对象的类型信息里会记录自己的 OopMap,记录了在该类型的对象内什么偏移量上是什么类型的数据,而在解释器中执行的方法可以通过解释器里的功能自动生成出 OopMap 出来给 GC 用。被 JIT 编译过的方法,也会在特定的位置生成 OopMap,记录了执行到该方法的某条指令时栈上和寄存器里哪些位置是引用。这些特定的位置主要在:循环的末尾(非 counted 循环)方法临返回前 / 调用方法的call指令后可能抛异常的位置这些位置就叫作安全点(safepoint)。那为什么要选择这些位置插入呢?因为如果对每条指令都记录一个 OopMap 的话空间开销就过大了,因此就选择这些个关键位置来记录即可。所以在 HotSpot 中 GC 不是在任何位置都能进入的,只能在安全点进入。至此我们知晓了可以在类加载时计算得到对象类型中的 OopMap,解释器生成的 OopMap 和 JIT 生成的 OopMap ,所以 GC 的时候已经有充足的条件来准确判断对象类型。因此称为准确式 GC。其实还有个 JNI 调用,它们既不在解释器执行,也不会经过 JIT 编译生成,所以会缺少 OopMap。在 HotSpot 是通过句柄包装来解决准确性问题的,像 JNI 的入参和返回值引用都通过句柄包装起来,也就是通过句柄再访问真正的对象。这样在 GC 的时候就不用扫描 JNI 的栈帧,直接扫描句柄表就知道 JNI 引用了 GC 堆中哪些对象了。安全点我们已经提到了安全点,安全点当然不是只给记录 OopMap 用的,因为 GC 需要一个一致性快照,所以应用线程需要暂停,而暂停点的选择就是安全点。我们来捋一遍思路。首先给个 GC 名词,在垃圾收集场景下将应用程序称为 mutator 。一个能被 mutator 访问的对象就是活着的,也就是说 mutator 的上下文包含了可以访问存活对象的数据。这个上下文其实指的就是栈、寄存器等上面的数据,对于 GC 而言它只关心栈上、寄存器等哪个位置是引用,因为它只需要关注引用。但是上下文在 mutator 运行过程中是一直在变化的,所以 GC 需要获取一个一致性上下文快照来枚举所有的根对象。而快照的获取需要停止 mutator 所有线程,不然就得不到一致的数据,导致一些活着对象丢失,这里说的一致性其实就像事务的一致性。而 mutator 所有线程中这些有机会成为暂停位置的点就叫 safepoint 即安全点。openjdk 官网对安全点的定义是:A point during program execution at which all GC roots are known and all heap object contents are consistent. From a global point of view, all threads must block at a safepoint before the GC can run.在JIT执行方式下,JIT编译的时候直接把Safepoint的检查代码加入了生成的本地代码。当JVM需要让Java线程进入Safepoint时,只需要设置一个标志位,让Java线程运行到Safepoint时主动检查这个标志位,如果标志被设置,那么线程停顿,如果没有被设置,那么继续执行。如HotSpot在x86中为轮询Safepoint会生成一条类似于test汇编指令。 什么时候触发GC l ","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"JVM之Java对象内存分布","slug":"JVM之Java对象内存分布","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-11T09:12:21.631Z","comments":true,"path":"2021/11/11/JVM之Java对象内存分布/","link":"","permalink":"http://www.010101.cc/2021/11/11/JVM%E4%B9%8BJava%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83/","excerpt":"","text":"Java对象内存分布Wirte by 021. 示例 对象全局图 头部markword","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"JVM之Java运行时数据区浅析---TODO","slug":"JVM之运行时数据区TODO","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-11T10:19:31.986Z","comments":true,"path":"2021/11/11/JVM之运行时数据区TODO/","link":"","permalink":"http://www.010101.cc/2021/11/11/JVM%E4%B9%8B%E8%BF%90%E8%A1%8C%E6%97%B6%E6%95%B0%E6%8D%AE%E5%8C%BATODO/","excerpt":"","text":"JVM之运行时数据区 ————– TODOWirte by 021. 示例代码1234567891011121314151617181920212223242526272829public class PerformanceTest { final int a = 0; final int b =10; final static int c = 0; volatile int d = 0; volatile int e = 0; public static void main(String[] args){ int f = 0; PerformanceTest p = new PerformanceTest(); p.test(a,b); System.out.println(VM.current().details()); System.out.println(ClassLayout.parseClass(PerformanceTest.class).toPrintable()); } private int test(int a , int b){ System.out.println(this.getClass().getClassLoader()); System.out.println(Long.class.getClass().getClassLoader()); System.out.println(Integer.class.getClass().getClassLoader()); return a + b; } } Progarm Counter 程序计数器 执行代码行标记 JVM stack 方法栈 Local Variable Table 局部变量表 1234args[]int f = 0; Operand Stack 操作数栈 1 Dynamic Linking 静态连接 1当前线程在运行时与常量池符号的连接. 指向常量池的变量. 示例代码中:final static int c = 0,p.test(0,0);; Retuen Address 返回地址 1函数执行结果的值地址存入栈顶. p.test(a,b) = return a + b; 本地方法栈 Native 本地方法相关方法栈. 方法区 l Direct Memory JVM可以直接访问的内核空间内存,0拷贝。 堆 参数设置123默认: -Xmixed 混合模式-Xint 解释模式,启动块,执行稍微慢-XComp 编译模式,启动时间慢,需要编译 ","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"数据结构Btree之Mysql自顶向下","slug":"数据结构Btree之Mysql自顶向下","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-11T15:40:05.573Z","comments":true,"path":"2021/11/11/数据结构Btree之Mysql自顶向下/","link":"","permalink":"http://www.010101.cc/2021/11/11/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84Btree%E4%B9%8BMysql%E8%87%AA%E9%A1%B6%E5%90%91%E4%B8%8B/","excerpt":"","text":"M Way Serach, Balance Tree , Balance+ tree, Mysql数据存储与底层结构浅析Wirte by 021.","categories":[{"name":"数据结构与算法","slug":"数据结构与算法","permalink":"http://www.010101.cc/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"树结构","slug":"树结构","permalink":"http://www.010101.cc/tags/%E6%A0%91%E7%BB%93%E6%9E%84/"}]},{"title":"JVM之GC垃圾回收器浅析","slug":"JVM之GC垃圾回收器分析","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-25T03:20:06.674Z","comments":true,"path":"2021/11/11/JVM之GC垃圾回收器分析/","link":"","permalink":"http://www.010101.cc/2021/11/11/JVM%E4%B9%8BGC%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8%E5%88%86%E6%9E%90/","excerpt":"","text":"JVM之GC垃圾回收器浅析Wirte by 021. Leave a message if i messed up ! : ) 对象头 GC垃圾回收器的目标 减少GC停顿时间 避免程序崩溃 优化JVM内存空间 GC垃圾回收的主要步骤:A garbage collector (GC) is a memory management tool. The G1 GC achieves automatic memory management through the following operations: Allocating objects to a young generation and promoting aged objects into an old generation. 在年轻代为新的对象分配内存, 将晋升的对象移到老年代 Finding live objects in the old generation through a concurrent (parallel) marking phase. The Java HotSpot VM triggers the marking phase when the total Java heap occupancy exceeds the default threshold. 通过并发(并行)标记阶段在老年代寻找存活对象,当Java的堆总内存占用率超过默认的阈值时hotspop虚拟机将触发标记执行阶段 Recovering free memory by compacting live objects through parallel copying. 通过并行的 ”复制和压缩存活对象“ 的方式,来恢复和释放可用的内存空间 CMS 基于 标记-清除 算法 适用于老年代 回收过程 初始标记 STW 并发标记 重新标记 STW 并发清除 缺陷 2次STW 内存碎片化 G1 (garbage first 垃圾第一) 逻辑分代对象 eden 新创建的对象(new object) survivor 存活对象 old 老年对象 humongous 超大对象 官方介绍 The Garbage First Garbage Collector (G1 GC) is the low-pause, server-style generational garbage collector for Java HotSpot VM. The G1 GC uses concurrent and parallel phases to achieve its target pause time and to maintain good throughput. When G1 GC determines that a garbage collection is necessary, it collects the regions with the least live data first (garbage first). 特性 it collects the regions with the least live data first 优先收集存活对最少的区域 The G1 GC is a regionalized and generational garbage collector, which means that the Java object heap (heap) is divided into a number of equally sized regions. Upon startup, the Java Virtual Machine (JVM) sets the region size. The region sizes can vary from 1 MB to 32 MB depending on the heap size. The goal is to have no more than 2048 regions. The eden, survivor, and old generations are logical sets of these regions and are not contiguous. G1是区域化和分代的垃圾回收器,堆内存被划分化为大小相等的区域。启动时,虚拟机会设置区域大小。这些区域的大小会根据总堆尺寸被划分为1-32m不同的尺寸。目标是不要超过2048个区域。年轻代,存活代,和老年代,在区域里是逻辑集合,并且并不是连续相邻的. The G1 GC has a pause time-target that it tries to meet (soft real time). During young collections, the G1 GC adjusts its young generation (eden and survivor sizes) to meet the soft real-time target. During mixed collections, the G1 GC adjusts the number of old regions that are collected based on a target number of mixed garbage collections, the percentage of live objects in each region of the heap, and the overall acceptable heap waste percentage. G1 GC 有一个它试图满足的暂停时间的目标, **在年轻代收集期间,G1回收器会调整 年轻代的堆尺寸 来达到 ”暂停时间的预期 目标“. ** 在混合混合收集期间,G1回收器会调整 收集过的老年代区域的数量, 基于混合收集器模式预期的目标数量,以及在整个堆每个区域中存活对象的比例,和综合可接受的堆浪费比。 Phases of the Marking Cycle(标记周期的阶段性)The marking cycle has the following phases: Initial mark phase: The G1 GC marks the roots during this phase. This phase is piggybacked on a normal (STW) young garbage collection. 初始化标记阶段(STW): G1 在这个阶段是 从ROOTS开始引用. 这阶段会装载一个普通(STW)的young垃圾回收上。 Root region scanning phase: The G1 GC scans survivor regions of the initial mark for references to the old generation and marks the referenced objects. This phase runs concurrently with the application (not STW) and must complete before the next STW young garbage collection can start. 根区块扫描阶段: G1会扫描 在 (初始标记阶段中 成为old老年代的引用和被老年代引用的对象) 中的survivor存活区块 . 这个阶段是并发运行的应用线程不需要停止,只有当前扫描完成后下一个young垃圾收集才能(STW)开始. Concurrent marking phase: The G1 GC finds reachable (live) objects across the entire heap. This phase happens concurrently with the application, and can be interrupted by STW young garbage collections. 并发标记阶段: G1 在整个堆中寻找可达存活的对象.这个阶段与应用线程并发执行,并且可以被young年轻代垃圾收集器(STW)打断. Remark phase: This phase is STW collection and helps the completion of the marking cycle. G1 GC drains SATB buffers, traces unvisited live objects, and performs reference processing. 重新标记阶段(STW): 这个阶段是为了帮助其完成标记周期的。G1将会清空SATB(Snapshot-At-The-Beginning)的缓冲区,追踪未访问的存活对象,和执行重新引用进程。 Cleanup phase: In this final phase, the G1 GC performs the STW operations of accounting and RSet scrubbing. During accounting, the G1 GC identifies completely free regions and mixed garbage collection candidates. The cleanup phase is partly concurrent when it resets and returns the empty regions to the free list. **清理阶段(STW): 在这最后一步,G1 会执行 计算和清理RSET的STW操作. 在计算期间, G1 会分解出 完整的空闲区块 和 混合垃圾收集的和混合收集器待收集的对象。 清理阶段一定程度上是并发的当 重置和返回空区块到空闲清单 的时候。 ** Garbage Collection Phases(垃圾收集阶段)Apart from evacuation pauses (described below) that compose the stop-the-world (STW) young and mixed garbage collections, the G1 GC also has parallel, concurrent, and multiphase marking cycles. G1 GC uses the Snapshot-At-The-Beginning (SATB) algorithm, which takes a snapshot of the set of live objects in the heap at the start of a marking cycle. The set of live objects is composed of the live objects in the snapshot, and the objects allocated since the start of the marking cycle. The G1 GC marking algorithm uses a pre-write barrier to record and mark objects that are part of the logical snapshot. 下面的两段分别描述了组成 stop-the-world (STW) 的两种回收器, 年轻代回收器与混合回收器,G1 也具备 并发 ,并行 和 联合多相标记周期. G1 使用快照的算法,在标记周期开始之前取得堆中存活对象集合的快照. 快照中的存活对象集合组成了一个集合,并且在开始标记之前这些对象已经被分配好了. G1的标记算法用了前置的写屏障来记录和标记对象来保证对象在物理快照中的一致性。 Young Garbage Collections(年轻代的垃圾收集)The G1 GC satisfies most allocation requests from regions added to the eden set of regions. During a young garbage collection, the G1 GC collects both the eden regions and the survivor regions from the previous garbage collection. The live objects from the eden and survivor regions are copied, or evacuated, to a new set of regions. The destination region for a particular object depends upon the object’s age; an object that has aged sufficiently evacuates to an old generation region (that is, promoted); otherwise, the object evacuates to a survivor region and will be included in the CSet of the next young or mixed garbage collection. G1满足大部分从一个区域添加到年轻代集合区域的内存申请,在年轻代回收期间,G1收集器会同时收集eden区域的垃圾和survivor区域的垃圾从上一个垃圾集中。 eden区和survivor区存活的对象已经被拷贝了,或者已经被撤离出当前区域到了一个的区域集。复制的目标区域是特定的,取决于对象的年龄来决定复制到哪个区块; 一个对象有足够的年龄撤离出eden与survivor到老年区域(这一步是晋升); 另外,如果这个对象撤离到survivor 区 对象的引用会被包含到下一个young 或者 mixed 垃圾集的 cset 中.(CSET标记存活) Mixed Garbage Collections (混合垃圾收集)Upon successful completion of a concurrent marking cycle, the G1 GC switches from performing young garbage collections to performing mixed garbage collections. In a mixed garbage collection, the G1 GC optionally adds some old regions to the set of eden and survivor regions that will be collected. The exact number of old regions added is controlled by a number of flags that will be discussed later (see “Taming Mixed GCs“). After the G1 GC collects a sufficient number of old regions (over multiple mixed garbage collections), G1 reverts to performing young garbage collections until the next marking cycle completes. 在成功完成并发标记周期的基础上,G1 从 执行年轻代垃圾收集切换到执行混合垃圾收集。在混合的垃圾收集模式中,G1选择性的添加一些老年代区块到已经被收集了的young和survivor的区块中. 一个标志数字控多少准确个老年代会被添加到已经被清空的新区块中;在G1 收集了足够多的old区块(在多个混合的垃圾集合中),G1 会切换执行young代的垃圾收集直到下一个标记周期完成. 核心算法实现 Rset (Remember Set) 引用记忆集 The G1 GC uses independent Remembered Sets (RSets) to track references into regions. Independent RSets enable parallel and independent collection of regions because only a region’s RSet must be scanned for references into that region, instead of the whole heap. The G1 GC uses a post-write barrier to record changes to the heap and update the RSets. G1垃圾回收器使用独立的记忆集来追踪区域里的引用,独立的RSET允许并行,并且 每个区域的RSET是独立的,因为只有当前区域的RSET为了扫描引用才必须进入当前区域,从而避免全堆扫描。垃圾回收器利用后置写屏障来标记记录堆更改和RSET的更新. Cset(Collection Set)存活收集集 The G1 GC reduces heap fragmentation by incremental parallel copying of live objects from one or more sets of regions (called Collection Set (CSet)) into different new region(s) to achieve compaction. 垃圾收集器为了减少堆内存碎片,将一组或多组区域的存活对象并行复制到不同的新区域来实现内存整理压缩. Important Defaults (官方文档)The G1 GC is an adaptive garbage collector with defaults that enable it to work efficiently without modification. Here is a list of important options and their default values. This list applies to the latest Java HotSpot VM, build 24. You can adapt and tune the G1 GC to your application performance needs by entering the following options with changed settings on the JVM command line. ```-XX:G1HeapRegionSize=n 12345 Sets the size of a G1 region. The value will be a power of two and can range from 1MB to 32MB. The goal is to have around 2048 regions based on the minimum Java heap size.- ``` -XX:MaxGCPauseMillis=200 Sets a target value for desired maximum pause time. The default value is 200 milliseconds. The specified value does not adapt to your heap size. ```-XX:G1NewSizePercent=5 12345 Sets the percentage of the heap to use as the minimum for the young generation size. The default value is 5 percent of your Java heap. This is an experimental flag. See "[How to unlock experimental VM flags](https://www.oracle.com/technical-resources/articles/java/g1gc.html#Unlock)" for an example. This setting replaces the `-XX:DefaultMinNewGenPercent` setting. This setting is not available in Java HotSpot VM, build 23.- ``` -XX:G1MaxNewSizePercent=60 Sets the percentage of the heap size to use as the maximum for young generation size. The default value is 60 percent of your Java heap. This is an experimental flag. See “How to unlock experimental VM flags“ for an example. This setting replaces the -XX:DefaultMaxNewGenPercent setting. This setting is not available in Java HotSpot VM, build 23. ```-XX:ParallelGCThreads=n 1234567 Sets the value of the STW worker threads. Sets the value of n to the number of logical processors. The value of `n` is the same as the number of logical processors up to a value of 8. If there are more than eight logical processors, sets the value of `n` to approximately 5/8 of the logical processors. This works in most cases except for larger SPARC systems where the value of `n` can be approximately 5/16 of the logical processors.- ``` -XX:ConcGCThreads=n Sets the number of parallel marking threads. Sets n to approximately 1/4 of the number of parallel garbage collection threads (ParallelGCThreads). ```-XX:InitiatingHeapOccupancyPercent=45 12345 Sets the Java heap occupancy threshold that triggers a marking cycle. The default occupancy is 45 percent of the entire Java heap.- ``` -XX:G1MixedGCLiveThresholdPercent=65 Sets the occupancy threshold for an old region to be included in a mixed garbage collection cycle. The default occupancy is 65 percent. This is an experimental flag. See “How to unlock experimental VM flags“ for an example. This setting replaces the -XX:G1OldCSetRegionLiveThresholdPercent setting. This setting is not available in Java HotSpot VM, build 23. ```-XX:G1HeapWastePercent=10 12345 Sets the percentage of heap that you are willing to waste. The Java HotSpot VM does not initiate the mixed garbage collection cycle when the reclaimable percentage is less than the heap waste percentage. The default is 10 percent. This setting is not available in Java HotSpot VM, build 23.- ``` -XX:G1MixedGCCountTarget=8 Sets the target number of mixed garbage collections after a marking cycle to collect old regions with at most G1MixedGCLIveThresholdPercent live data. The default is 8 mixed garbage collections. The goal for mixed collections is to be within this target number. This setting is not available in Java HotSpot VM, build 23. ```-XX:G1OldCSetRegionThresholdPercent=10 12345 Sets an upper limit on the number of old regions to be collected during a mixed garbage collection cycle. The default is 10 percent of the Java heap. This setting is not available in Java HotSpot VM, build 23.- ``` -XX:G1ReservePercent=10 Sets the percentage of reserve memory to keep free so as to reduce the risk of to-space overflows. The default is 10 percent. When you increase or decrease the percentage, make sure to adjust the total Java heap by the same amount. This setting is not available in Java HotSpot VM, build 23. How to Unlock Experimental VM FlagsTo change the value of experimental flags, you must unlock them first. You can do this by setting -XX:+UnlockExperimentalVMOptions explicitly on the command line before any experimental flags. For example: 1> java -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75 G1test.jar RecommendationsWhen you evaluate and fine-tune G1 GC, keep the following recommendations in mind: Young Generation Size: Avoid explicitly setting young generation size with the -Xmn option or any or other related option such as -XX:NewRatio. Fixing the size of the young generation overrides the target pause-time goal. Pause Time Goals: When you evaluate or tune any garbage collection, there is always a latency versus throughput trade-off. The G1 GC is an incremental garbage collector with uniform pauses, but also more overhead on the application threads. The throughput goal for the G1 GC is 90 percent application time and 10 percent garbage collection time. When you compare this to Java HotSpot VM’s throughput collector, the goal there is 99 percent application time and 1 percent garbage collection time. Therefore, when you evaluate the G1 GC for throughput, relax your pause-time target. Setting too aggressive a goal indicates that you are willing to bear an increase in garbage collection overhead, which has a direct impact on throughput. When you evaluate the G1 GC for latency, you set your desired (soft) real-time goal, and the G1 GC will try to meet it. As a side effect, throughput may suffer. Taming Mixed Garbage Collections : Experiment with the following options when you tune mixed garbage collections. See “ Important Defaults “ for information about these options: -XX:InitiatingHeapOccupancyPercentFor changing the marking threshold. -XX:G1MixedGCLiveThresholdPercent and -XX:G1HeapWastePercentWhen you want to change the mixed garbage collections decisions. -XX:G1MixedGCCountTarget and -XX:G1OldCSetRegionThresholdPercentWhen you want to adjust the CSet for old regions. Overflow and Exhausted Log MessagesWhen you see to-space overflow/exhausted messages in your logs, the G1 GC does not have enough memory for either survivor or promoted objects, or for both. The Java heap cannot expand since it is already at its max. Example messages: 1924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space exhausted), 0.1957310 secs] OR 1924.897: [GC pause (G1 Evacuation Pause) (mixed) (to-space overflow), 0.1957310 secs] To alleviate the problem, try the following adjustments: Increase the value of the -XX:G1ReservePercent option (and the total heap accordingly) to increase the amount of reserve memory for “to-space”. Start the marking cycle earlier by reducing the -XX:InitiatingHeapOccupancyPercent. You can also increase the value of the -XX:ConcGCThreads option to increase the number of parallel marking threads. See “Important Defaults“ for a description of these options. Humongous Objects and Humongous AllocationsFor G1 GC, any object that is more than half a region size is considered a “Humongous object”. Such an object is allocated directly in the old generation into “Humongous regions”. These Humongous regions are a contiguous set of regions. StartsHumongous marks the start of the contiguous set and ContinuesHumongous marks the continuation of the set. Before allocating any Humongous region, the marking threshold is checked, initiating a concurrent cycle, if necessary. Dead Humongous objects are freed at the end of the marking cycle during the cleanup phase also during a full garbage collection cycle. In-order to reduce copying overhead, the Humongous objects are not included in any evacuation pause. A full garbage collection cycle compacts Humongous objects in place. Since each individual set of StartsHumongous and ContinuesHumongous regions contains just one humongous object, the space between the end of the humongous object and the end of the last region spanned by the object is unused. For objects that are just slightly larger than a multiple of the heap region size, this unused space can cause the heap to become fragmented. If you see back-to-back concurrent cycles initiated due to Humongous allocations and if such allocations are fragmenting your old generation, please increase your -XX:G1HeapRegionSize such that previous Humongous objects are no longer Humongous and will follow the regular allocation path. ConclusionG1 GC is a regionalized, parallel-concurrent, incremental garbage collector that provides more predictable pauses compared to other HotSpot GCs. The incremental nature lets G1 GC work with larger heaps and still provide reasonable worst-case response times. The adaptive nature of G1 GC just needs a maximum soft-real time pause-time goal along-with the desired maximum and minimum size for the Java heap on the JVM command line. See Also The Garbage-First Garbage Collector Java HotSpot Garbage Collection About the AuthorMonica Beckwith, Principal Member of Technical Staff at Oracle, is the performance lead for the Java HotSpot VM’s Garbage First Garbage Collector. She has worked in the performance and architecture industry for over 10 years. Prior to Oracle and Sun Microsystems, Monica lead the performance effort at Spansion Inc. Monica has worked with many industry standard Java based benchmarks with a constant goal of finding opportunities for improvement in the Java HotSpot VM. 王者ZGC 回收过程 初始标记 STW 并发标记 并发转移STW 核心算法 标记对象指针 暂时可以理解为在64位内存地址中取三个标志位映射对象的同一块物理内存地址,标记对象的标记过程,重新定位过程,等GC回收标志 合理利用64位处理器架构 ,Numa架构 现在多CPU插槽的服务器都是Numa架构,比如两颗CPU插槽(24核),64G内存的服务器,那其中一颗CPU上的12个核,访问从属于它的32G本地内存,要比访问另外32G远端内存要快得多。 读屏障 因为在标记和移动过程中,GC线程和应用线程是并发执行的,所以存在这种情况:对象A内部的引用所指的对象B在标记或者移动状态,为了保证应用线程拿到的B对象是对的,那么在读取B的指针时会经过一个 “load barriers” 读屏障,这个屏障可以保证在执行GC时,数据读取的正确性。 Compacting 内存页压缩","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"数据结构RingBuffer之机械之美---TODO","slug":"数据结构RingBuffer之机械之美---TODO","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-18T04:45:53.600Z","comments":true,"path":"2021/11/11/数据结构RingBuffer之机械之美---TODO/","link":"","permalink":"http://www.010101.cc/2021/11/11/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84RingBuffer%E4%B9%8B%E6%9C%BA%E6%A2%B0%E4%B9%8B%E7%BE%8E---TODO/","excerpt":"","text":"数据结构RingBuffer之机械之美Wirte by 021.","categories":[{"name":"数据结构与算法","slug":"数据结构与算法","permalink":"http://www.010101.cc/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"线性结构","slug":"线性结构","permalink":"http://www.010101.cc/tags/%E7%BA%BF%E6%80%A7%E7%BB%93%E6%9E%84/"}]},{"title":"经典设计之AQS与ConcurrentHashMap---TODO","slug":"经典设计之AQS与ConcurrentHashMap---TODO","date":"2021-11-11T07:04:04.000Z","updated":"2021-11-18T04:47:09.006Z","comments":true,"path":"2021/11/11/经典设计之AQS与ConcurrentHashMap---TODO/","link":"","permalink":"http://www.010101.cc/2021/11/11/%E7%BB%8F%E5%85%B8%E8%AE%BE%E8%AE%A1%E4%B9%8BAQS%E4%B8%8EConcurrentHashMap---TODO/","excerpt":"","text":"经典设计之AQS与ConcurrentHashMap—TODOWirte by 021.","categories":[{"name":"数据结构与算法","slug":"数据结构与算法","permalink":"http://www.010101.cc/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"设计模式","slug":"设计模式","permalink":"http://www.010101.cc/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}]},{"title":"内核之线程与进程的关系","slug":"E_Process","date":"2021-11-10T07:15:06.000Z","updated":"2021-11-10T12:33:50.091Z","comments":true,"path":"2021/11/10/E_Process/","link":"","permalink":"http://www.010101.cc/2021/11/10/E_Process/","excerpt":"","text":"线程与进程关系 进程是资源集合,提供cr3的值,cr3是页目录表的基址,cr3确定了,线程才能访问. 一个进程有多个线程,但至少有一个线程。 11,进程提供了空间上概念,进程提供了线程哪些空间地址可以访问的逻辑。 创建进程: 通过线程如何找到进程: 12345678910111213141516171819202122232425262728* thread -> cr3页面 -> _Kprocess 的指针假如线程执行:mov eax,dword ptr ds:[0x12345678]cpu的解析路径: 1,线程拿到当前进程的 cr3 值 Kprocess_directoryTableBase(+0x018) 2,通过cr3的寄存器页表目录定位物理页3,EThread结构体:{ 0x000 KThread 结构体 { 0x034 _KAPC_STAT { 0x10 ptr32 EPROCESS; 0x044 , cmp [esi, prt* process] #SWapContext时会比较 44 和 220 的值是否一样,不一样会造成进程空间切换,cr3切换,详情见进程挂靠 #44位置上提供是cr3的值,资源提供方的值 } } 0x220 ptr32 EPROCESS #当前线程所属进程,当前线程是谁创建的}","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"进程&线程","slug":"进程-线程","permalink":"http://www.010101.cc/tags/%E8%BF%9B%E7%A8%8B-%E7%BA%BF%E7%A8%8B/"}]},{"title":"高并发之CAS自顶向下","slug":"高并发之CAS自顶向下","date":"2021-11-10T07:15:06.000Z","updated":"2021-11-16T12:32:12.147Z","comments":true,"path":"2021/11/10/高并发之CAS自顶向下/","link":"","permalink":"http://www.010101.cc/2021/11/10/%E9%AB%98%E5%B9%B6%E5%8F%91%E4%B9%8BCAS%E8%87%AA%E9%A1%B6%E5%90%91%E4%B8%8B/","excerpt":"","text":"高并发之CAS自顶向下Wirte by 021. 机器级代码 计算机系统使用了多种不同形式的抽象,可以通过一个简单的抽象模型来隐藏实现细节。对于机器级别的程序来说,有两点非常重要。 首先第一点,定义机器级别程序的格式和行为被称为 指令集体系结构或指令集架构(instruction set architecture), ISA。ISA 定义了进程状态、指令的格式和每一个指令对状态的影响。大部分的指令集架构包括 ISA 用来描述进程的行为就好像是顺序执行的,一条指令执行结束后,另外一条指令再开始。处理器硬件的描述要更复杂,它可以同时并行执行许多指令,但是它采用了安全措施来确保整体行为与 ISA 规定的顺序一致。 第二点,机器级别对内存地址的描述就是 虚拟地址(virtual address),它提供了一个内存模型来表示一个巨大的字节数组。 编译器在整个编译的过程中起到了至关重要的作用,把 C 语言转换为处理器执行的基本指令。汇编代码非常接近于机器代码,只不过与二进制机器代码相比,汇编代码的可读性更强,所以理解汇编是理解机器工作的第一步。 一些进程状态对机器可见,但是 C 语言程序员却看不到这些,包括 程序计数器(Program counter),它存储下一条指令的地址,在 x86-64 架构中用 %rip 来表示。 程序执行时,PC 的初始值为程序第一条指令的地址,在顺序执行程序时, CPU 首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将 PC 的值加 1 并指向下一条要执行的指令。 比如下面一个例子。 这是一段数值进行相加的操作,程序启动,在经过编译解析后会由操作系统把硬盘中的程序复制到内存中,示例中的程序是将 123 和 456 执行相加操作,并将结果输出到显示器上。由于使用机器语言难以描述,所以这是经过翻译后的结果,实际上每个指令和数据都可能分布在不同的地址上,但为了方便说明,把组成一条指令的内存和数据放在了一个内存地址上。 整数寄存器文件(register file)包含 16 个命名的位置,用来存储 64 位的值。这些寄存器可以存储地址和整型数据。有些寄存器用于跟踪程序状态,而另一些寄存器用于保存临时数据,例如过程的参数和局部变量,以及函数要返回的值。这个 文件 是和磁盘文件无关的,它只是 CPU 内部的一块高速存储单元。有专用的寄存器,也有通用的寄存器用来存储操作数。 条件码寄存器 用来保存有关最近执行的算术或逻辑指令的状态信息。这些用于实现控件或数据流中的条件更改,例如实现 if 和 while 语句所需的条件更改。我们都学过高级语言,高级语言中的条件控制流程主要分为三种:顺序执行、条件分支、循环判断三种,顺序执行是按照地址的内容顺序的执行指令。条件分支是根据条件执行任意地址的指令。循环是重复执行同一地址的指令。 顺序执行的情况比较简单,每执行一条指令程序计数器的值就是 + 1。 条件和循环分支会使程序计数器的值指向任意的地址,这样一来,程序便可以返回到上一个地址来重复执行同一个指令,或者跳转到任意指令。 下面以条件分支为例来说明程序的执行过程(循环也很相似) 程序的开始过程和顺序流程是一样的,CPU 从 0100 处开始执行命令,在 0100 和 0101 都是顺序执行,PC 的值顺序+1,执行到 0102 地址的指令时,判断 0106 寄存器的数值大于 0,跳转(jump)到 0104 地址的指令,将数值输出到显示器中,然后结束程序,0103 的指令被跳过了,这就和我们程序中的 if() 判断是一样的,在不满足条件的情况下,指令会直接跳过。所以 PC 的执行过程也就没有直接+1,而是下一条指令的地址。 一组 向量寄存器用来存储一个或者多个整数或者浮点数值,向量寄存器是对一维数据上进行操作。 机器指令只会执行非常简单的操作,例如将存放在寄存器的两个数进行相加,把数据从内存转移到寄存器中或者是条件分支转移到新的指令地址。编译器必须生成此类指令的序列,以实现程序构造,例如算术表达式求值,循环或过程调用和返回 认识汇编 我相信各位应该都知道汇编语言的出现背景吧,那就是二进制表示数据,太复杂太庞大了,为了解决这个问题,出现了汇编语言,汇编语言和机器指令的区别就在于表示方法上,汇编使用操作数来表示,机器指令使用二进制来表示,我之前多次提到机器码就是汇编,你也不能说我错,但是不准确。 但是汇编适合二进制代码存在转换关系的。 汇编代码需要经过 汇编器 编译后才产生二进制代码,这个二进制代码就是目标代码,然后由链接器将其连接起来运行。 汇编语言主要分为以下三类 汇编指令:它是一种机器码的助记符,它有对应的机器码 伪指令:没有对应的机器码,由编译器执行,计算机并不执行 其他符号,比如 +、-、*、/ 等,由编译器识别,没有对应的机器码 汇编语言的核心是汇编指令,而我们对汇编的探讨也是基于汇编指令展开的。 与汇编有关的硬件和概念 CPU CPU 是计算机的大脑,它也是整个计算机的核心,它也是执行汇编语言的硬件,CPU 的内部包含有寄存器,而寄存器是用于存储指令和数据的,汇编语言的本质也就是 CPU 内部操作数所执行的一系列计算。 内存 没有内存,计算机就像是一个没有记忆的人类,只会永无休止的重复性劳动。CPU 所需的指令和数据都由内存来提供,CPU 指令经由内存提供,经过一系列计算后再输出到内存。 磁盘 磁盘也是一种存储设备,它和内存的最大区别在于永久存储,程序需要在内存装载后才能运行,而提供给内存的程序都是由磁盘存储的。 总线 一般来说,内存内部会划分多个存储单元,存储单元用来存储指令和数据,就像是房子一样,存储单元就是房子的门牌号。而 CPU 与内存之间的交互是通过地址总线来进行的,总线从逻辑上分为三种 地址线 数据线 控制线 CPU 与存储器之间的读写主要经过以下几步 读操作步骤 CPU 通过地址线发出需要读取指令的位置 CPU 通过控制线发出读指令 内存把数据放在数据线上返回给 CPU 写操作步骤 CPU 通过地址线发出需要写出指令的位置 CPU 通过控制线发出写指令 CPU 把数据通过数据线写入内存 下面我们就来具体了解一下这三类总线 地址总线 通过我们上面的探讨,我们知道 CPU 通过地址总线来指定存储位置的,地址总线上能传送多少不同的信息,CPU 就可以对多少个存储单元进行寻址。 上图中 CPU 和内存中间信息交换通过了 10 条地址总线,每一条线能够传递的数据都是 0 或 1 ,所以上图一次 CPU 和内存传递的数据是 2 的十次方。 所以,如果 CPU 有 N 条地址总线,那么可以说这个地址总线的宽度是 N 。这样 CPU 可以寻找 2 的 N 次方个内存单元。 数据总线 CPU 与内存或其他部件之间的数据传送是由数据总线来完成的。数据总线的宽度决定了 CPU 和外界的数据传输速度。8 根数据总线可以一次传送一个 8 位二进制数据(即一个字节)。16 根数据总线一次可以传输两个字节,32 根数据总线可以一次传输四个字节。。。。。。 控制总线 CPU 与其他部件之间的控制是通过 控制总线 来完成的。有多少根控制总线,就意味着 CPU 提供了对外部器件的多少种控制。所以,控制总线的宽度决定了 CPU 对外部部件的控制能力。 一次内存的读取过程 内存结构 内存 IC 是一个完整的结构,它内部也有电源、地址信号、数据信号、控制信号和用于寻址的 IC 引脚来进行数据的读写。下面是一个虚拟的 IC 引脚示意图 图中 VCC 和 GND 表示电源,A0 - A9 是地址信号的引脚,D0 - D7 表示的是控制信号、RD 和 WR 都是好控制信号,我用不同的颜色进行了区分,将电源连接到 VCC 和 GND 后,就可以对其他引脚传递 0 和 1 的信号,大多数情况下,**+5V** 表示1,****0V 表示 0。 我们都知道内存是用来存储数据,那么这个内存 IC 中能存储多少数据呢?D0 - D7 表示的是数据信号,也就是说,一次可以输入输出 8 bit = 1 byte 的数据。A0 - A9 是地址信号共十个,表示可以指定 00000 00000 - 11111 11111 共 2 的 10次方 = 1024个地址。每个地址都会存放 1 byte 的数据,因此我们可以得出内存 IC 的容量就是 1 KB。 如果我们使用的是 512 MB 的内存,这就相当于是 512000(512 * 1000) 个内存 IC。当然,一台计算机不太可能有这么多个内存 IC ,然而,通常情况下,一个内存 IC 会有更多的引脚,也就能存储更多数据。 内存读取过程 下面是一次内存的读取过程。 来详细描述一下这个过程,假设我们要向内存 IC 中写入 1byte 的数据的话,它的过程是这样的: 首先给 VCC 接通 +5V 的电源,给 GND 接通 0V 的电源,使用 A0 - A9 来指定数据的存储场所,然后再把数据的值输入给 D0 - D7 的数据信号,并把 WR(write)的值置为 1,执行完这些操作后,即可以向内存 IC 写入数据 读出数据时,只需要通过 A0 - A9 的地址信号指定数据的存储场所,然后再将 RD 的值置为 1 即可。 图中的 RD 和 WR 又被称为控制信号。其中当WR 和 RD 都为 0 时,无法进行写入和读取操作。 CAS原子性操作之汇编自旋锁本质 模拟临界区 1234567891011121314//设置全局变量flag = 0;Lab: mov eax,1; lock xadd [flag],eax; //先交换 ,flag = 1,eax =0; 再相加 - flag = flag+eax = 1; eax =0; cmp eax,0; // 对比eax 是否为0 jz endLab // cmp指令会影响 标志寄存器, jz根据标志寄存的值进行指令跳转. dec [flag] // eax不为0,证明有未能拿到临界区令牌; //线程等待endLab: ret 自旋锁源码 伪代码模拟自旋锁 1234567891011121314151617181920//取锁tryLock(){ boolean flag = lock getLock(); //原子操作, 检查锁,并设置标志位; if(!flag){ waitiLock(); } return flag;}//自旋等待waitiLock(){ boolean flag = checkLock(); //检查锁状态 if(flag){ tryLock(); } sleep(...); waitiLock();} 最常见的原子操作有Compare and Exchange,Self Increase/Decrease等等 8086汇编相关指令 LOCK:这是一个指令前缀,在所对应的指令操作期间使此指令的目标操作数指定的存储区域锁定,以得到保护。 XADD:先交换两个操作数的值,再进行算术加法操作。多处理器安全,在80486及以上CPU中支持。 CMPXCHG:比较交换指令,第一操作数先和AL/AX/EAX比较,如果相等ZF置1,第二操作数赋给第一操作数,否则ZF清0,第一操作数赋给AL/AX/EAX。多处理器安全,在80486及以上CPU中支持。 XCHG:交换两个操作数,其中至少有一个是寄存器寻址.其他寄存器和标志位不受影响. 原子操作函数 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289long __stdcall **CompareExchange**(long volatile***Destination**,long **Exchange**,long **Comperand**){ __asm **{** **mov ecx**, **Destination**; **mov edx**, **Exchange**; **mov eax**, **Comperand**; **lock cmpxchg [ecx**], **edx**; **}**}交换long __stdcall **Exchange**(long volatile* **Target**,long **Value**){ __asm **{** **mov ecx**, **Target**; **mov edx**, **Value**;**label**: **lock cmpxchg [ecx**], **edx**;//加 **jnz** short **label**; **}**}自减long __stdcall **Decrement**(long volatile* **Addend**){ __asm **{** **mov ecx**, **Addend**; **mov eax**, 0FFFFFFFFh;//-1 **lock xadd [ecx**], **eax**; //加-1 **dec eax**; **}**}自增long __stdcall **Increment**(long volatile* **Addend**){ __asm **{** **mov ecx**, **Addend**; **mov eax**, 1; **lock xadd [ecx**], **eax**; //加 **inc eax**; **}**}相加后交换long __stdcall **ExchangeAdd**(long volatile* **Addend**,long **Value**){ __asm **{** **mov ecx**, **Addend**; **mov eax**, **Value**; **lock xadd [ecx**], **eax**; **}**} 原子操作(2) - 泛型后的原子操作32位的数据类型有4种,但是上面的只支持long,怎么办?手工hack?太土了,当然是C++的大规模杀伤性武器template了.同时把几个不跨平台的地方抽出来用macro表示.目前模板还没有得到concept的支持,所以只能用boost.type_traits低级的手动判断,要求只有32位的整数类型才能实例化这些函数.\\#include\\#include\\#define **CALL_METHOD** __stdcall\\#define **VOLATILE** volatiletemplate<typename **T**>**T CALL_METHOD compare_exchange32**(**T VOLATILE*****Destination**,**T exchange32**,**T Comperand**){ **BOOST_STATIC_ASSERT**(sizeof(**T**) == 4 && **boost**::**is_integral**<**T**>::**value**); __asm **{** **mov ecx**, **Destination**; **mov edx**, **exchange32**; **mov eax**, **Comperand**; **lock cmpxchg [ecx**], **edx**; **}**}template<typename **T**>**T CALL_METHOD exchange32**(**T VOLATILE*** **Target**,**T Value**){ **BOOST_STATIC_ASSERT**(sizeof(**T**) == 4 && **boost**::**is_integral**<**T**>::**value**); __asm **{** // mov ecx, Target; // mov edx, Value; //label: // lock cmpxchg [ecx], edx;//加 // jnz short label; **mov ecx**, **Target**; **mov eax**, **Value**; **xchg [ecx**],**eax**; **}**}template<typename **T**>**T CALL_METHOD decrement32**(**T VOLATILE*** **Addend**){ **BOOST_STATIC_ASSERT**(sizeof(**T**) == 4 && **boost**::**is_integral**<**T**>::**value**); __asm **{** **mov ecx**, **Addend**; **mov eax**, 0FFFFFFFFh;//-1 **lock xadd [ecx**], **eax**; //加-1 **dec eax**; **}**}template<typename **T**>**T CALL_METHOD increment32**(**T VOLATILE*** **Addend**){ **BOOST_STATIC_ASSERT**(sizeof(**T**) == 4 && **boost**::**is_integral**<**T**>::**value**); __asm **{** **mov ecx**, **Addend**; **mov eax**, 1; **lock xadd [ecx**], **eax**; //加 **inc eax**; **}**}template<typename **T**>**T CALL_METHOD exchange_add32**(**T VOLATILE*** **Addend**,**T Value**){ **BOOST_STATIC_ASSERT**(sizeof(**T**) == 4 && **boost**::**is_integral**<**T**>::**value**); __asm **{** **mov ecx**, **Addend**; **mov eax**, **Value**; **lock xadd [ecx**], **eax**; **}**}","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"并发原子锁","slug":"并发原子锁","permalink":"http://www.010101.cc/tags/%E5%B9%B6%E5%8F%91%E5%8E%9F%E5%AD%90%E9%94%81/"}]},{"title":"线程等待与唤醒之win32内核浅析","slug":"线程等待与唤醒","date":"2021-11-10T07:15:06.000Z","updated":"2021-11-10T12:36:55.347Z","comments":true,"path":"2021/11/10/线程等待与唤醒/","link":"","permalink":"http://www.010101.cc/2021/11/10/%E7%BA%BF%E7%A8%8B%E7%AD%89%E5%BE%85%E4%B8%8E%E5%94%A4%E9%86%92/","excerpt":"","text":"线程唤醒与等待等待对象 伪代码 解释 基于win32 等待对象 与 当前线程 ,被等待对象的关系. 12345678910111213141516171819202122232425262728//被等待对象1int Thread { int a = 0; return a++;}//被等待对象2int Thread1 { int a = 0; return a++;}//当前主线程 void main(...){ //被等待对象 int a Thead.t1.start; int a1 = Thead1.t1.start; 解析 -> 等待对象 waitingforSinglObject(arr[t1,t2],xx); 只有等待: 等待对象中的被等待对象t1和t2执行完后,当前main线程才能继续执行. printf(...);} 逻辑步骤-TODO1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859WaitForSingleObject调用内核函数NtWaitForObject,NtWaitForObject 调用ObReferenceObjectByHandle,通过当前线程对象句柄找到'等待对象结构体'的首地址将结构体首地址传递给 KeWaitSingleObjectKeWaitSingleObject 步骤:1,准备等待块._wait_block; 默认初始4个等待块 ..... 这块内核比较复杂黑盒,todo被等待对象:必须具备_DISPATCHER_HEADER属性的对象,_DISPATCHER_HEADER结构体{ ... ... +0x004 SingleStat; 信号状态 > 0; +0x008 WaitListHead; 所有等待块双向链表地址. }内部处理逻辑伪代码:while(1){ if(符合条件) // 1,符合超时条件, 等待对象的信号量 > 0; { 1,修改等待对象的singleStat; 2,退出循环 } else { if(不符合条件) // 超时并且被等待对象 !>0; { if(第一次执行) { } } } // 将waitingblockList 位置清0; //释放_kwait_block等待块内存.}","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"进程&线程","slug":"进程-线程","permalink":"http://www.010101.cc/tags/%E8%BF%9B%E7%A8%8B-%E7%BA%BF%E7%A8%8B/"}]},{"title":"高并发之硬件MESI缓存一致性协议与CPU屏障浅析","slug":"高并发之硬件缓存一致性与CPU屏障浅析","date":"2021-11-10T07:15:06.000Z","updated":"2021-11-11T08:32:18.875Z","comments":true,"path":"2021/11/10/高并发之硬件缓存一致性与CPU屏障浅析/","link":"","permalink":"http://www.010101.cc/2021/11/10/%E9%AB%98%E5%B9%B6%E5%8F%91%E4%B9%8B%E7%A1%AC%E4%BB%B6%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E4%B8%8ECPU%E5%B1%8F%E9%9A%9C%E6%B5%85%E6%9E%90/","excerpt":"","text":"高并发之硬件MESI缓存一致性协议与CPU屏障 Wirte by 021. MESI缓存一致性协议 cache line是cache与内存数据交换的最小单位,根据操作系统一般是32byte或64byte。在MESI协议中,状态可以是M、E、S、I,地址则是cache line中映射的内存地址,数据则是从内存中读取的数据。 状态介绍 MESI协议将cache line的状态分成modify、exclusive、shared、invalid,分别是修改、独占、共享和失效。 modify:当前CPU cache拥有最新数据(最新的cache line),其他CPU拥有失效数据(cache line的状态是invalid),虽然当前CPU中的数据和主存是不一致的,但是以当前CPU的数据为准; exclusive:只有当前CPU中有数据,其他CPU中没有改数据,当前CPU的数据和主存中的数据是一致的; shared:当前CPU和其他CPU中都有共同数据,并且和主存中的数据一致; invalid:当前CPU中的数据失效,数据应该从主存中获取,其他CPU中可能有数据也可能无数据,当前CPU中的数据和主存被认为是不一致的; 对于invalid而言,在MESI协议中采取的是写失效(write invalidate)。 cache操作 MESI协议中,每个cache的控制器不仅知道自己的操作(local read和local write),每个核心的缓存控制器通过监听也知道其他CPU中cache的操作(remote read和remote write),今儿再确定自己cache中共享数据的状态是否需要调整。 local read(LR):读本地cache中的数据; local write(LW):将数据写到本地cache; remote read(RR):其他核心发生read; remote write(RW):其他核心发生write; CPU内存保证 通常情况下,在代码被正常编译成机器码后,在汇编代码级别不加lock 原子指令情况下,代码也会产生乱序,编译器会优化代码,调整执行顺序,如果需要执行强代码一致性需要加上CPU内存屏蔽代码; final int a =0 这行代码在编译器编译成机器码时会加内存屏障,防止重排序. 12345678910#TODOsfence :ifence :mfence :","categories":[{"name":"计算机硬件","slug":"计算机硬件","permalink":"http://www.010101.cc/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6/"}],"tags":[{"name":"并发原子锁","slug":"并发原子锁","permalink":"http://www.010101.cc/tags/%E5%B9%B6%E5%8F%91%E5%8E%9F%E5%AD%90%E9%94%81/"}]},{"title":"win32 之 PE文件结构解读","slug":"win32之PE文件","date":"2021-11-10T06:44:58.000Z","updated":"2021-11-10T06:48:40.804Z","comments":true,"path":"2021/11/10/win32之PE文件/","link":"","permalink":"http://www.010101.cc/2021/11/10/win32%E4%B9%8BPE%E6%96%87%E4%BB%B6/","excerpt":"","text":"PE文件结构 Windows : PE (Portable Excuteable) 文件结构 Linux : ELF(Executable and linking formate) 文件结构 PE 文件特征和格式 执行二进制文件转16进制后,前2个字节是MZ, 3C位置值,再往下找3C位置的字节值会显示PE. 可判定为PE. PE文件结构排列DOS + PE文件头 + 字表 + 字节数 dos文件头 MZ头 :64byte + 20byte PE_header + 节表section_header 40byte + option_header 224byte节数据 内存拉升: PE文件在硬盘中200倍拉升, 在内存中1000倍拉升,空间扩充.","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"PE可执行文件","slug":"PE可执行文件","permalink":"http://www.010101.cc/tags/PE%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6/"}]},{"title":"线程切换之内核E_thead浅析","slug":"E_Thread","date":"2021-11-10T06:44:58.000Z","updated":"2021-11-16T11:17:34.367Z","comments":true,"path":"2021/11/10/E_Thread/","link":"","permalink":"http://www.010101.cc/2021/11/10/E_Thread/","excerpt":"","text":"Wirte by 021. E_ThreadK_THREAD 线程结构体 123456789101112131415161718192021结构体{ 线程名称 线程状态 堆栈起始地址 线程堆栈界限 (终止地址) 线程当前位置 ESP 线程函数参数 线程函数地址}线程结构体数组0 --- 当前线程 ,main1 --- 创建的新线程 线程状态不同存储 123456正在运行线程 --- kpcr等待线程 --- 等待链表就绪/调度线程 --- 32个链表 线程的生命周期 创建 — 初始化线程变量 12345678910111,结构体初始化赋值2,执行体,函数,参数,赋值7, kernelStack 栈顶 赋值,esp 赋值。kernelStack = 入栈对象 (1-6) 创建堆栈 12345创建堆栈,申请堆栈 stackpage , 0x80000 大小,4,堆栈初始化,起始地址、5,设定堆栈边界值(终止地址) 寄存器入栈 16, 入栈 线程切换 SwapContext 线程不是被动切换,而是主动切换。 意思是,无论是时间片到了的被动切换,还是线程IO阻塞主动出让执行权,都需要当前线程调用切换线程函数. 线程没有使用TSS保存寄存器,而是使用堆栈. 线程切换过程就是堆栈切换过程. 在线程切换过程中,会判断2个线程是否属于同一个进程,如果不是,就切换cr3页面,也就是进程空间也切换了. TheadSwap 1234567参数 : 1,当前线程(esp)地址, 2,将要被切换线程(edi)地址步骤: 1,保存当前线的栈顶,上图的push操作. 2,将当前线程(esp)地址,存储到KernelStack中 --- mov [esi kernelStack] ,esp 3,将内核中 将要被切换edi线程 的地址,交换到当前位置 --- mov esp, [edi kernelStack] 主动切换 1取出当前CPU kpcr正在执行的线程放到edi中 线程中断 时钟中断 线程中断条件 1231.时间片到期2,备用线程(KPCR.PrcbData.nextThread)3,主动调用切换切换线程函数,KiswapThread CPU时间片 123451,KPROCESS.ThreadQuantum 值是当前进程设置的时间片值2,_KTHREAD.ThreadQuantum 会读取 KPROCESS.ThreadQuantum的值,在线程初始化的时候在win32里面初始为6,每次中断会调用KeupdateRuntime函数会将当前线程_KTHREAD.ThreadQuantum - 3,如果减到0,则将KPCR.PrcbData.QuantumEnd 位 置为 非0,表示当前线程时间片到期. Tss 内核堆栈 12345initStack 栈底kernelStack 栈顶stackLimte 边界TSS.esp0得到当前线程0环堆栈,线程在切换的时候保证0环线程堆栈的唯一和有序性. 异常中断 1INT N, 页面异常 如何永久占用CPU? 1满足 不调用API , 并且不会出现异常,并调用cli指令,屏蔽时间中断,CPU就会忽略时钟片中断. 线程优先级 线程调度链表 1234532个线程调度双向链表。KiFindReadyThread 查找方式: 先找 31 ... 30 .. 29, 优先查找高级别的。32位:0 1 0 1 0 0 0 0 ... 0 , 31位上为1,证明当前有优先级的线程需要处理。当调度链表中没有任何需要调度执行的任务时,cpu会执行idelThread.","categories":[{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"}],"tags":[{"name":"进程&线程","slug":"进程-线程","permalink":"http://www.010101.cc/tags/%E8%BF%9B%E7%A8%8B-%E7%BA%BF%E7%A8%8B/"}]},{"title":"CPU内核模块之KPCR","slug":"博客模板KPCR","date":"2021-11-10T06:44:58.000Z","updated":"2021-11-10T06:49:08.943Z","comments":true,"path":"2021/11/10/博客模板KPCR/","link":"","permalink":"http://www.010101.cc/2021/11/10/%E5%8D%9A%E5%AE%A2%E6%A8%A1%E6%9D%BFKPCR/","excerpt":"","text":"github 访问慢,而其他网页正常.Mac 修改hosts文件12345678910111213141516171819202122232425262728293031Step 1. cd /etc/Step 2. sudo vi hosts# 粘贴140.82.112.4 github.com140.82.113.3 gist.github.com185.199.108.153 assets-cdn.github.com199.232.68.133 raw.githubusercontent.com199.232.68.133 gist.githubusercontent.com199.232.68.133 cloud.githubusercontent.com151.101.192.133 camo.githubusercontent.com199.232.68.133 avatars0.githubusercontent.com199.232.68.133 avatars1.githubusercontent.com199.232.68.133 avatars2.githubusercontent.com199.232.68.133 avatars3.githubusercontent.com199.232.68.133 avatars4.githubusercontent.com199.232.68.133 avatars5.githubusercontent.com199.232.68.133 avatars6.githubusercontent.com199.232.68.133 avatars7.githubusercontent.com199.232.68.133 avatars8.githubusercontent.com","categories":[{"name":"测试分类","slug":"测试分类","permalink":"http://www.010101.cc/categories/%E6%B5%8B%E8%AF%95%E5%88%86%E7%B1%BB/"}],"tags":[{"name":"测试标签","slug":"测试标签","permalink":"http://www.010101.cc/tags/%E6%B5%8B%E8%AF%95%E6%A0%87%E7%AD%BE/"}]},{"title":"JVM之Java类加载与自定义加载热更新","slug":"JVM之classLoader浅析","date":"2021-11-10T06:44:58.000Z","updated":"2021-11-11T07:41:22.126Z","comments":true,"path":"2021/11/10/JVM之classLoader浅析/","link":"","permalink":"http://www.010101.cc/2021/11/10/JVM%E4%B9%8BclassLoader%E6%B5%85%E6%9E%90/","excerpt":"","text":"Java类加载器浅析.java 文件生命周期1.java -> javac编译 -> Jvm加载classloader -> Jvm连接[验证,准备,解析] -> 准备初始化(常量变量等内存分配过程)-> GC 类加载器 双亲委派含义与功能简单分析 123456781,Bootstrap ClassLoader 启动类加载器 主要负责加载Java核心类库,%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等。2,Extention ClassLoader 标准扩展类加载器 主要负责加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件。3,Application ClassLoader 应用类加载器 主要负责加载当前应用的classpath下的所有类4,User ClassLoader 用户自定义类加载器 用户自定义的类加载器,可加载指定路径的class文件上面是Java中提供的4类加载器,每个加载器负责不同层次的class文件加载功能。 双亲委派的含义 123456 双亲委派是java为了保护程序稳定性,防止字节码重复加载,或者加载不安全的class文件的一种机制,机制规范了Java中四种加载器的加载class的范围. 当一个java收到一个类加载请求的时候,不会立刻执行当前类的加载函数,而是将加载任务抛给父类加载器,父类的加载器不是继承的,而是引用父类的加载器,从而达到加载扫描的作用,如果父类已经加载了该类,当前加载函数不会重复加载,负责将由父类加载。 当使用 类.class.getClassloader的时候返回null的时候,表明已经是顶层加载器, 否则返回的就是父类加载器地址. 双亲委派的功能 防止重复加载 防止加载不安全的class文件,比如用户自定义Long类然后加载进去,此时顶层加载器Bootstrap ClassLoader已经加载过一遍了,此时父加载器为空,所以自定义的Long类不会被加载,从而达到保证安全性的效果. 自定义classLoader, 重写findClass方法 定义加载路径 defind 解释 二进制 转换 class. findClass 找到自定义的class 转换 class 为目标类. 扩展 可以加密class文件,更安全 ClassLoader加载过程源码 12345678910111213141516171819202122232425262728293031323334353637383940414243protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 当父类没有加载,自定义加载函数 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } 热更新: 摘抄一段网上示例的代码,实现了,文件更改后,类的卸载和重新加载","categories":[{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"}],"tags":[{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"}]},{"title":"github访问慢,而其他网页正常解决","slug":"github 访问慢,而其他网页正常","date":"2021-11-08T14:04:58.000Z","updated":"2021-11-10T06:54:04.831Z","comments":true,"path":"2021/11/08/github 访问慢,而其他网页正常/","link":"","permalink":"http://www.010101.cc/2021/11/08/github%20%E8%AE%BF%E9%97%AE%E6%85%A2%EF%BC%8C%E8%80%8C%E5%85%B6%E4%BB%96%E7%BD%91%E9%A1%B5%E6%AD%A3%E5%B8%B8/","excerpt":"","text":"github 访问慢,而其他网页正常.Mac 修改hosts文件123456789101112131415161718192021222324Step 1. cd /etc/Step 2. sudo vi hosts# 粘贴140.82.112.4 github.com140.82.113.3 gist.github.com185.199.108.153 assets-cdn.github.com199.232.68.133 raw.githubusercontent.com199.232.68.133 gist.githubusercontent.com199.232.68.133 cloud.githubusercontent.com151.101.192.133 camo.githubusercontent.com199.232.68.133 avatars0.githubusercontent.com199.232.68.133 avatars1.githubusercontent.com199.232.68.133 avatars2.githubusercontent.com199.232.68.133 avatars3.githubusercontent.com199.232.68.133 avatars4.githubusercontent.com199.232.68.133 avatars5.githubusercontent.com199.232.68.133 avatars6.githubusercontent.com199.232.68.133 avatars7.githubusercontent.com199.232.68.133 avatars8.githubusercontent.com","categories":[],"tags":[{"name":"常见问题解决","slug":"常见问题解决","permalink":"http://www.010101.cc/tags/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"}]},{"title":"免主机,免备案,从0到1教你搭建个人博客(hexo + github page)!","slug":"github+hexo","date":"2021-11-08T14:04:58.000Z","updated":"2021-11-09T04:46:22.987Z","comments":true,"path":"2021/11/08/github+hexo/","link":"","permalink":"http://www.010101.cc/2021/11/08/github+hexo/","excerpt":"","text":"原理简介:1.github page 提供服务和博客文档云端托管2.hexo提供博客页面模板引擎(负责加载md文档并引用渲染到博客主页)3.md编辑器负责本地编辑博客内容4.git客户端负责push文档到github托管.5.github page 负责显示推送的最新内容环境准备以Mac机器为操作环境, 安装步骤本篇不作讲解.NodeJs12node -vv12.13.1 Hexo12345678910111213141516171819202122hexo versionINFO Validating confighexo: 5.4.0hexo-cli: 4.3.0os: darwin 20.6.0 11.6node: 12.13.1v8: 7.7.299.13-node.16uv: 1.33.1zlib: 1.2.11brotli: 1.0.7ares: 1.15.0modules: 72nghttp2: 1.39.2napi: 5llhttp: 1.1.4http_parser: 2.8.0openssl: 1.1.1dcldr: 35.1icu: 64.2tz: 2019cunicode: 12.1 Git客户端12git --versiongit version 2.23.0 MarkDown 编辑器1博主安装的是 typora. 操作部署步骤1. 总流程步骤123456789101112131415161718191,新建博客文档文件夹 ,博主是在桌面 /desktopmkdir blog2,初始化bloghexo init blog3, 编译环境hexo g4, 本地查看效果hexo s5, 部署githubhexo d6, 清除本地缓存hexo clean 2. hexo配置文件环境介绍123456789101112# hexo 配置文件,负责博客主题样式加载引导/blog/_config.yml# themes 博客主题文件夹 负责存储主题文件/blog/themes# public 此文件是对外开放的博客文件,也是推送github的文件/blog/public# 资源文件夹 ,负责你写的博客内容给主页加载的md部分,也可以CNAME放在其中这样Push不会被覆盖/blog/source ","categories":[],"tags":[{"name":"工具使用","slug":"工具使用","permalink":"http://www.010101.cc/tags/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"}]}],"categories":[{"name":"Redis","slug":"Redis","permalink":"http://www.010101.cc/categories/Redis/"},{"name":"工具使用","slug":"工具使用","permalink":"http://www.010101.cc/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"},{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/categories/C%E8%AF%AD%E8%A8%80/"},{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"},{"name":"Assembly","slug":"Assembly","permalink":"http://www.010101.cc/categories/Assembly/"},{"name":"内存管理","slug":"内存管理","permalink":"http://www.010101.cc/categories/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/"},{"name":"Java","slug":"Java","permalink":"http://www.010101.cc/categories/Java/"},{"name":"数据结构与算法","slug":"数据结构与算法","permalink":"http://www.010101.cc/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/"},{"name":"计算机硬件","slug":"计算机硬件","permalink":"http://www.010101.cc/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A1%AC%E4%BB%B6/"},{"name":"测试分类","slug":"测试分类","permalink":"http://www.010101.cc/categories/%E6%B5%8B%E8%AF%95%E5%88%86%E7%B1%BB/"}],"tags":[{"name":"高并发原子锁","slug":"高并发原子锁","permalink":"http://www.010101.cc/tags/%E9%AB%98%E5%B9%B6%E5%8F%91%E5%8E%9F%E5%AD%90%E9%94%81/"},{"name":"工具使用问题","slug":"工具使用问题","permalink":"http://www.010101.cc/tags/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E9%97%AE%E9%A2%98/"},{"name":"C语言","slug":"C语言","permalink":"http://www.010101.cc/tags/C%E8%AF%AD%E8%A8%80/"},{"name":"C++","slug":"C","permalink":"http://www.010101.cc/tags/C/"},{"name":"Redis","slug":"Redis","permalink":"http://www.010101.cc/tags/Redis/"},{"name":"操作系统内核","slug":"操作系统内核","permalink":"http://www.010101.cc/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8/"},{"name":"汇编","slug":"汇编","permalink":"http://www.010101.cc/tags/%E6%B1%87%E7%BC%96/"},{"name":"页","slug":"页","permalink":"http://www.010101.cc/tags/%E9%A1%B5/"},{"name":"JVM","slug":"JVM","permalink":"http://www.010101.cc/tags/JVM/"},{"name":"树结构","slug":"树结构","permalink":"http://www.010101.cc/tags/%E6%A0%91%E7%BB%93%E6%9E%84/"},{"name":"线性结构","slug":"线性结构","permalink":"http://www.010101.cc/tags/%E7%BA%BF%E6%80%A7%E7%BB%93%E6%9E%84/"},{"name":"设计模式","slug":"设计模式","permalink":"http://www.010101.cc/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"},{"name":"进程&线程","slug":"进程-线程","permalink":"http://www.010101.cc/tags/%E8%BF%9B%E7%A8%8B-%E7%BA%BF%E7%A8%8B/"},{"name":"并发原子锁","slug":"并发原子锁","permalink":"http://www.010101.cc/tags/%E5%B9%B6%E5%8F%91%E5%8E%9F%E5%AD%90%E9%94%81/"},{"name":"PE可执行文件","slug":"PE可执行文件","permalink":"http://www.010101.cc/tags/PE%E5%8F%AF%E6%89%A7%E8%A1%8C%E6%96%87%E4%BB%B6/"},{"name":"测试标签","slug":"测试标签","permalink":"http://www.010101.cc/tags/%E6%B5%8B%E8%AF%95%E6%A0%87%E7%AD%BE/"},{"name":"常见问题解决","slug":"常见问题解决","permalink":"http://www.010101.cc/tags/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3/"},{"name":"工具使用","slug":"工具使用","permalink":"http://www.010101.cc/tags/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"}]}