佛教

总结一:

正念:观察自己的感受和情绪;接受你的感受和情绪;不要评判自己的感受和情绪;保持专注;

你可以问自己:“我现在感觉到什么?”“我的身体感觉怎样?”“我的思维和情绪是怎样的?”通过观察自己的内在体验,你可以深入了解自己的情绪和想法,从而更好地处理它们,减少烦恼和焦虑。

观察自己的感受和情绪:正念的第一步是观察自己的感受和情绪。这意味着不要试图压制或逃避你的感受,而是尝试意识到你感到痛苦。

接受你的感受和情绪:接下来,你需要接受你的感受和情绪,不要试图去改变它们或者对它们有所期待。这意味着你需要学会接受现实,和你所感受到的痛苦。

不要评判自己的感受和情绪:正念的第三步是不要评判自己的感受和情绪。这意味着不要指责自己或感到自己应该感到不同的情绪。你的感受是真实的,你需要承认并尊重它们。

保持专注:在你感到痛苦和不舍的时候,你可以通过专注于呼吸或身体感受来维持正念。当你的注意力转移到当下的经验和感受上时,你可以减轻痛苦并更好地处理情绪。

 

总结二:

更多的是关注当下,接受现实,感谢曾经拥有过的美好时光,而不是沉浸在痛苦和悲伤中,与家人和朋友建立正面和支持的关系,以获得支持和安慰。简单点说就是:正视情绪,观察情绪,接受情绪,保持专注,享受当下,不要有欲望,找朋友或家人诉说。

1.佛教的理念:

四谛:苦谛、集谛、灭谛和道谛,它们描述了人类的存在和解脱之路。

缘起性:一切现象都是由因果关系而生,没有永恒不变的实体存在。

无我:个体的存在是虚幻的,不存在永恒的自我。

涅槃:摆脱生死轮回,达到精神的解脱和平静的境界。

善恶因果:行善会带来好果,行恶会带来恶果,因果关系也是缘起性的体现。

2.佛教对个人有用的思想和方法包括:

禅修:通过冥想和静心,培养内在的平静和专注,从而超越烦恼和苦难。

正念实践:通过觉察和关注当下的感受、想法和行为,培养自我意识和觉醒,提升心灵的智慧和洞察力。

忍辱:面对困难和挑战时,保持冷静和坚忍的心态,不为外界所动,从而超越自我和烦恼。

善行积累:通过行善和修行,增加善业的力量,减少恶业的负面影响,从而获得更多的福报和正面能量。

真理探求:通过思考和探求真理,理解人类存在的本质和意义,从而超越生死和苦难,达到内在的平静和解脱。

3.七情六欲:

佛教认为七情六欲是导致烦恼和苦难的根源之一。七情指的是喜、怒、忧、思、悲、恐、惊等人类情感,而六欲则是眼、耳、鼻、舌、身、意六种感觉和欲望的表现。

佛教认为,七情六欲是人类烦恼和苦难的重要来源,因为它们让人们沉迷于欲望的循环之中,无法超越生死轮回。人们在追求欲望和感官刺激时,往往会忽略自己内心的真实需求和价值,从而导致贪婪、愤怒、嫉妒、焦虑、恐惧等负面情绪的出现。这些负面情绪和欲望,会阻碍人们内在智慧和解脱的发展,使人们陷入无尽的苦难之中。

因此,佛教鼓励人们通过禅修、正念实践等方式,培养内在的平静和洞察力,超越七情六欲的束缚,从而实现内心的解脱和精神的平静。通过摆脱欲望和负面情绪的影响,人们可以更好地关注自己内在的真实需求和价值,实现全面的人生成长和发展。

 

4.禅修

禅修是通过冥想和静心来培养内在的平静和专注,超越世俗的烦恼和苦难,实现心灵的解脱和平静。

禅修的核心是坐禅,下面是坐禅的基本步骤:

a.找一个安静、舒适的地方坐下,如禅堂或自己的房间。

b.采用莲花坐或半跏趺坐的姿势,身体直立但不僵硬,双手放在膝盖上,眼睛略微闭合,口微闭,舌头轻轻抵住上颚。

c.关注呼吸,让呼吸自然地流动,不要强迫控制它。注意呼吸进出的感觉,从鼻子进入、从嘴巴出来的气流,感受空气的温度和质地。

d.当你的注意力开始跑神,想到其他的事情时,不要把注意力引回呼吸,避免评判、分析、反应、幻想和担忧,只需要轻轻地把注意力带回呼吸。

e.继续保持这种状态,直到冥想结束。

5.正念

正念是通过觉察和关注当下的感受、想法和行为,培养自我意识和觉醒,提升心灵的智慧和洞察力。正念的核心是保持当下的专注和觉察,关注自己内在的体验,以此深入了解自己和周围的世界。下面是正念的基本步骤:

a.关注当下的感受和体验,比如呼吸、身体的感觉、外界的声音等。

b.不要评判、分析或反应,只是保持关注和觉察,让自己逐渐深入了解当前的状态。

c.然后开始观察自己的想法和情绪,不要试图改变或控制它们,只是保持觉察和专注。

d.当你的注意力开始跑神时,不要判断或批评自己,只需要轻轻地把注意力带回当前的感受和体验。

e.继续保持这种状态,直到禅修或正念结束。

6.正念练习及例子

练习:

a.呼吸观察:在舒适的姿势下坐下,专注于你的呼吸。注意你的呼吸进入和离开你的鼻子和口腔。当你的思维开始漂移时,不要被它们带走,只需轻轻地带回你的注意力到呼吸上。

b.身体感受观察:在坐禅中,你可以注意你的身体感受,例如你的重量感、紧张感、温度感等。注意这些感受时,不要做出任何评判或反应,只是观察它们,让它们自然地流动。

c.情绪观察:当你感到愤怒、焦虑或悲伤时,你可以观察你的情绪,注意你的身体和思维的反应。不要试图抑制或掩盖你的情绪,只是观察它们,让它们自然地流动。通过这种方式,你可以更好地了解你的情绪,并更好地处理它们。

d.思维观察:当你的思维漂移或杂念缠绕你的头脑时,你可以注意你的思维,观察它们的内容、流动和变化。不要被思维带走,只是观察它们,让它们自然地流动。

例子:

a.饮食:在吃饭时,注意你的食物的味道、口感和气味。不要分散你的注意力,只是享受你的食物。

b.行走:在走路时,注意你的脚步和身体的感受。感受你的脚掌与地面的接触,感受你的身体重量和平衡感。

c.与人交往:在与人交往时,注意你的情绪和反应。不要被情绪控制,只是观察它们。

d.工作:在工作时,注意你的呼吸和身体感受。保持专注和冷静,不要让压力和杂念影响你的工作效率。

e.睡眠:在睡觉前,可以进行正念冥想来放松自己。注意你的呼吸和身体感受,让自己进入深度放松的状态,以更好地入睡。

f.洗澡:在洗澡时,注意你的身体感受和水的感觉。感受水流的触感和温度,注意你的身体洗净和放松的感觉。避免心急洗澡和分散注意力,专注于当下的水和身体经验。

g.扫地:在扫地时,注意你的扫帚和地面的感受。感受扫帚的摩擦力和地面的杂物,注意你的手和身体的动作。避免心急扫地和分散注意力,专注于当下的扫地经验和感受。

h.呼吸:在呼吸过程中,注意气息的进出,专注于呼吸的感觉,避免杂念的干扰。你可以数数呼吸,或者专注于气息的感觉,从而帮助自己保持专注和平静。

7.佛教的苦

生老病死的苦:生命的自然流程包括出生、成长、衰老和死亡。这是每个人都无法避免的过程,也是人类普遍经历的苦难。

爱别离的苦:人类往往与亲人、朋友和其他人建立深厚的关系。当这些关系受到破坏或分离时,会带来痛苦和悲伤。

不得所愿的苦:当我们努力追求一些目标或满足某些欲望,但最终失败或无法实现时,我们会经历痛苦和失落。

五阴盖罩的苦:佛教认为人类的存在被五阴所覆盖,包括身体、感受、思想、意识和意识形态。这些阴盖让我们感受到许多形式的苦难,例如生理疾病、情感困扰和思想上的困惑。

怨憎会恼的苦:人类往往会因为别人的行为或态度而感到困扰或不快。这种情况下,我们经历的苦难可能来自于自己的怨恨、烦恼或憎恨。

8.苦的例子

a.假设一个人被诊断出患有绝症,面临生命威胁和痛苦的治疗过程。他可以通过修行和觉醒来减轻苦难的程度,例如:

通过正念冥想来认识自己的内心感受和情绪反应,并减轻焦虑和恐惧。

与家人和朋友建立正面和支持的关系,以获得支持和安慰。

关注当下的经验和感受,享受现在的生命,减轻对未来或过去的担忧和焦虑。

通过慈悲和无私的行为来提高自己的幸福感和生命意义,从而减少对生老病死的恐惧和痛苦。

b.当我们失去爱人时,我们可以怀着感恩的心,感谢曾经拥有过的美好时光和对方的陪伴,而不是沉浸在痛苦和悲伤中。我们可以思考自己如何继续前行,发掘生活中其他的美好,并把自己的经历和经验分享给身边的人,帮助他们度过类似的痛苦。我们还可以培养慈悲心,去关爱身边的人,让他们感受到我们的温暖和关爱。

总之,缓解爱别离的苦需要我们培养智慧和慈悲心,接受分离的不可避免,珍惜当下,关爱身边的人,并以感恩的心态对待生命中的一切。

c.缓解不得所愿的苦的方法主要有以下几个:

接受现实:接受现实是减轻不得所愿苦的第一步。我们不得不承认,有些事情不在我们的控制范围之内,无论我们如何努力,最终的结果可能不是我们期望的那样。接受这个事实,能让我们更加从容地应对挫折和失落。

改变态度:我们可以改变自己的态度来应对不得所愿的情况。这包括尝试从不同的角度看待问题,从失败中学习经验教训,寻找积极的方面,并以一种更积极的方式看待未来。

保持平静:当我们面临失落和挫折时,我们的情绪可能会变得焦虑、愤怒或沮丧。这时候,保持平静是减轻不得所愿苦的关键。我们可以通过冥想、呼吸练习或其他的放松技巧来保持平静。

换个方向:我们可以通过改变方向来应对不得所愿的情况。这包括尝试新的方法或寻找新的机会。

d.假如你和某个人之间有矛盾,你可以采取以下措施来缓解怨憎会恼的苦:

首先,不要攻击或责怪对方,而应该尽可能冷静地探讨问题的本质,找出共同点和不同点,以达成共识。

如果对方不肯合作或者态度固执,你可以尝试转移自己的注意力,将精力集中在其他的事情上,比如读书、运动或者和朋友聊天,以减轻自己的负面情绪。

同时,你也可以借助佛教的教义来缓解自己的怨憎会恼。你可以学习佛经,培养智慧和慈悲心,从而更好地处理和解决与他人的矛盾,同时也可以通过禅修等方式,调整自己的心态和情绪,从而缓解内心的苦

e.假设一个人感到身体疼痛,通过内观禅修,他可以不是针对疼痛本身,而是将注意力集中在感受身体疼痛的过程中,观察这种感受的变化和流动。逐渐地,他可能会发现疼痛感并不是静态不变的,而是在不断变化和流动之中。通过观照疼痛的变化和流动,他可能会逐渐认识到疼痛感并非固定不变的实体,最终超越了身体疼痛的苦。

注:还是找医生靠谱

9.八正道

苦的原因在于人们执着于欲望,可以通过八正道实现解脱。

  1. 正见:正确的看待世界和自我,摒弃错误的观念和想法,认识真实的人生和存在。
  2. 正思:正思维是指摆脱执著、偏见、成见和不真实的想法,不被过去或未来的想象所干扰,集中精神,以清晰、明了的思维来面对事物。
  3. 正语:发言真实,言行一致,不说谎话,不恶语相向,不背后议论他人,言语体现内心的美好。
  4. 正业:根据佛法和良心,从事合乎道德规范和社会公德的职业和工作,通过自身的努力实现自我提升和对社会的贡献。
  5. 正修:指正确的修行方法,包括禅修、念佛、持咒等方式,目的是通过反观内心,认识自我,超越烦恼,达到解脱和平静。
  6. 正精进:持之以恒地修行,不断提高自我,努力达到涅槃的境界。
  7. 正念:正念是指正确认知和正确认识的基础上,以注意力集中、持续和稳定的方式,保持对当前的感受和体验的觉知,不被外界的干扰所扰乱,保持心理平衡。
  8. 正定:通过正念和正精进的实践,达到内心的平静和专注,摆脱烦恼和执着,达到涅槃的境界。

10.离群索居

个人主义和离群索居的倾向:佛教重视个人的修行和悟道,但这种个人主义的倾向有时候可能会导致离群索居和对社会责任的忽视。

情绪的解析

一、跨越文化的情绪

不同文化的人都会有相同的情绪,遇到别人插队都会有厌恶的情绪,而不会有高兴、轻蔑的情绪产生。

二、我们何时变得情绪化

1.例子

当你正和朋友交谈时,突然对面来了一辆飞快的车,马上就要撞到你了,此时,你会冒冷汗,心跳加速,双腿充血,然后马上踩刹车打方向盘,最后救了你一命,这是因为恐惧情绪下的本能,是好的。即情绪是第一时间产生的,也是无意识的。

情绪是我们对切身的重大事件所产生的反应

存在共同的情绪诱因,但是也有个体差异。个体情绪的诱因,有后天学习得来的,是每个人经历的反应。

人类共有的情绪诱因。

2.九种产生或改变情绪的诱因

a.通过自动评估体系群的运作完成的;

b.从思考性评估开始,最后再以自动评估结束;

c.对过去情绪经历的回顾

d.想象

e.谈论过去的经历

f.体会别人的情绪

g.别人教给我们何时应该情绪化

h.违反社会规范

i.主动做出某种表情而产生相应的情绪

 

三、改变情绪诱因

悬崖边行走,尽管有护栏,依旧会有恐惧的情绪产生。表明,知识通常无法超越自动评估体系群对所产生的情绪的判断。

在情绪已经产生之后,我们也许能够意识到情绪是不必要的,然而情绪仍在持续。我认为,这类诱因要么是人类进化过程中形成的情绪主题,要么是后天习得的,与主题很相近。当后天习得的诱因与主题有较大差别时,意识才有可能干扰情绪产生的过程。换言之,如果关注的对象和主题相去甚远,我们有意识的选择才有可能超过自动评估体系群的判断。

Simon写作

一、task1概述

  • 20分钟完成。
  • task1不应该有结论和个人观点,只是描述图表,可以有个summary概要总结。

1.结构

a. 称呼
Dear Mr/Mrs. + name / Sir or Madam / officer / professor,   (正式用Sir or Madam; 半正式用Mr.+姓; 熟人用名)
b. 主体部分:3段
xxx
xxx
xxx
c. 结尾敬语及落款署名
Best regards/Best wishes/Yours sincerely/Yours faithfully,(正式用faithfully,半正式用sincerely,否则wishes)
Name

注:每一行顶头,段落中间空行。

 

2.例子

Dear Mr Li,

I am writing to xxx.

xxx.

Best wishes,

Tom

 

附录:https://zhuanlan.zhihu.com/p/266223414

3.雅思G类小作文常用句式

1.投诉信开头常用句式

I am writing to inform you that I am dissatisfied with your …

我写信是想告诉你我对你的……

I am writing to express my dissatisfaction with…

我写信是为了表达我对……的不满。

I regret to have to inform you that…

我很遗憾地通知你……

I feel bad to trouble you but I am afraid that I have to make a complaint about…

我很抱歉麻烦你,但恐怕我不得不投诉……

I am writing to complain about…

我写信是为了投诉……

1.投诉信结尾常用句式

I understand you will give immediate attention to this matter.

我知道你会立即处理这件事。

I would like to have this matter settled by the end of …

我希望在……之前解决这件事。

I feel something ought to be done about…

我觉得应该对……做点什么。

I hope that the above situation will be improved as soon as possible.

希望以上情况能尽快得到改善。

Your effort to provide good services will be highly appreciated by all.

您为提供良好服务所做的努力将得到所有人的高度赞赏。

We will appreciate your willingness to make up for the loss.

我们将感激你方弥补损失的意愿。

I am looking forward to a favorable reply at your earliest convenience.

我期待您在方便的时候尽早给我一个满意的答复。

Thank you for your consideration and I will be looking forward to your reply.

感谢您的考虑,期待您的回复。

I would like to have this matter settled by…

我希望这件事能在……之前解决。

I appreciate it very much if you could…

如果你能……我将不胜感激。

I trust that you will consider this matter seriously and make an effort to prevent the recurrence of this kind.

我相信您会认真考虑此事,并努力防止此类事件再次发生。

 

 

二、task2概述

  • 40分钟完成。
  • 需要250词以上,建议分4段,13句话。
  • 首段两个句子,中间两段,每段5句,末尾1句话总结,总共13段。
  • 写作时间:5分钟写开头,10分钟第一段,10分钟第二段,末尾5分钟。一共30分钟。然后构思可以用10分钟,整体40分钟写大作文。
  • 正文段落:不需要复杂的连接词,复杂的连接词不会提高词汇分,高级词汇分要靠topic vocabulary(主题词汇)。
段落 句子 时间 字数 备注
1 2 5mins 30-40 两句话,第一句题目概述,第二句对题目做出回答。
2 5 10mins 90-100 方法一:主题句(观点)。Firstly。扩充。Secondly。Finally。

方法二:主题句(观点)。解释1。解释2。解释3。例子。

3 5 10mins 90-100
4 1 5mins 不要出现新的观点,一句话,repeat(转述,用不同的词) or summary。

 

三、task2类型及首段

1.discussion

2.opinion

同意:

不同意:

中立:

3.Problem and solution

4.Two-part question

 

四、main body

1.概述

正文部分段落,分为以下方法。第一种适合想到的点比较多,第二种适合一种观点展开讨论的。

注:两种方法如何选用?需要自己区分是否是三个独立的观点还是只是同一观点的详细阐述。

2.第一种 – 框架

3.第一种 – 写作

4.第一种 – 五句话如何写

注:不需要复杂的连接词,复杂的连接词不会提高词汇分,高级词汇分要靠topic vocabulary(主题词汇)。

 

5.第二种 – 框架

注:上面几个点,都是对voluntary(自愿)的详细阐述,所以是同一种观点。

6.第二种 – 写作

7.第二种 – 五句话如何写

 

五、总结

一句话总结,不要引入新的观点,用新的词转述观点,或者做总结summary。

1.discussion

问题:

例子:

2.opinion

问题:

例子:

3.Problem and solution

问题:

例子:

4. two-part question

问题:

例子:

 

六、例子

1.agree or disagree

a.问题及框架

b.introduction

c.第二段

d.第三段

e.conclusion

16结束,下一课17 https://www.bilibili.com/video/BV15j411K79Z/?p=14&spm_id_from=pageDriver&vd_source=46ecf9bdfd8287ee029b66b491512f92

Spring cloud

一、Spring Cloud Config

1.自己维护client和server

  1. 把配置文件放在Git Repository中。
  2. Config Server从Git repository中读取配置信息。
  3. 其他客户端再从Config Server中加载配置文件

缺点:没法自动刷新,需要重启服务才能读到最新的配置,所以需要引入actuator可以自动刷新。

2.actuator实现自动刷新

每次发布配置后,需要访问一个接口,才会触发配置的更新。

3.Spring Cloud Bus

通过引入mq,可以达到自动更新的目的。目前官方支持kafka和RabbitMQ。

4.结合 Eureka 使用 Spring Cloud Config

通过将Spring Cloud Config注册在Eureka中,可以搭建集群实现高可用。注:Eureka 是 Netflix 出品的用于实现服务注册和发现的工具。

  1. 把配置文件放在Git Repository中。
  2. Config Server从Git repository中读取配置信息。
  3. Config Server将自己注册到Eureka中
  4. Client从Eureka中获取Config Server的信息
  5. Client访问Config Server,加载配置信息

参考:https://blog.csdn.net/weixin_44335140/article/details/115801223

二、spring data jpa

总的来说JPA是ORM规范,Hibernate、TopLink等是JPA规范的具体实现,这样的好处是开发者可以面向JPA规范进行持久层的开发,而底层的实现则是可以切换的。Spring Data Jpa则是在JPA之上添加另一层抽象(Repository层的实现),极大地简化持久层开发及ORM框架切换的成本。

参考:https://blog.csdn.net/qq_42495847/article/details/107991361

Redis基础知识

一、Redis基础

1.Redis为什么快

  • 纯内存KV操作:减少了一些不必要的 I/O 操作。
  • 单线程操作:省去多线程时CPU上下文切换的时间,也不用去考虑各种锁的问题。(机器内存和网络带宽才是瓶颈)
  • 高效的数据结构:底层多种数据结构支持不同的数据类型,使得数据存储时间复杂度降到最低。
  • I/O 多路复用:多个客户端的I/O请求,当某个socket(客户端连接)的请求可读或者可写的时候,它会给一个通知,达到一个线程同时处理多个IO请求的目的。

注:Redis 单线程是说一个线程处理所有网络请求,接收到用户的请求后,全部推送到一个队列里,然后交给文件事件分派器。后面的依旧是多线程。

注2:I/O :网络 I/O,多路:多个 TCP 连接,复用:共用一个线程或进程。也就是多个客户端的I/O操作复用一个线程。

注3:IO多路复用是指内核一旦发现进程指定的一个或者多个文件描述符IO条件准备好以后就通知该进程。

注4:Redis6.0 之后引入了多线程,处理命令还是单线程,网络数据的读写这类耗时操作上是用多线程。

2.Redis与Memcached

2.1 概述:

Memcached :是高性能分布式内存缓存服务器,本质一个key-value 数据库,但不支持数据持久化,服务器关闭后全丢失。只支持key-value结构。

Redis:将大部分数据放在内存中,支持的类型有:字符串、hash、list、set、zset。

2.2 区别

两者都是内存数据库,数据都在内存。不过memcache还可用于缓存其他东西,例如图片、视频等等

1.Redis数据类型更丰富;

2.Redis支持持久化,挂掉后可以通过aof文件进行恢复,而memcache不行;

3.Redis还可以用来做消息队列、数据缓存等,而memcache主要用来做SQL语句缓存、用户临时数据缓存等;

4.Redis支持数据备份,即master-slave模式的主从数据备份;

5.Redis并不是所有数据all存在内存中,当物理内存用完时,可以将一些很久没用到的value 交换到磁盘。

3.数据结构

Redis 有8?种数据结构,主要由5种最基本的数据结构(String、List、Hash、Set、zset) 加 HyperLogLogs(基数/不重复元素 统计)、Bitmap (位存储)、Geospatial (地理位置)。

注:最新的可能不长这样,最新的有QuickList,结合了双端队列和压缩链表的优点,细节可以见 这里

粗略帮助记忆:

String:C的char*不高效,所以用了sds。

List:有序队列,所以用双端队列。

Hash:Hash和压缩链表。

Set:元素不重复,用hash结构(不重复)。

Zset:有序列表,所以用跳表。

3.1 String

动态字符串SDS(Simple Dynamic String)。String只用SDS数据结构。

简单说就是:1.用一个 len 字段记录当前字符串的长度,想要获取长度只需要获取 len 字段即可,而传统C语言需要遍历字符。2.每次空间预分配double空间 3.字符串缩短时不直接回收,记录到free里面,后续操作直接用free对应的空间。

注:Redis基于C语言开发,但是没用char*类型,引入了sds。因为c里面判断数组长度得遍历到 ‘\0’,sds可以直接知道长度。

  • 空间预分配对 SDS 修改或扩充时,会额外分配未使用的空间。len长度小于 1M,double。如果修改后长度大于 1M,那么将分配1M的使用空间。

  • 惰性空间释放SDS 缩短时,并不会回收多余的内存空间,而是使用 free 字段将多出来的空间记录下来。如果后续有变更操作,直接使用 free 中记录的空间,减少了内存的分配。

用途:平常的存储token等

3.2 List

List用双端列表和压缩列表两种数据结构。双端列表只有List用。

列表 List 更多是被当作队列或栈来使用。底层是用的双端队列:链表中每个节点有两个指针,prev 指向前节点,next 指向后节点。

1.头结点有len长度,能知道链表长度。2.头节点里有 head 和 tail 两个参数,分别指向头节点和尾节点,方便对两端操作。

用途:消息队列。

3.3 Hash

List、Hash、Zset都会压缩列表。

类似于双端链表,只不过双端列表需要存储前后指针等额外的数据。所以用压缩列表。

所有的操作通过指针与解码出来的偏移量进行的。并且压缩列表的内存是连续分配的,遍历的速度很快。

注:压缩列表可以理解成数组的升级版,只不过数组每个元素空间大小固定(取最大元素的长度作为每个元素空间大小,存储不同数据可能有空隙),压缩列表更紧凑。所以每个entity会存储上一元素和当前元素的大小(用于遍历的时候区分不同元素)。

  • 好处:极大的节省了内存空间
  • 缺点:不能存储太多的元素,否则遍历效率就会很低;其次,新增或者修改某个元素时,会重新分配内存,甚至可能会引起连锁更新(每个entity记录上一节点元素大小的字段是一样的,如果跨数量级了就会连锁更新)。

用途:存储对象。

3.4 Set

用hash数据结构,能够在 O(1) 时间复杂度内取出和插入关联的值。

用途:去重

3.5 Zset

用跳跃表和压缩列表两种数据结构。跳跃表只有Zset用。

跳跃表,其在链表的基础上增加了多级索引来提升查找效率。

简单的说就是多级链表,方便快速查找。查找时间复杂度 O(logN)。

用途:排行榜

4.分布式锁

利用setnx和getset命令。Getset 命令用于设置指定 key 的值,并返回 key 的旧值。

原来分布式锁实现会比较复杂:1.要考虑锁不释放,引入expire 2.setnx和expire非原子操作,引入自动检测是否过期,过期了则getset占用锁 3.需要考虑是否会删除别人的锁(超时挂起了被别的线程占用了,恢复后把锁删了)(解决:存储时增加标记,删除判断标记是否是自己的)

注:最新的setnx和expire官方提供了原子操作,所以不用考虑后续了。

5.过期数据删除策略

  • 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。优点:对 CPU 友好 缺点:可能会造成太多过期 key 没有被删除。
  • 定期删除 : 每隔一段时间取一批过期key 执行删除操作。优点:对内存更友好。

注:Redis对应key的过期时间,维护在过期字典里(hash表),每个key对应一个过期时间,是一个long long类型的整数。

6.内存淘汰策略

Redis 提供 6 种数据淘汰策略:

  • volatile-lru(least recently used):从设置了过期时间的key中,选最近最少使用的数据淘汰
  • volatile-ttl:从设置了过期时间的key中,挑选将要过期的数据淘汰
  • volatile-random:从设置了过期时间的key中,任意选择数据淘汰
  • allkeys-lru(least recently used):在键空间中,移除最近最少使用的 key(这个是最常用的)
  • allkeys-random:从数据集中任意选择数据淘汰
  • no-eviction:禁止驱逐数据,报错。

简单说就是:当空间不足时,会报错,或者随机删key,或者从有过期时间的key里面随机删或者根据LRU删或者根据快要过期删

4.0 版本后增加以下两种:

  • volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

二、数据持久化方式

rdb

aof

三、缓存一致性

1.更新缓存 VS 淘汰缓存

  • 更新缓存:数据不但写入数据库,还会写入缓存

优点:缓存不会增加一次miss,命中率高

缺点:加锁及各种并发情况可能会复杂

  • 淘汰缓存:数据只会写入数据库,不会写入缓存,只会把数据淘汰掉

优点:简单

缺点:会存在缓存miss

注:淘汰缓存后也可以通过加锁来保证只有一次命中DB,所以尽量是淘汰缓存。如果更新缓存的复杂度低那么可能才会考虑。

 

  • 先删缓存,再更新数据库  —> 进阶版:延迟双删(更新完了再删一遍)
  • 先更新数据库,再删缓存 —> 进阶版:监听binlog变更的mq,做删除

 

其他的更新缓存的不推荐:

  • 比如先更新缓存,再同步更新DB;或者先更新缓存,再异步批量更新DB。
  • 或者先更新DB再更新缓存

2.3种常用的缓存读写策略

  • Cache Aside Pattern(旁路缓存模式):先更新DB,然后删除缓存
  • Read/Write Through Pattern(读写穿透):先更新缓存再同步更新DB(缓存没有直接更新DB)
  • Write Behind Pattern(异步缓存写入):只更新缓存,异步批量更新 DB。

注:上面的读都一样,缓存有读缓存,缓存没有读DB写缓存。

注2:后两种并不常用。第三种可能点赞,或者维护浏览量这种场景可能用,吞吐量比较高。

 

3.几种缓存问题

  • 缓存穿透:请求不存在的key,每次都会落在DB上。

解决方法:布隆过滤器(一个白名单,判断请求值在不在集合中);缓存空对象(避免用一个不存在的key一直请求一直打到DB)

注:布隆过滤器可能会误判,因为采用的是hash,可能会hash冲突,那么会有小概率误判(部分不在白名单的误判成在了)。但是不影响,因为布隆过滤器说没在,那么就肯定没在。

  • 缓存击穿:缓存失效的时候,所有请求打到了DB。

解决方法:加锁

  • 缓存雪崩:同一时间不同key的缓存同时失效,请求都打到了DB。

解决方法:打散过期时间。

 

DB基础知识

一、MySQL

1.什么是MySQL

MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息等。

2.MySQL架构

  • 连接器: 身份认证、权限相关。
  • 查询缓存: 查询的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
  • 分析器: 词法分析、语法分析。
  • 优化器: 按照 MySQL 认为最优的方案去执行。
  • 执行器: 操作引擎,返回结果。
  • 存储引擎 : 主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。

3.MySQL存储引擎

主要有四种:MyISAM、InnoDB、MEMORY、Archive,其他的还有BDB等

3.1 MyISAM:

数据库存储对应三个文件,一个索引文件,一个数据文件,一个表结构文件。叶子节点data域存放的是数据的地址。

MyISAM拥有较高的插入、查询速度,但不支持事务,支持表锁,不支持外键。

MYI:索引文件

MYD:数据文件

3.2 InnoDB:

数据库存储对应的两个文件,一个表结构文件,一个数据和索引文件。叶子节点存放的是数据。

支持事务安全表(ACID),支持行级锁和外键,InnoDB是默认的MySQL引擎。

3.3 MEMORY:

MEMORY存储引擎将表中的数据存储到内存中,MySQL中使用该引擎作为临时表,存放查询的中间结果。数据的处理速度很快但是安全性不高。

3.4 Archive:

如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive。

 

注: MySQL当前默认的存储引擎是 InnoDB。 5.5.5 之前,MyISAM 是默认存储引擎。

注2:show engines;    //查看MySQL引擎

二、事务

1.什么是事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。

2.数据库事务四大特性

ACID

  • Atomic(原子性)     事务必须是原子的工作单元
  • Consistent(一致性)  事务完成时,必须使所有数据都保持一致状态
  • Isolation(隔离性)    并发事务所做的修改必须和其他事务所做的修改是隔离的
  • Duration(持久性) 事务完成之后,对系统的影响是永久性的

3.MySQL事务隔离级别

3.1 并发事务带来的问题

3.1.1 脏读:在一个事务处理过程里读取了另一个未提交的事务中的数据。(读取了另外事务未提交的内容)

3.1.2 不可重复读:一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。(读取了另外事务提交的内容)

3.1.3 幻读:事务T1将表中所有某列的数据由“1”改为“2”,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

注:不可重复读侧重于修改,幻读侧重于新增删除。解决不可重复读的问题只需锁住满足条件的,解决幻读需要锁表

3.2MySQL事务隔离级别

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
读已提交/不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

3.3 MySQL隔离级别的实现

基于锁和 MVCC 机制共同实现。

SERIALIZABLE 隔离级别,是通过锁来实现的。除了 SERIALIZABLE 隔离级别,其他的隔离级别都是基于 MVCC 实现。

4. Mysql里的事务处理过程

  1. 记录redo和undo log文件,确保日志在磁盘上的持久化
  2. 更新数据记录
  3. 提交事务 ,redo 写入commit记录

4.1 Undo + Redo事务的简化过程

假设有A、B两个数据,值分别为1,2,开始一个事务,事务的操作内容为:把1修改为3,2修改为4,那么实际的记录如下(简化):
A.事务开始.
B.记录A=1到undo log.
C.修改A=3.
D.记录A=3到redo log.
E.记录B=2到undo log.
F.修改B=4.
G.记录B=4到redo log.
H.将redo log写入磁盘。
I.事务提交

5.分布式事务

单独开模块说明

三、索引

1.什么是索引

索引是帮助高效获取数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。

为什么不用hash:hash无法实现范围查找,比如搜索id>5。hash冲突。

优点:加快数据检索速度;通过唯一索引可以保证数据唯一。

缺点:创建和维护耗费时间,降低更新表的速度;存储索引耗费物理空间。

1.1 时间局部性原理和空间局部性原理

  • 时间局部性原理:查找时,会缓存。比如先查找了itemID=1的,猜想你可能还会查找=1的,则会缓存
  • 空间局部性原理:扩大范围查找。要查找itemID=1的,则同时可能会读取itemID=2或3 的。

1.2 三种存储方式可视化页面

https://www.cs.usfca.edu/~galles/visualization/BST.html

https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

1.3 BTree和B+TREE

BTree也就是B-树,是一种多路搜索树,对应节点都存储数据。B+树只有叶子节点存储数据。

 

1.4 MYISAM和INNODB

MYI:索引文件

MYD:数据文件

以name为索引的B+Tree类似于上图,每次找到对应节点,该节点记录地址,然后从对应地址取得数据返回。

MyISAM为非聚集索引(数据存储在一个地方,索引存储在另外一个地方)。

 

以id(主键)查找数据时,从根节点开始找到对应节点,返回数据。而且对应id>1这种查找(空间局部性原理)会比较方便。

以name为副索引查找时,会先找到主键,然后从根节点查找对应数据返回。

 

1.5 为什么要用自增ID做主键

插入:每申请一页内存后,能充分利用内存,用完后,再去申请下一块(因为自增,所以可以充分用连续的地址空间,如果是乱序的话,就不会一块用完才去申请下一块)

查找:数字大小比较 方便查找

1.6 索引类型

细节可以见:索引类型

  • 普通索引:是最基本的索引,它没有任何限制,只是为了加快查询速度。
  • 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
  • 主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
  • 组合索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
  • 全文索引(Full Text) :为了检索大文本数据中的关键字的信息。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。

注:主键索引是一级索引,其他都属于二级索引,即通过二级索引找到主键id,再回表查到具体的数据。

  • 聚集索引:索引结构和数据一起存放的索引。主键索引属于聚集索引。叶子(节点存放的是数据)
  • 非聚集索引索引结构和数据分开存放的索引。二级索引属于非聚集索引。(叶子节点存放的是主键+列值)。优点:更新代价小。缺点:需要回表。

覆盖索引:需要查询的字段正好是索引的字段,就不需要再回表查询了。

2.联合/组合索引的最左前缀匹配

在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配。

如:当创建(a,b,c)复合索引时,想要索引生效的话,只能使用 a和ab、ac和abc三种组合!(ac索引其实用的是a的?)

3.索引失效的情况

  • 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。
  • 对于多列索引,不是使用的第一部分,则不会使用索引(最左前缀匹配)
  • like查询以%开头(如果是xxx%则会使用到索引)
  • 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
  • 如果mysql估计使用全表扫描要比使用索引快,则不使用索引

4.建立索引原则

  • 不为 NULL 的字段 :对于数据为 NULL 的字段,数据库较难优化。
  • 区分度高的字段。
  • 被频繁更新的字段应该慎重建立索引。
  • 索引尽可能简洁,避免冗余索引
  • 尽可能的考虑建立联合索引而不是单列索引。

四、MySQL锁

1.锁类型/级别

MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。

  • 表级锁:锁定粒度大,锁冲突概率高;开销小,加锁快;不会出现死锁。主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎使用
  • 行级锁:锁定粒度小,锁冲突概率低;开销大,加锁慢;会出现死锁。主要是InnoDB存储引擎使用;
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般,主要是BerkeleyDB存储引擎使用。

适用:表级锁更适合于以查询为主,有少量按索引更新的应用;而行级锁则更适合于有大量按索引更新数据,同时又有并发查询的应用。

注:InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。

2.共享锁和排它锁

不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:

  • 共享锁(S 锁) :读锁,允许多个事务同时获取(锁兼容)。
  • 排他锁(X 锁) :写锁/独占锁,不允许多个事务同时获取。

排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。

S 锁 X 锁
S 锁 不冲突 冲突
X 锁 冲突 冲突
# 共享锁
SELECT ... LOCK IN SHARE MODE;
# 排他锁
SELECT ... FOR UPDATE;

注:由于 MVCC 的存在,一般的 SELECT 语句,InnoDB 不会加任何锁。

3.意向锁

3.1 作用

用来快速判断是否可以对某个表使用表锁(没意向锁的话需要判断有没有行锁,得一行行遍历)。

3.2 意向锁分类:

  • 意向共享锁(Intention Shared Lock,IS 锁):事务有意向对表中的某些加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
  • 意向排他锁(Intention Exclusive Lock,IX 锁):事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的 IX 锁。

注:意向锁由数据引擎自己维护,用户无法手动操作。

这四种锁的共存逻辑关系:

注:这里的锁都是说明的表级别的锁兼容关系,不涉及到行(这里的X、S都是表级锁)。意向锁不会与行级的共享锁和排他锁互斥。

比如:要加意向排它锁,如果发现表已经存在共享锁(表级别)或者排它锁(表级别),那么就会加锁不成功。

又比如:一个表已经被加了意向排它锁(证明这个表中的某行数据正在被修改),那么此时加表级别共享锁S和排它锁X都会冲突,但是加其他意向锁不会冲突,因为加了意向锁,只是代表我要修改具体某行的数据,不一定会和现有的行冲突,而且就算有冲突,应该会在后续对行加锁时可以检测出来,但是并不影响意向锁的使用。上表的关系是用来表示意向锁和表级S、X锁的关系。

 

4. InnoDB 行锁分类

  • 记录锁(Record Lock) :也被称为记录锁,属于单个行记录上的锁。
  • 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。
  • 临键锁(Next-key Lock) :Record Lock+Gap Lock,锁定一个范围,包含记录本身。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。

5. 快照读和当前读

快照读(一致性非锁定读) :利用 MVCC 机制,读取的是记录的历史版本,也就是快照。普通的select语句。

当前读 (一致性锁定读): 就是给行记录加 X 锁或 S 锁。select for update

当前读常见 SQL 语句:

# 对读的记录加一个X锁
SELECT...FOR UPDATE
# 对读的记录加一个S锁
SELECT...LOCK IN SHARE MODE
# 对修改的记录加一个X锁
INSERT...
UPDATE...
DELETE...

五、日志

1.三大日志

binlog:事务日志

redo log:重做日志

undo log:回滚日志

2.redo log和binlog

2.1 InnoDB 的 redo log细节

简单说就是固定大小的一块地址,可以循环写,有两个指针,一个代表写入,一个代表擦除(持久化到DB了)

注:只要redolog有了,那么数据就不会丢了。

2.2 redo log和binlog区别

  • redo log 是 InnoDB 引擎特有的,而binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
  • redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
  • redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

2.3 redo log和binlog协助处理流程

使用两阶段提交。先写redo log为prepare,后写binlog,然后更新redo log为commit状态。

注:提交事务先写binlog,提交redo log。如果commit阶段宕机,MySQL会检测如果有binlog,那么redo log继续提交,如果没有binlog,那么就会回滚该事务。

3.binlog

3.1 binlog的格式

  • statement : 基于sql语句的模式。update table set name =””; effect row 1000; uuid、now() other function   (默认模式)(如果对于now()函数这种问题,statement模式可能导致主从对应字段的值不一致)
  • row: 基于行模式; 存在1000条数据变更;  记录修改以后每一条记录变化的值(可以保证每一条数据一致,但是记录数据太多)
  • mixed: 混合模式,由mysql自动判断处理(对于now()函数这种自动会用row模式)

3.2 主从同步的原理

1.   master记录二进制日志。在每个事务更新数据完成之前,master在二日志记录这些改变。MySQL将事务串行的写入二进制日志,即使事务中的语句都是交叉执行的。在事件写入二进制日志完成后,master通知存储引擎提交事务

2.   slave将master的binary log拷贝到它自己的中继日志。首先,slave开始一个工作线程——I/O线程。I/O线程在master上打开一个普通的连接,然后开始binlog dump process。Binlog dump process从master的二进制日志中读取事件,如果已经跟上master,它会睡眠并等待master产生新的事件。I/O线程将这些事件写入中继日志

3.   SQL线程从中继日志读取事件,并重放其中的事件而更新slave的数据,使其与master中的数据一致

3.3 主从同步延迟产生原因

  • 当master库tps比较高的时候,产生的DDL数量超过slave一个sql线程所能承受的范围,或者slave的大型query语句产生锁等待
  • 网络传输: bin文件的传输延迟
  • 磁盘的读写耗时:文件通知更新、磁盘读取延迟、磁盘写入延迟

3.4 主从同步延迟解决方案

a.在数据库和应用层增加缓存处理,优先从缓存中读取数据(应用层在master写入数据的时候,把新数据缓存,等到缓存失效的时候,可能从库已经同步了数据)

b.减少slave同步延迟,可以修改slave库sync_binlog属性;

sync_binlog=0  文件系统来调度把binlog_cache刷新到磁盘

sync_binlog=n

c.增加延时监控

4.undolog

在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的。undo log在事务开始修改之前会记录当前的值。

六、分库分表&分布式事务

1.分库分表

1.1 跨库join问题

2. X/OpenDTP事务模型

X/Open Distributed Transaction Processing Reference Model

X/Open是一个组织机构,定义出的一套分布式事务标准, 定义了规范的API接口

 

2PC(two -phase-commit), 用来保证分布式事务的完整性

J2EE 遵循了X/open DTP规范,设计并实现了java里面的分布式事务编程接口规范-JTA

XA是X/Open DTP定义的中间件与数据库之间的接口规范。 XA接口函数由数据库厂商提供

 

2.1 X/OpenDTP 角色

AP application  具体的应用 比如上图的库存中心、订单中心

RM resouces manager   资源管理器。 数据库

TM transaction manager  事务管理器,事务协调者

3. 2PC(two -phase-commit)

(CAP:CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼)

3.1 事务过程

3.1.1 阶段一:提交事务请求(投票)

1.TM向所有的AP发送事务内容,询问是否可以执行事务的提交操作,并等待各个AP的响应

2.执行事务

各个AP节点执行事务操作,将undo和redo信息记录到事务日志中,尽量把提交过程中所消耗时间的操作和准备都提前完成后确保后续

事务提交的成功率

3.各个AP向TM反馈事务询问的响应

各个AP成功执行了事务操作,那么反馈给TM yes的response;如果AP没有成功执行事务,就反馈TM no的response

3.1.2 阶段二:执行事务提交

执行提交事务

假设一个事务的提交过程总共需要30s, 其中prepare操作需要28(事务日志落地磁盘及各种io操作),而真正commit只需要2s

那么,commit阶段发生错误的概率和prepare相比, 2/28 (<10%) .只要第一个阶段成功,那么commit阶段出现失败的概率就非常小

大大增加了分布式事务的成功概率

3.1.3 中断事务提交

3.2 2pc存在的问题

  1. 数据一致性问题
  2. 同步阻塞

4. 3PC(three phase commit)

阶段一:canCommit      询问是否可以提交

阶段二:preCommit      进行预提交,类似于2pc中的预提交

阶段三:doCommit        提交

 

4.1 改进点

  1. 增加了超时机制
  2. 第二阶段,如果协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令
  3. 第三阶段,如果参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于概率)

4.2 3pc的问题

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

 

2.1实现方式

2.2 事务悬挂

 

 

MySQL实战学习总结

一、MySql架构

1.redo log和binlog

  • redo log 是 InnoDB 引擎特有的,而binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
  • redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
  • redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

2.redo log和binlog协助处理流程

使用两阶段提交。先写redo log为prepare,后写binlog,然后更新redo log为commit状态。

3.InnoDB 的 redo log细节

简单说就是固定大小的一块地址,可以循环写,有两个指针,一个代表写入,一个代表擦除(持久化到DB了)

注:只要redolog有了,那么数据就不会丢了。

 

4.InnoDB 的索引模型

InnoDB 使用了 B+ 树索引模型,每一个索引在 InnoDB 里面对应一棵 B+ 树
左边为主键索引(聚簇索引),主键索引叶子节点存储的是整行数据;右边为非主键索引(普通索引、二级索引),叶子节点存储内容是主键的值。
使用主键索引查询可以直接获得数据,而使用普通索引需要先搜索得到主键的值,然后再根据主键查询主键索引得到数据(这个过程叫回表)。

 

Spring

一、Spring三大特性

IOC控制反转、AOP面向切面编程、DI依赖注入。

1.IOC控制反转

将创建对象的权利交给Spring来进行处理,可以减低计算机代码之间的耦合度。

  • 作用:解耦(减低程序间的耦合性)。
  • 优点:解耦,降低程序间的依赖关系;
  • 缺点:使用反射生成对象,损耗效率。

注:最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。

2.AOP面向切面编程

将纵向重复的代码(公共行为和逻辑)横向抽取出来并封装为一个可重用的模块,这个模块就是“切面”(Aspect)。简单的说就是将程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在每个流程里面都能执行到,而不用重复开发代码。

  • 优点:减少重复代码;提高开发效率;维护方便。
  • 主要用于:权限认证、日志、事务处理、埋点等。

3.DI依赖注入

  • 创建对象实例时,为这个对象注入对应属性值。
  • Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

 

二、Spring MVC 和Spring boot

都属于spring这个轻量级java开发框架。springMvc属于WEB开发的MVC框架,包含模型、前端视图、控制器(也就是对应逻辑)。springBoot框架相对于springMvc框架来说, 更专注于开发微服务后台接口,不开发前端视图,简化了配置流程,不需要配置xml等。

Java基础知识

一、JAVA面向对象的三大特征

封装、继承、多态

1.封装:

隐藏实现细节,提高代码的复用性,提高了安全性。

2.继承:

多个事物直接有共同的属性和行为放到父类中,特有的放在子类中,子类可以继承父类的属性和行为。通过extends关键字。

注:JAVA只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。但可以多层继承。子类访问父类的成员变量通过super关键字。

  • 优点:可以继承父类的特征
  • 缺点:如果多层父类有相同名字的实例变量或者相同的方法,调用可能会产生歧义,不知道用的哪个父类的。

3.多态:

子类的对象放在父类的引用中,如 Animal a=new Cat(),子类的对象当父类对象来用。

优点:提高了程序的扩展性和复用性

缺点:通过父类引用操作子类对象时,只能用父类中有的方法,不能操作子类特有的方法。

注:多态的前提:1、必须有关系:继承、实现  2、通常都有重写的操作

3.1 向上转型:

当父类的引用指向子类的对象时,就发生了向上转型,即把子类类型转成了父类类型。

优点:隐藏了子类类型,提高了代码的扩展性

弊端:只能使用父类的内容,无法使用子类特有的功能

3.2 向下转型:

当要使用子类特有的功能时,就需要使用向下转型

优点:可以使用子类的 特有功能

弊端:需要面对具体的子类对象时,向下转型时容易发生ClassCastException类型转换异常。所以转型前必须要做判断。

3.3 对象的强制转换:

格式: 引用 instanceof 类型 判断当前对象是否是引用类

用法: Animal a1=new Dog();

if( !Cat instanceof a1){ //判断当前对象是不是Cat类型

}

Cat d=(Cat)a1;

二、java异常

1.异常

Java主要分为Error错误Exception异常两类。Error(错误)是程序无法处理的错误,表示运行应用程序中较严重问题。Exception(异常)是程序本身可以处理的异常。Exception(异常)又包含运行时异常非运行时异常(编译异常)

  • Error:OOM等
  • 运行时异常:ArrayIndexOutOfBoundException数组越界、NullPointerException空指针等。
  • 非运行时异常:如IOException IO异常。

2.可查的异常和不可查异常

Java的异常(Throwable)分为可查的异常(checked exceptions)不可查的异常(unchecked exceptions)

 

  • 可查异常必须要去处理,否则编译不通过,如IOException和ClassNotFoundException等。

处理异常方法:要么用try-catch语句捕获它,要么用throws子句声明抛出它。

  •  不可查异常(编译器不要求强制处置的异常)包括运行时异常和错误,不用捕获对应异常,应该找出错误程序进行修改。

三、基础知识点汇总

1.java中extends和implements

extends是继承类,implements是实现接口。 类只能继承一个,接口可以实现多个。
extends继承父类的时候可以重写父类的方法,implements实现接口,必须实现接口的所有方法。

abstract class A {
    abstract m(): void;
}


class B extends A{
}

class C implements A {
    m(): void { } //必须要实现定义在A中的所有方法
}

 

2.基本类型和包装类型

2.1 区别

  • 包装类型对应变量default值是 null ,而基本类型有默认值且不是 null
  • 包装类型可用于泛型,而基本类型不可以。
  • 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,几乎所有对象实例都存在于堆中。
  • 相比于对象类型, 基本数据类型占用的空间非常小。

2.2 装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

举例:

Integer i = 10;  //装箱
int n = i;   //拆箱

3.接口和抽象类

3.1 共同点 :

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

3.2 区别 :

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

4.深拷贝、浅拷贝、引用拷贝

浅拷贝、深拷贝、引用拷贝示意图

  • 引用拷贝:两个不同的引用指向同一个对象
  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),对象内部的属性是引用类型的话,指向的是同一地址。
  • 深拷贝 :深拷贝会完全复制整个对象。

5.== 和 equals()

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

 ==:比较的是值是否相等(基本类型比较值,引用类型(对象)比较的是内存地址)

equals():只能用来判断对象,不能用于基本数据类型。不重写等同于==,重写可以自定义。

6.String、StringBuffer、StringBuilder 的区别

  • String :里面的对象是不可变的,线程安全。适用于操作少量数据
  • StringBuffer :线程安全。适用于多线程操作大量数据。
  • StringBuilder:非线程安全的。适用于单线程操作大量数据。

四、反射

赋予了我们在运行时分析类以及执行类中方法的能力。通过反射可以获取任意一个类的所有属性和方法。

优点:代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

缺点:不安全(比如无视泛型参数的安全检查),性能会变差。

应用场景:业务场景使用较少,框架使用较多。像 Spring/Spring Boot、MyBatis这些框架, Spring 里面的注解,都用了反射机制。里面也用了动态代理,动态代理的实现也依赖反射。

五、集合

1.java集合框架

两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。

2. List, Set, Queue, Map 四者的区别

List: 有序、可重复。
Set: 无序、不可重复。
Queue: 排队,有序、可重复。一般用于排队功能的叫号机。
Map: 使用键值对(key-value)存储。

List

  • ArrayList: Object[] 数组
  • VectorObject[] 数组
  • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

Set

  • HashSet(无序,唯一): 基于 HashMap 实现,底层采用 HashMap 来保存元素
  • LinkedHashSet: 通过 LinkedHashMap 实现。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

Queue

  • PriorityQueueObject[] 数组来实现二叉堆
  • ArrayQueueObject[] 数组 + 双指针

Map

  • HashMap: 数组+链表组成,链表长度大于阈值(默认为 8)会出现红黑树转换。
  • LinkedHashMap: 继承自 HashMap,增加了一条双向链表,可以实现顺序访问。
  • Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)

注:上图是LinkedHashMap的存储结构,在hashMap的基础上多了一个双向链表,可以实现顺序访问。

3.区别

3.1 ArrayList 和 Vector :

  • ArrayList 是 List 的主要实现类,底层使用 Object[ ]存储,线程不安全 ;
  • Vector 是 List 的古老实现类,底层使用Object[ ] 存储,线程安全的。

3.2 ArrayList 与 LinkedList:

  • 数据结构:一个数组一个链表数组。LinkedList 是 双向链表 (JDK1.6 之前为循环链表,JDK1.7 取消了循环)
  • 访问速度:数组可以快速随机访问,链表不行
  • 更新:链表对中间数据插入较友好好。
  • 线程安全:都不是线程安全的;
注:很少使用LinkedList

3.3 HashSet、LinkedHashSet 和 TreeSet

  • 都是 Set 接口的实现类,元素唯一,并且都不是线程安全的。
  • HashSet 用于不需要保证元素插入和取出顺序的场景,LinkedHashSet 用于 FIFO 的场景,TreeSet 用于元素需要自定义排序场景。

3.4 Queue 与 Deque 

  • Queue :单端队列,一端入,另一端出,一般遵循 先进先出(FIFO) 规则
  • Deque :双端队列,在队列的两端均可以插入或删除元素。

3.5HashMap 和 Hashtable

  • HashMap 非线程安全,Hashtable 是线程安全的(内部方法基本都经过synchronized 修饰)。
  • HashMap 要比 Hashtable 效率高一点。

注:基本不会用Hashtable,要线程安全就用ConcurrentHashMap

3.6ConcurrentHashMap在jdk1.7和1.8的区别

  • 数据结构:1.7是Segment 数组 + hashMap(HashEntry 数组 + 链表),1.8是 Node 数组 + 链表 / 红黑树。相当于原来是Segment和hashMap的数组是映射关系,现在合并了。
  • 锁1.7是Segment 分段锁,针对Segment(段/槽)加锁,1.8后锁的力度更细了,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。
  • 1.7的分段锁继承自 ReentrantLock。1.8 放弃了分段锁设计,采用 Node + CAS + synchronized 保证线程安全。
  • 并发度 :JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大

3.7 HashMap实现

默认大小16,负载因子0.75,超过对应阈值会扩容。

put过程:先计算key的hash值,然后低16位和高16位做异或(充分利用每一位),然后与数组长度-1做与操作确定数组的位置,然后在链表最后插入对应的节点(如果已经有相同的key会替换)。如果超过8则会有红黑树的转换。

六、并发编程

1.进程和线程

进程:是系统运行程序的基本单位。一个进程在其执行的过程中可以产生多个线程。

线程:是一个比进程更小的执行单位。同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。多线程之间切换负担要比进程小得多。

2.并发与并行的区别

:两个及两个以上的作业在同一 时间段 内执行。
:两个及两个以上的作业在同一 时刻 执行。

3.同步和异步的区别

同步 : 发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
异步 :调用在发出之后,不用等待返回结果,该调用直接返回。

4.线程的生命周期和状态

5.sleep() 方法和 wait() 方法

  • 都可以暂停线程的执行。
  • sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
  • sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法(本质是每个对象都有对象锁,要释放当前线程占有的对象锁,所以操作的是对象而不是线程)。
  • wait() 需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法唤醒。sleep()方法执行完成后,线程会自动苏醒。

6.可以直接调用 Thread 类的 run 方法吗

可以但不会以多线程的方式执行,用当前线程执行的。

new 一个 Thread后,需要调用 start()方法,start()方法作用是启动一个线程处于就绪状态,当分配到时间片后就可以开始运行,这时候该线程会自动执行 run() 方法进行执行,这样就是多线程了。省略了start,那么就没有额外的线程,会用当前线程执行。

7.volatile

作用:保证变量的可见性,禁止指令重排。

7.1 如何保证变量可见性

volatile修饰的,不会有在工作内存有变量副本,线程直接从主内存取。

7.2 如何禁止指令重排

单例模式(线程安全) :

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

 uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

JVM 的指令重排可能导致执行顺序不是1->2->3,而是 1->3->2。这样再多线程情况下,1->3的时候,另一个线程读到的就不是null了。

8.synchronized

8.1 作用域

修饰方法

修饰对象:根据修饰的对象不同可以分为全局锁(xxx.class)或者代码块.

8.2 底层原理

通过 JDK 自带的 javap 命令,可以查看java字节码.class信息,发现synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令;synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。不过两者的本质都是对对象监视器 monitor 的获取。

8.3 锁升级

偏向锁、轻量级锁、重量级锁

用synchronized修饰的会有锁升级过程,如果只有一个线程获取锁,那么这个锁会偏向这个线程,叫做偏向锁;如果这时候有其他线程也来获取这个锁,那么这个锁会变成轻量级锁,获取不到锁的线程会自旋,然后重新获取;如果还是获取不到,则会变为重量级锁,获取不到的线程会阻塞(锁池状态)。

8.4 synchronized 和 ReentrantLock 的区别

  • 两者都是可重入锁
  • synchronized非公平锁,ReentrantLock默认也是非公平锁,不过可以有参数可以配置变成公平锁
  • synchronized 依赖于 JVM , ReentrantLock 依赖于 API(JDK 层面实现),需要lock() 和 unlock() 方法配合 try/finally 语句块来完成
  • ReentrantLock功能更多,如可以变成公平锁

9.ThreadLocal

为了实现每个线程都有自己的专属本地变量。通过创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,从而避免了线程安全问题。

9.1 ThreadLocal 原理

// 源码:
public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
}

ThreadLocal内部有一个ThreadLocalMap,每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

10.线程池

10.1为什么使用线程池

  • 降低资源消耗:可以重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,用已有线程可以直接执行。
  • 方便管理:统一创建,分配,调优和监控。

10.2 实现 Runnable 接口和 Callable 接口的区别

  • Runnable自 Java 1.0 以来一直存在,Callable在 Java 1.5 中引入
  • Runnable 接口不会返回结果或抛出检查异常,但是 Callable 接口 可以。所以如果任务不需要返回结果或抛出异常就使用 Runnable 接口 ,看起来更简洁。

10.3  execute()方法和 submit()方法的区别

  • execute()方法:没有返回值,无法判断任务是否被线程池执行成功;
  • submit()方法:线程池会返回一个 Future 类型的对象,可以判断是否执行成功。此外可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成。

10.4 四类线程池

CachedThreadPool: 可根据实际情况动态调整线程数量的线程池。

FixedThreadPool : 固定线程数量的线程池。

SingleThreadExecutor: 只有一个线程的线程池。

ScheduledThreadPool:调度类的线程池。

 

10.4.1 几个核心参数:

  • corePoolSize : 核心线程数。
  • maximumPoolSize : 最大线程数。
  • workQueue: 工作队列。
  • keepAliveTime:非核心线程(临时线程)存活时间;
  • handler :饱和策略。主要有报错、丢弃当前任务、丢弃最早未处理的任务、用提交任务的线程执行这四种。

10.4.2 大致流程

进来任务时,用核心线程做任务;如果任务超过核心线程数,则放入队列;如果任务还在增加,则创建临时线程执行,直到最大线程数;如果还在增加,则按照对应的饱和策略处理。

11.JUC 包中的4大Atomic原子类

基本类型:使用原子的方式更新基本类型

  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类

数组类型:使用原子的方式更新数组里的某个元素

  • AtomicIntegerArray:整型数组原子类
  • AtomicLongArray:长整型数组原子类
  • AtomicReferenceArray:引用类型数组原子类

引用类型:

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新带有版本号的引用类型。
  • AtomicMarkableReference :原子更新带有标记位的引用类型

对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器

12.AQS

AQS 的全称为(AbstractQueuedSynchronizer),是一个用来构建锁和同步器的框架。如: ReentrantLock,Semaphore信号量等都是用的AQS框架。

12.1 AQS 原理

通过ReentrantLock加锁说明:简单的说就是,AQS对象内部有一个volatile的int变量,初始state的值是0,一个线程要来加锁时,通过CAS把0改为1,代表加锁成功,释放锁就是再减回去,减到0就代表锁释放了。

另外AQS内部还维护着一个先进先出的等待队列,如果其他线程也来加锁,但是因为锁已经被其他线程占用而加锁失败,就会存放这些加锁失败的线程。

 

注:CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

12.2 AQS 组件总结

  • ReentrantLock:一次只允许一个线程访问某个资源,可重入。
  • Semaphore(信号量)-允许多个线程同时访问问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
  • CountDownLatch (倒计时器): 可以让某一个线程等待直到倒计时结束,再开始执行。
  • CyclicBarrier(循环栅栏): 让一组线程到达栅栏时被阻塞,直到最后一个线程到达时,栅栏被推倒,所有线程开始执行。

七、反射

1.概念

可以在运行时分析类以及执行类中方法,通过反射可以获取和调用对应类的属性和方法。

 像Spring/Spring Boot、MyBatis 等框架中都大量使用了反射机制,动态代理也是基于反射。

2.优缺点

  • 优点 : 更加灵活、为各种框架实现对应功能提供了便利。
  • 缺点 :无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。性能也会稍差些。

3.获取 Class 对象的四种方式

3.1. 知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;

3.2. 通过 Class.forName()传入类的全路径获取:

Class alunbarClass1 = Class.forName("cn.test.TargetObject");

3.3. 通过对象实例instance.getClass()获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

3.4. 通过类加载器xxxClassLoader.loadClass()传入类路径获取:

ClassLoader.getSystemClassLoader().loadClass("cn.test.TargetObject");

八、语法糖

1.概念:

计算机语言中的某种特殊语法,方便程序员使用。可以让程序更加简洁,有更高的可读性。

注:反编译,可以用java自带的javap,对.class文件进行反编译,可读性稍差。有一些网站可以把字节码反编译成对应的代码。所以查看语法糖依赖反编译。

2.常见语法糖

  • switch 支持 String 与枚举:java7开始支持switch string,正常switch对比的是基本数据类型的值或者ascii码。switch string,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查。
  • 泛型:编译阶段通过类型擦除的方式进行解语法糖。
  • 自动装箱与拆箱:装箱通过调用包装器的 valueOf 方法实现,而拆箱通过调用包装器的 xxxValue 方法实现的。
  • 可变长参数:把可变长参数变成了一个数组,方法调用传的是数组。

注:在编译阶段,如果有一些代码根本不会走到(废代码),那么编译器会不进行编译。

 

九、jvm内存模型

十、垃圾回收

1.垃圾回收算法

1.1 标记-清除算法

该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

  • 效率问题
  • 空间问题(标记清除后会产生大量不连续的碎片)

1.2 标记-复制算法

将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。

注:新生代采用标记-复制算法

1.3 标记-整理算法

先标记,然后让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

注:老年代有使用标记-整理算法

1.4 分代收集算法

根据对象存活周期的不同将内存分为几块。分为新生代和老年代。新生代朝生夕死的多,所以用标记-复制算法。老年代存活几率比较高,没有额外的空间来对它进行担保,所以用标记-清除或者标记-整理算法。

2.分代回收细节

分为新生代、老年代和永久代(1.8后改成meta space),新生代里面有一个Eden区,两个Survivor区,每次回收时,复制Eden区和Survivor区存活对象到另外一个Survivor区,如此反复。如果超过15次还存活的对象,则进入老年代。

注:大的对象直接进入老年代,如大的数组、字符串等。

注2:对象一般在Eden区分配,当 Eden 区没有足够空间进行分配时,将会进行 Minor GC

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):对老年代进行垃圾收集。
  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
  • 整堆收集 (Full GC):收集整个 Java 堆和方法区。

3.死亡对象判断

3.1 引用计数法

对象有一个引用计数器,每次被引用,计数器加 1,引用失效,计数器就减 1;为 0 的对象可以被回收。

优点:效率高

缺点:无法解决循环引用的问题

3.2 可达性分析算法

通过根节点 “GC Roots”向下搜索,当一个对象到 GC Roots 没有任何引用链相连的话,就可以被回收。

可以作为 GC Roots 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

4. 引用类型

4.1.强引用(StrongReference):

我们平常使用的Object ob = new Object(),内存不足抛出 OOM,只有ob=null,才会回收。

4.2.软引用(SoftReference)

内存空间足够,就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

4.3.弱引用(WeakReference)

垃圾收集器只要扫到,都会回收。

4.4.虚引用(PhantomReference)

被回收的时候会有一个通知,主要用来跟踪对象被垃圾回收的活动。

十一、高cpu占用优化

1.背景:

saas服务启动时,cpu占用太高。

2.过程:

  1. 首先确定CPU占比高的进程,线程ID。通过windows提供的工具Process Explorer可查到saas服务的进程ID(PID), 以及内部线程占用cpu情况。实时显示选中的进程内部线程ID,占用CPU情况。 操作saas,记录下占比CPU比较高的线程ID。
  2. 通过Jstack工具查看进程内部的线程执行情况: jstack -l PID > XXX.stack .
  3. 通过线程ID在jstack文件中找到消耗cpu比较大的线程为:C2 CompilerThread0 .在server模式下启动jar包后默认是开启tiered compiler对javac产生的字节码进行优化,这个线程消耗资源比较多。
  4.  用 -client 模式启动jar包后, 初步看内存占用从400+MB降到200+MB,CPU占比高的持续时间明显降低。

3.分析:

因为我们都知道JIT( just in time ), 也就是即时编译编译器。使用即时编译器技术,能够加速 Java 程序的执行速度。主要有Server 模式和 client 模式两种启动模式,主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。