为了保证制作简历的安全性和流畅性,建议您使用Chrome浏览器进行访问
老丛
河北工业大学·2022届

那么多人想去金融行业??你们真的了解它嘛...

因为有在金融行业多个大型企业的实习与工作经历,因此对金融行业各层次的就业情况有大概的了解,总体而言,金融行业的岗位和平台的待遇差距很大,即使同是应届生,如果在同一个公司的不同岗位,那待遇薪酬也会差数倍,可能前5%的薪酬高于后95%的薪酬。 一、本科生 毕业去向: (1) 主流去向包括银行、证券公司、P2P性质企业、保险公司、期货公司、担保公司等,主要岗位包括柜员、客户经理、理财经理、业务员等; (2) 水平高些的本科生,可以去银行的管培、四大会计事务所、华为的财经岗位、金融国企; (3) 即使是毕业于清北复交人,能本科毕业直接去证券公司、基金公司、银行、资产管理公司的投研岗位的也极少。 能力要求: 本科生找工作时,大部分企业对本科生工作能力的要求,除了靠谱踏实以外,更看重其销售、沟通交流、找资源的能力。 二、研究生 毕业去向: (1) 除了可能依旧去上面这些本科生的岗位以外,还可能去银行/券商/基金公司的的投研、管理岗位,具体情况因研究生毕业学校以及个人能力而异;(2) 去券商、基金、投行的投研岗位的人数总体而言还是偏少的,这些岗位对院校背景要求也较高。 (3) 还有部分财经硕士会去考公务员、实体企业、外资行,但这部分总体还是占比很小。 能力要求: 靠谱踏实的性格、良好的沟通交流能力肯定还是要的,除此之外,对研究分析能力、写报告的能力也会有要求。比如券商的研究报告,大部分都是给刚毕业入职两年内的员工写的。 总体而言,金融行业,首先,家庭资源、名校背景、研究生学历这个肯定很关键;其次,良好的实习经历和高含金量的证书(个人觉得是CPA)对职业生涯会有很大帮助,实习和考证学到的东西,会比课堂上学到的,更贴近实践操作很多。 三、具体就业去向(硕士) 下面我就根据自己的实习工作经历及同学的交流,谈谈目前经济金融硕士的就业情况。 经济金融硕士的就业非常多元化,既有去主流的银行、券商、基金、信托等金融机构,也有去银保监会、证监会、国开行、公务员等体制内的岗位,还有部分去华为、苏宁、大疆、腾讯、网易等实体企业。 (1)金融类监管机构,主要是中央(人民)银行、银保监会、证监会、政策性银行 总部录取的难度较大,省市分部会好进不少,具体待遇因城市而异。比如在国开行深圳分行第一年也有20+W(含年终),但在一些二线城市,则是拿着公务员的工资,干市场化机构的活。工作强度总体中等,可以积攒体制内资源人脉。 (2)商业银行,包括四大行和股份制商行、城市商业银行 银行的岗位种类较多,研究生的就业主要集中于管培生、资管、投行、金融市场部等部门。大部分管培生都需要去轮岗2年左右,但以后就是银行的骨干精英;资管部门,负责发理财产品配置资产;银行的投行部门更侧重于债券承销;金融市场部门,名利双收的三高岗位(高收入、高起点、高地位)。总行相比分行的进入难度会更大,当然平台和待遇会好很多,例如四大行的总行是可以解决北京户口的!! (3)证券公司、基金管理公司、信托投资公司、金融控股集团等金融公司 (1)券商:热门岗位主要是行业研究、投行部门。投行和行研的工作强度都很大,投行偏向体力活,经常出差熬夜赶材料;行研是脑力体力兼具,不仅要绞尽脑汁写出所在行业的深度研究报告,作为卖方也需要较强的勾兑能力。前十券商的base一般是月薪1.5左右,主要看年终奖,碰到一波牛市,三五年一线城市买房不是梦!去年股市行情太差,券商研究员招的极其少,今年名额更多不少。 (2)基金:热门岗位主要分研究员和交易、风控等部门。基本工资比券商高,作为买方研究员,进入的难度会更高,大部分本科要求清北复交,当了基金经理的话,碰到一波牛市,也是靠海别墅的节奏。(3)信托公司:总体待遇和福利都很可观,竞争也是挤得头破血流。(4)金融控股集团:有民企巨头成立的,如苏宁、万达等,也有国资背景的如越秀金控、杭州金控。 (4)四大资产管理公司 根据我周边小伙伴的反馈情况,我的感觉是五个字:闷声发大财!!工作强度总体不小,但如果跟到好的团队,做出业务,年终奖会很高。举个栗子,我认识的小伙伴在四大资管之一,目前月薪税前18k左右,第二年年终发了15w左右,当然工作强度不比投行低。 (5)VC、PE、FA、实体企业的战略投资部门 工作内容主要是做行业分析,搜寻优质创业公司,协助领导做投资决策。是不是感觉很高大上!但刚毕业就做VC、PE,学习曲线总体较慢,并且奖金主要来自投资项目的退出。适合有工作经验或者产业背景的人,个人建议应届毕业生慎重选择VC/PE。 (6)读博深造 读博的特点是“回报周期长、回报率高”,举个栗子,很多省市的人才引进,给清北博士的待遇是副处级。认识一个北大金融博士,毕业直接去某二线城市当金融集团副总,不到30岁。当然普通博士的待遇也都是20+万起步! (7)外资行 包括摩根、高盛、德银等外资,待遇极好,也非常难进,本科基本要求清北复交,第一年50-100w之间,一线城市买房压力不大。 (8)其他就业 主要包括公务员、选调生、省市的人才引进、华为财经人员、网易、腾讯、苏宁、大疆管培生等。 四、薪酬待遇 至于薪酬待遇方面,贫富差距很大,可能前5%的人拿得工资比后95%的都多(所以我比较建议,如果本科毕业时没找到好的岗位,去读个两年的专业硕士)。举个例子吧,同在中信证券工作,如果是做投行的话,今年提了工资已经涨到2万元一个月,年终另算,当然工作强度非常大,周末也在加班。但如果你在中信的营业部做客户经理,我估计一个月也就5000-6000吧。 这是今年披露的行业薪酬,当然,关于平均薪酬嘛你懂的,你和马云家产平均下,也有百亿美元,所以我觉得平均薪酬再除以2,可能比较接近中位数。
分享
评论
先马后看
SIMON
中央民族大学·2022届

[社招]商汤科技测试开发岗面经总结

背景: 楼主18年本科毕业,大概一年半工作经验。 之前工作也是测试岗,离职了三个多月再次刷题面试,大概花了一个月准备+面试,现在已经入职新公司。 趁着有空赶紧补一下之前面试的一些总结,可能有些内容有遗漏,大概传达意思没问题就行。大家觉得不错的可以帮忙顶一下哈~ 一面(技术面): 1.       自我介绍 2.       对商汤科技了解多少?(通过朋友了解,在大学对这方面也感兴趣,也了解过) 3.       对人工智能了解多少?对人工智能的产品了解多少?(人工智能就是机器学习+深度学习,具体讲了大学做过的人脸识别项目,和申请的专利和论文) 4.       讲自己学习过图像处理相关知识,开发过相关项目。 5.       讲一下你做过的项目? 6.       你的自动化测试平台是怎么做的?为什么叫MVC框架,怎么个前后端分离法,解释一下?为什么用这个框架呢? 7.       讲一下你对Django的了解,优点和缺点是什么? 8.       除了用过django外,对python的其他框架了解过吗? 9.       你们做的自动化都是基于QTA框架吗?具体讲一下? 10.   对linux了解吗?问你几个小问题?怎么查进程,怎么查文件?(ps,find) 11.   对docker了解吗?docker的优点是? 12.   对k8s了解吗?是什么来的? 13.   自己的项目中用过docker开发,讲讲具体是怎么做的? 14.   平时用什么语言?python,问你几个问题? 15.   Python,进程&线程&协程的了解?线程是串行还是并行?ORM了解吗?REFUL API了解吗? 16.   对测试的持续集成ci有了解吗?是怎么做的? 17.   性能测试做过吗?怎么做的,用什么框架做的? 18.   软件测试的流程?(需求评审->用例设计->环境部署->测试执行->bug回归->发布->验收) 19.   如何提高测试点的覆盖率?(测试用例评审?) 20.   给你一道题,设计测试用例?怎么测试系统十万的承载量? 21.   你有什么想问我的?(做什么业务的,测试+开发?我有什么可以提升和改进的?) 面试总结: 1.       用小鱼易连远程视频面试的,没有IDE平台所以这次没有手写代码。 2.       需要改进的地方:python的技术深度和广度需要加深,测试精度,性能,稳定性,持续集成需要加深。 二面(技术面): 1.       讲一下做过的工具 2.       讲一下之前的业务和研发流程 3.       如何做测试的,case是怎么写的?从整体上阐述流程? 4.       测试过程中遇到的风险问题怎么解决?遇到过那些QA问题,又是如何解决的?比如需求变更怎么办 5.       商汤了解多少? 6.       人工智能了解多少?了解人工智能的那些产品? 7.       阐述一下模型是什么? 8.       工作上的要求?技术只是一种工具,测试效率提升需要依赖工具。 9.       测试方案是怎么看的?质量+效率? 10.   K8s了解吗? 11.   工作中有做过什么方面的文档输出呢? 12.   你有什么想问我的?我就大概了解了下项目到时候所做的业务 大概就是算法模型的测试。要求正式测试能独档一面,测试团队需要阶段性成果输出->ui接口设计->模型效果->数据集整理等。 面试总结:面试官是测试负责人,最后一轮技术面了。 三面(领导面): 1.       自我介绍 2.       AI产品测试了解多少讲一讲。 3.       工作地点?能不能接受出差,比如北京,上海,深圳,出差半年到一年 4.       没什么问题了,你的问题要问我? 5.       需要提升和改进?大概就是AI基础方面。 面试总结:全程大概20分钟 四面(HR面试): 1.       HR大概讲一下所在的事业群是智慧城市事业群。 2.       问我之前在腾讯是在那个事业群,工作内容是什么。 3.       问之前在哪实习。 4.       问之前公司工作体验?之前公司的培训制度和商汤的培训制度 5.       你最欣赏的同事是?为什么 6.       职业规划 7.       之前加班情况如何?对加班怎么看 8.       现在有哪些offer? 9.       问我拿的offer的薪酬多少,问我之前的薪酬多少? 10.   你有什么要问我的?(我大概就问了面试官的评级,问定级,问晋升机制,问薪酬架构,问offer审批时间和流程)   最后总结: 1.商汤的面试效率还是很快的,我跟HR说手上有offer问能不能安排快点面试,然后一天上午下午晚上就二三四面搞完了。 2.关于面试定级和offer审批这一块的流程就比较慢了,已经等了一周多还在流程中。
分享
12
原味笔面经
张郁婷
科维智娱_招聘经理

Hello~近期在找新的机会吗? 这个岗位考虑一下不? 岗位职责: 1、独立进行三消关卡设计,独立撰写文档; 2、完善当前游戏已有玩法,监控数据情况; 3、分析关卡优缺点,针对关卡进行调优。 任职要求: 1、有很强的观察、思考、总结能力; 2、丰富的游戏经验,熟悉各类休闲游戏关卡玩法设计; 3、超过300个三消关卡设计经验,或其他游戏关卡设计经验,具有独立设计关卡的能力; 4、能独立分析关卡优缺点,并对现有关卡进行优化调整; 5、有独立的关内玩法设计能力,能独立完成关内玩法的文档设计。 职位关键词:游戏策划 工作地点:北京
分享
评论
我这里招人
浪得虚名张大师
中国矿业大学·2022届

许愿贴

听说在论坛里面许愿很灵! 许愿pdd oc! 许愿美团转正顺利! 许愿shopee二面顺利!
分享
1
超好运许愿池
why
西交利物浦大学·2022届

腾讯,百度,京东,美团,VIVO,VIPKID面经和一些感想

一直在犹豫这个放面经到底好不好,但是犹豫最近发生的一些事,觉得有些话还是要现在说出来,给各位一些激励。 内推基本结束之后,由于妹子这边找工作困难比较多,目前就主要帮妹子投简历,总结编程题,总结测开面经,跑线下宣讲会。 有些话想给程序媛们说一下。不管怎么样,明年你都会变成一位职场女性,即是今天被diss的再惨,擦干眼泪,妈的!下一场!这两个月的辛苦,带给你的不仅是offer和技术上的成长,更有心态上的成熟和自控力的加强。 有些话想给暂时不如意的非科班兄弟们说一下。如果说基础,我们可能不如科班同学扎实,但是我们能做的就是调整好自己的心态,总有神仙在打架,咱管不着,他们在上面飞,我们走好自己的路,如果成功再接再厉,如果失败,没关系,老子又不是专门搞这个的,后面机会还多。 每一次失败之后,我总在自我调节,作为一个男人,这点压力,这点失败,只要不要了老子的命,老子就和你们刚到底! 只有这样,等自己到了上有老,下有小的时候,一大家子人压力都在你身上的时候,你才能更加从容的应对。 下面上干货: 求职方向:C++后台(非科班) 本人从今年三月份才开始打算找互联网公司,四月份确定后台方向。其实四月份的时候,我连socket,bind,listen是啥都不知道。但在找好方向之后,就是放手去搞。其中重点看的知识点,就是之前的面试高频问题。 建议:如果面经已经看得差不多了,感觉没什么新鲜玩意了,但还没有如意offer的同学,就可以看看书,复习复习项目,刷刷题。如果现在看面经还觉得问题很多,那就抓紧看面经。 书籍推荐: linux方向:《鸟哥的Linux私房菜-基础篇》、《shell编程从入门到精通》 操作系统:《操作系统精髓与设计原理》 后台方向:《Linux多线程服务端编程:使用muduo》、《Linux高性能服务端编程》、《后台开发核心技术与应用》 数据库:《SQL必知必会》、《高性能MySQL》 网络方向:《TCPIP网络编程》、《TCP-IP详解:卷一》、《UNIX网络编程》、《图解HTTP》 C++语言:《Primer c++》、《Effective C++》、《STL源码》、《深度探索C++模型》 就这几个月的时间,书不可能都看完,但是看到面经上的问题,和自己面试过程中回答的不好的问题,我都会去百度找答案,发现很多博客也是在抄书,最后干脆自己直接从书上找答案吧。 但是APUE,UNP,TCP详解,STL源码这几本圣经还是要从头到尾看一下。如果现在觉得刷面经对自己提升慢了,可以回过头从书中找答案。 实习腾讯一面: 1、自我介绍 2、是否用过vector,map。vector和map的底层实现?vector填充元素满了之后怎么办? 3、TCP,UDP区别?TCP三次握手?time_wait发生在哪里?为什么需要等待两分钟?边界性什么意思? 4、描述一下快排,复杂度?最好复杂度?最差复杂度?分别对应什么情况。 5、epoll和poll 区别。epoll底层实现是什么?epoll一定比poll好么?epoll的两种工作模式? 6、平时怎么调试C++?linux 下常用命令?查看当前进程端口?gdb的常用命令,core dump怎么办? 7、你有什么问题要问的?(面试官嘱咐,对于问题需要仔细探究其原理。) 内推腾讯二面(一面当时忘记了): 1、介绍自己的项目。 2、Reactor怎么回事?那你为什么要用Reactor模式? 3、epoll函数怎么用的? 4、如果现在打开你的网站慢了,你觉得哪些地方会有问题? 5、如果给你一个数组,里面的数是0-N,你有哪些方法实现,空间复杂度和时间复杂度都是最低。 内推腾讯三面: 1、首先进行一个自我介绍吧。 2、C++网络库用在哪里?如何理解阻塞和非阻塞?同步和非同步? 3、了解堆排序的原理么?如何进行堆排序的?手写一个模板类的堆排序。 4、如果让你用线性空间,只知道头结点,如何存储一个key-value数据结构,让他时间复杂度都尽量低。 5、我有n个节点m个任务,每个任务可以访问若干个节点,每个节点有自己的上限流量,也有不同的价钱, 每个任务可以拆分成不同大小的任务,分给不同节点去完成。如何保证总价最低。 最后这个问题我是真的没懂,不知道题是不是这个意思,一般的贪心,动态规划都不行,大家大概看一下。 内推腾讯四面: 1、介绍自己的项目。 2、你都考虑过哪些模式?Preactor和Reactor模式区别? 3、进行过测试么?能达到一个什么样的性能? 4、性能的瓶颈是在哪里?top命令中三个值含义是什么?CPU负载是什么?单核可能超过1么? 5、怎么用你这个库,几个基本的调用API是什么? 6、软件语言上写的问题?硬件资源的问题? 7、最高多少的QPS?有么有看过别人的网络库?他们是怎么写的? 8、局域网监控项目是什么?用在哪里? 9、实验室项目做什么?简单的讲一下? 10、你有什么要问的? 部门大佬最后给的建议: 1、看看自己服务器瓶颈是在软件那个部分? 2、将最终错误落实在硬件上。 3、多看看别人网络库是怎么写的。 内推百度SRE一面: 1、首先自我介绍 2、说一下从输入www.baidu.com到响应,到底经过了什么。每一个协议分别是什么。 3、DNS怎么回事,如何查找对应的IP地址。DNS用什么协议。广播是怎么回事? 4、如果手机查看网页,响应变慢了,原因会有哪些。 5、Linux时间系统到底怎么回事,如何确定。网络库中定时机制怎么回事。Linux时间精度多少?如何统一很多机器之间的时间。 6、你了解什么网络攻击方式? 7、进程,线程对于CPU负载来说,区别是什么。 8、12个core,多少进程和线程可以让core达到90以上? 9、排序算法中,熟悉哪一个?什么时候适合用冒泡?什么时候适合用快排?快排和堆排区别?STL中排序用的哪一个? 10、给你64G int类型数据,32G内存,12个core,如何快速高性能对这64G数据进行排序? 内推百度SRE二面: 1、首先自我介绍? 2、你觉得那个项目有难度,我问你? a、什么是reactor模式? b、你觉得你的网络库有什么作用?可以用来干什么? c、多线程有什么优势?多线程怎么实现通信?多线程的声明周期是什么? d、锁是怎么回事?底层是什么?读写锁怎么回事? e、还是昨天的问题?十二core的计算机,和线程有什么关系?你怎么发挥它这个性能? f、C++11新特性?你用到了哪些?干什么用? 3、四次挥手是什么?HTTP服务器是谁先断开连接?CLOSE_WAIT是怎么回事?如何解决该问题?TCP拥塞控制怎么回事? 4、ping是怎么回事?ICMP是什么?介绍一下ICMP?哪一层的协议? 5、怎么进行路由器探活,寻址?路由器下一跳如果断了,怎么知道?让你自己设计一个路由器协议,怎么设计?哪一层?探活周期? 6、了解操作系统么?虚拟内存是什么?地址映射怎么回事?如果内存都是无限大的可以不用虚拟内存么? 7、给你一个树,不知道每个节点相关的子节点,父节点,可以知道左子树,和兄弟节点。将他们存储在文本中,如何从文本恢复这个树的整个结构? 8、你有什么要问我的么? 内推百度系统部一面: 1、首先自我介绍。 2、能说一下什么是Reactor模式么?那和Preactor有什么区别呢? 3、网站压测为什么会有错误呢?错误在哪里?如何改进呢?如何查看IO,如何查看netstat。如何查看负载。 4、如果你的内存不用,但是常用的数据结构不能存储你当前的数据,怎么办?LRU 5、vector<int>的迭代器什么情况下会失效。 6、map和list有什么区别?底层数据结构是什么? 7、红黑树和B+树的区别?分别有什么优点,各自操作的复杂度? 8、tcp和udp有什么特点?tcp和udp适用哪些场景?调用的API分别是哪些? 9、tcp状态机了解哪些?close_wait和time_wait分别怎么回事? 10、HTTP首部,你知道哪些内容?cookie和fashion分别是什么?expire分别是什么? 11、epoll函数和select比有哪些优点?epoll底层实现是怎么回事?ET和LT工作模式?优缺点? 12、C++语言虚函数?用处?缺点?哪些应该用,哪些不能用? 13、四种转换模式分别是什么?inline函数的优缺点? 14、new和malloc的区别?new的重载用在哪些情况? 15、数据库用在哪里?如何优化?索引怎么回事? 16、再问两个数据结构题:最大连续子数组和?100亿数据,找最大的N个数据? 内推百度系统部二面: 1、首先自我介绍。 2、介绍一下自己的项目。每个项目里,每个人都负责什么内容? 3、如何理解阻塞和非阻塞?同步和异步? 4、HTTP服务器,完成了哪些功能?POST,GET。 5、有过什么方式改进自己的HTTP服务器?如何进行测试的? 6、HTTP服务器头部你了解哪些关键字?200,206,302,404分别是什么意思? 6、TCP协议和IP协议的区别? 7、TCP如何进行拥塞控制?RTT怎么回事? 8、如果让你重新设计一个TCP协议,你该怎么设计?针对无线卫星链路,带宽资源丰富,但是经常丢帧。 内推百度系统部三面: 1、首先自我介绍,主要想听到你本科和研究生期间做了些什么?让自己分别在哪些方面有所成长。 2、为什么要做这里两个项目呢? 3、介绍一下你这个网络库吧。最终有没有使用在什么地方?硬件的配置是什么样子? 4、有没有进行过测试?webbench里面的错误代表什么呢?503是如何返回的? 5、错误概率是怎么回事?你觉得是哪些方面引起你这个错误的? 6、实验室的项目都在做什么?用普通的语言组织一下,给我讲清楚。 7、你有什么要问我的么? 内推京东云一面: 1、首先自我介绍 2、介绍一下项目?局域网监控? 3、UDP和TCP区别?陈硕网络库怎么实现?TCP为什么需要三次握手,四次挥手? 4、TCP状态机,timewait和closewait是怎么回事?为什么是2MSL? 5、你怎么理解非阻塞?怎么设置?errno线程安全么?C++重载函数const可以作为重载依据么。 6、epoll函数理解?两种工作模式区别? 7、线程和进程的区别?线程共享哪些内存? 8、进程间通信方式?PIPE怎么回事? 9、两种信号不能屏蔽?子进程继承父进程的什么?fork,vfork?COW? 10、孤儿进程?僵尸进程? 11、进程sigaction和signal区别? 12、多线程编程,静态管理,读写锁的实现?compare_swap函数?CAS? 13、死锁的四个必要条件? 14、TCP的拥塞控制? 15、编程题,求两个链表的差别。 内推京东云二面: 1、首先自我介绍。 2、如何设计一个hash表?你会考虑哪些因素?哈希函数怎么选择? 3、hash表的容量你怎么设计,负载因子怎么考虑? 4、如果有一个数据库,大量的读和和少量的写,你会怎么处理?考虑哪些因素? 5、如果要加锁是在数据库加锁好一些,还是在服务端直接加锁? 6、数据库联合索引的命中问题?如何查看是否命中。 7、B+数和红黑树的区别?Redis移植性怎么做? 8、C++怎么由代码转换成二进制数据?动态链接是怎么回事?每个过程都有些什么工作? 9、出了一道链表两两反转的问题。现场写代码。 内推美团一面: 1、首先自我介绍 2、了解Web层开发?数据库索引了解么?聚簇索引,非聚簇索引?索引分类? 3、了解数据库都由哪些引擎?分别有什么区别和使用场景? 4、了解分布式?高可用?如何保证节点集群的同步?Nginx了解过么? 5、对象的重写和重载? 6、设计模式里面,单例模式?实现单例模式的双重校验。 7、epoll函数怎么理解?epoll函数在别的哪些地方有用到? 8、手撕:两个链表的重合第一个节点。 内推美团二面: 1、首先自我介绍 2、数据库基本操作?如何建立索引?还有一些数据库基本问题。 3、手撕:有时间区间,判断昨天送外卖的峰值。(这个题做了快三十分钟,主要是外卖订单可能是前天的,昨天的或者今天的)。 4、平时都怎么学习? 5、自己的博客和个人网站有么? 6、你有什么要问我的? 内推美团三面: 1、首先自我介绍一下。 2、介绍一下自己的项目。(疯狂怼项目了,连实验室的都问了,楼主搞通信的,面试官让我用白话给面试官讲了一些技术点。) 3、web方面的应用多么,问了问HTTP? 4、智力题:一天24小时,时针和分针会重合几次? 5、类似于百度地图,如何求A地到B地的路径,刚开始被面试官套路了,用各种分类讨论了半天,最后才发觉面试官想问的是有向图求最短路径?说了一下方法是什么?迪杰斯特拉算法作用,复杂度? 6、你有什么问题要问我的? 内推VIPKID一面: 1、介绍一下自己的项目。 2、自己选择方向问题。 3、说一下三次握手? 4、说一下什么是TIME_WAIT,什么是CLOSE_WAIT,CLOSE_WAIT会遇到什么问题? 5、说一下创建socket时候,如何创建阻塞和非阻塞的,几种方法?listen函数怎么调用,参数的含义。 6、说一下epoll和select是什么?相比优缺点是什么?epoll两种工作模式?效率对比。 7、问一下C++的知识?static特性?const特性?引用和指针区别? 8、说一下什么事override和overload?virtual什么意思?virtual =0什么意思? 9、你知道哪些排序?说出复杂度?哪些是稳定,哪些不是稳定? 10、你有什么要问我的? 内推VIPKID二面: 1、首先自我介绍。 2、C++默认构造函数怎么回事?什么时候会用到默认构造函数? 3、C++的继承是怎么回事? 4、shell常用么?awk使用方法?如何读取第一百行到第二百行数据? 5、说一下select和epoll的区别?epoll的两种工作模式? 6、select和epoll底层实现,数据结构有什么区别呢? 7、如何根据前序遍历和中序遍历恢复二叉树?必要条件是什么?如果值重复,那怎么处理? 8、了解LRU算法么?叙述一下?LRU怎么实现?按照时间来实现? 9、如果是在LRU加入频次这个影响因素怎么解决这个问题呢? 内推OPPO一面: 1、自我介绍 2、介绍一下你做的和后端有关的项目。 3、介绍一下Reactor网络库。线程池怎么实现?怎么用?用过的和没有用过的怎么区分? 4、多线程和多进程区别?用的什么锁?怎么解决共享变量问题?你会怎么选择用多线程还是多进程? 进程间通信方式,你知道几个? 5、C++语言虚函数?纯虚函数?栈和堆的区别?哪些变量在堆,哪些变量在栈? 6、了解单例模式么?一般会在什么情形下用到? 7、一千瓶药,只有一个毒药,若干小白鼠,小白鼠吃了毒药第二天才回死亡,问你至少需要多少只小白鼠? 8、100万集合数据,怎么找第二个,怎么找第k个? 9、利用shell写一个文字处理脚本,如何只获取第三行数据?shell和pthyon用的多不多?分布式有什么理解? 10、你有什么问题? 内推VIVO一面: 1、首先自我介绍一下。 2、学过哪些和计算机有关的课程。操作系统?网络?汇编? 3、挑一个你觉得难度大的项目,讲一下?为什么做这个项目? 4、说一下你项目中对于多线程的控制方法?如何解决竟态问题? 5、讲一下你对网络编程的理解。 6、讲一下,你对C++面向对象的理解?C++内存分布是怎么分布? 7、讲一下资源泄露控制?你对智能指针底层实现? 8、页面置换?虚拟内存? 9、你还有什么要问的么? 内推VIVOHR面: 1、首先自我介绍。 2、为什么想做软件开发?什么时候确定的这个目标? 3、一般通过那种方式学习效率高? 4、你觉得你和计算机专业相比有什么优势呢? 5、你做过这些项目,哪些对自己的提升比较高?为什么? 6、你最近都在看什么书? 7、可以说一下你的本科成绩和研究生成绩么?四六级通过了么? 8、你还有什么问题么? 其他的HR面我没写,感觉参考意义不大。华为感觉就是讲项目,没什么实际问题。 不过最后想和大家分享一下华为优招总监面的一个问题。 总监:如果我今天不让你通过,你会花多长时间调整自己。 我:没关系,我失败的次数已经很多了,如果这次失败的话,我会用一天时间调整自己的心态,总结失败经验,然后继续准备,避免以后继续犯同样的错误。
分享
8
原味笔面经
苏三公子
华南理工大学·2022届

Java 知识点整理:集合、JVM、并发

P18:List List 是一种线性列表结构,元素是有序、可重复的。 *ArrayList * 底层由数组实现,随机访问效率高,读快写慢,由于写操作涉及元素的移动,因此写操作效率低。 ArrayList 实现了 RandomAcess 标记接口,如果一个类实现了该接口,那么表示这个类使用索引遍历比迭代器更快。 三个重要的成员变量: 复制代码1transient Object[] elementData; elementData 是 ArrayList 的数据域,transient 表示它不会被序列化,不使用 elementData 直接序列化是因为这是一个缓存数组,出于性能考虑通常会预留一些容量,当容量不足时会扩充容量,因此可能会有大量空间没有存储元素,采用这样的方式可以保证只序列化实际有值的那些元素而不需要序列化整个数组。 复制代码1private int size; size 表示当前 List 的实际大小,elementData 的大小是大于等于 size 的。 复制代码1protected transient int modCount = 0; 该成员变量继承自 AbstractList,记录了ArrayList 结构性变化的次数。所有涉及结构变化的方法都会增加该值,包括add()、remove()、addAll()、removeRange() 及clear() 等。 在使用迭代器遍历 ArrayList 时不能修改元素,modCount 统计 ArrayList 修改次数,expectedModCount 则是在迭代器初始化时记录的modCount 值,每次访问新元素时都会检查 modCount 和 expectedModCount是否相等,如果不相等就会抛出异常。 LinkedList 底层由链表实现,与 ArrayList 相反,需要顺序访问元素,即使有索引也需要从头遍历,因此写快读慢。 LinkedList 实现了 Deque 接口,具有队列的属性,可在尾部增加元素,在头部获取元素,也能操作头尾之间任意元素。 所有成员变量都被 transient 修饰,序列化原理和ArrayList类似。 Vector 和 Stack Vector 的实现和 ArrayList 基本一致,底层使用的也是数组,它和 ArrayList 的区别主要在于:(1)Vector 的所有公有方法都使用了 synchronized 修饰保证线程安全性。(2)增长策略不同,Vector 多了一个成员变量 capacityIncrement 用于标明扩容的增量。 Stack 是 Vector 的子类,实现和 Vector基本一致,与之相比多提供了一些方法表达栈的含义。 P19:HashSet HashSet 中的元素是无序、不重复的,最多只能有一个 null 值。 HashSet 的底层是通过 HashMap 实现的,HashMap 的 key 值即 HashSet 存储的元素,所有 key 都使用相同的 value ,一个static final 修饰的变量名为 PRESENT 的 Object 类型的对象。 由于 HashSet 的底层是 HashMap 实现的,HashMap 是线程不安全的,因此 HashSet 也是线程不安全的。 去重:对于基本类型的包装类,直接按值进行比较。对于引用数据类型,会先比较 hashCode() 返回值是否相同,如果不同则代表不是同一个对象,如果相同则继续比较equals()方法返回值是否相同,都相同说明是同一个对象。 P20:HashMap JDK 8 之前 底层实现是数组 + 链表,主要成员变量包括:存储数据的 table 数组、键值对数量 size、加载因子 loadFactor。 table 数组用于记录 HashMap 的所有数据,它的每一个下标都对应一条链表,所有哈希冲突的数据都会被存放到同一条链表中,Entry 是链表的节点元素,包含四个成员变量:键 key、值 value、指向下一个节点的指针 next 和 元素的散列值 hash。 在 HashMap 中数据都是以键值对的形式存在的,键对应的 hash 值将会作为其在数组里的下标,如果两个元素 key 的 hash 值一样,就会发送哈希冲突,被放到同一个下标中的链表上,为了使 HashMap 的查询效率尽可能高,应该使键的 hash 值尽可能分散。 HashMap 默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。 1.put 方法:添加元素 ① 如果 key 为 null 值,直接存入 table[0]。② 如果 key 不为 null 值,先计算 key 对应的散列值。③ 调用 indexFor 方法根据 key 的散列值和数组的长度计算元素存放的下标 i。④ 遍历 table[i] 对应的链表,如果 key 已经存在,就更新其 value 值然后返回旧的 value 值。⑤ 如果 key 不存在,就将 modCount 的值加 1,使用 addEntry 方法增加一个节点,并返回 null 值。 2.hash 方法:计算元素 key 对应的散列值 ① 处理 String 类型的数据时,直接调用对应方法来获取最终的hash值。② 处理其他类型数据时,提供一个相对于 HashMap 实例唯一不变的随机值 hashSeed 作为计算的初始量。③ 执行异或和无符号右移操作使 hash 值更加离散,减小哈希冲突的概率。 3.indexFor 方法:计算元素下标 直接将 hash 值和数组长度 - 1 进行与操作并返回,保证计算后的结果不会超过 table 数组的长度范围。 4.resize 方法:根据newCapacity 来确定新的扩容阈值 threshold ① 如果当前容量已经达到了最大容量,就将阈值设置为 Integer 的最大值,之后扩容就不会再触发。② 创建一个新的容量为 newCapacity 的 Entry 数组,并调用 transfer 方法将旧数组的元素转移到新数组.③ 将阈值设为(newCapacity 和加载因子 loadFactor 的积)和(最大容量 + 1 )的较小值。 5.transfer:转移旧数组到新数组 ① 遍历旧数组的所有元素,调用 rehash 方法判断是否需要哈希重构,如果需要就重新计算元素 key 的散列值。② 调用 indexFor 方法根据 key 的散列值和数组的长度计算元素存放的下标 i,利用头插法将旧数组的元素转移到新的数组。 6.get 方法:根据 key 获取元素的 value 值 ① 如果 key 为 null 值,调用 getForNullKey 方法,如果 size 为 0 表示链表为空,返回 null 值。如果 size 不为 0,说明存在链表,遍历 table[0] 的链表,如果找到了 key 为 null 的节点则返回其 value 值,否则返回 null 值。② 调用 getEntry 方法,如果 size 为 0 表示链表为空,返回 null 值。如果 size 不为 0,首先计算 key 的散列值,然后遍历该链表的所有节点,如果节点的 key 值和 hash 值都和要查找的元素相同则返回其 Entry 节点。③ 如果找到了对应的 Entry 节点,使用 getValue 方法获取其 value 值并返回,否则返回 null 值。 *JDK 8 开始 * 使用的是数组 + 链表/红黑树的形式,table 数组的元素数据类型换成了 Entry 的静态实现类 Node。 1.put 方法:添加元素 ① 调用 putVal 方法添加元素。② 如果 table 为空或没有元素时就进行扩容,否则计算元素下标位置,如果不存在就新创建一个节点存入。③ 如果首节点和待插入元素的 hash值和 key 值都一样,直接更新 value 值。④ 如果首节点是 TreeNode 类型,调用 putTreeVal 方法增加一个树节点,每一次都比较插入节点和当前节点的大小,待插入节点小就往左子树查找,否则往右子树查找,找到空位后执行两个方法:balanceInsert 方法,把节点插入红黑树并对红黑树进行调整使之平衡。moveRootToFront 方法,由于调整平衡后根节点可能变化,table 里记录的节点不再是根节点,需要重置根节点。⑤ 如果是链表节点,就遍历链表,根据 hash 值和 key 值判断是否重复,决定更新值还是新增节点。如果遍历到了链表末尾,添加链表元素,如果达到了建树阈值,还需要调用 treeifyBin 方法把链表重构为红黑树。⑥ 存放元素后,将 modCount 值加 1,如果节点数 + 1大于扩容阈值,还需要进行扩容。 2.get 方法:根据 key 获取元素的 value 值 ① 调用 getNode 方法获取 Node 节点,如果不是 null 值就返回 Node 节点的 value 值,否则返回 null。② 如果数组不为空,先比较第一个节点和要查找元素的 hash 值和 key 值,如果都相同则直接返回。③ 如果第二个节点是 TreeNode 节点则调用 getTreeNode 方法进行查找,否则遍历链表根据 hash 值和 key 值进行查找,如果没有找到就返回 null。 3.hash 方法:计算元素 key 对应的散列值 Java 8 的计算过程简单了许多,如果 key 非空就将 key 的 hashCode() 返回值的高低16位进行异或操作,这主要是为了让尽可能多的位参与运算,让结果中的 0 和 1 分布得更加均匀,从而降低哈希冲突的概率。 4.resize 方法:扩容数组 重新规划长度和阈值,如果长度发生了变化,部分数据节点也要重新排列。 重新规划长度 ① 如果 size 超出扩容阈值,把 table 容量增加为之前的2倍。② 如果新的 table 容量小于默认的初始化容量16,那么将 table 容量重置为16。③ 如果新的 table 容量大于等于最大容量,那么将阈值设为 Integer 的最大值,并且 return 终止扩容,由于 size 不可能超过该值因此之后不会再发生扩容。 重新排列数据节点 ① 如果节点为 null 值则不进行处理。② 如果节点不为 null 值且没有next节点,那么重新计算其散列值然后存入新的 table 数组中。③ 如果节点为 TreeNode 节点,那么调用 split 方法进行处理,该方法用于对红黑树调整,如果太小会退化回链表。④ 如果节点是链表节点,需要将链表拆分为 hashCode() 返回值超出旧容量的链表和未超出容量的链表。对于hash & oldCap == 0 的部分不需要做处理,反之需要放到新的下标位置上,新下标 = 旧下标 + 旧容量。 线程不安全:Java 7 扩容时 resize 方法调用的 transfer 方法中使用头插法迁移元素,多线程会导致 Entry 链表形成环形数据结构,Entry 节点的 next 永远不为空,引起死循环。Java 8 在 resize 方法中完成扩容,并且改用了尾插法,不会产生死循环的问题,但是在多线程的情况下还是可能会导致数据覆盖的问题,因此依旧线程不安全。 红黑树:红黑树是一种自平衡的二叉查找树。 特性:红黑树的每个节点只能是红色或者黑色、根节点是黑色的、每个叶子节点都是黑色的、如果一个叶子节点是红色的,它的子结点必须是黑色的、从一个节点到该节点的叶子节点的所有路径都包含相同数目的黑色节点。 左旋:对节点进行左旋,相当于把节点的右节点作为其父节点,即将节点变成一个左节点。 右旋:对节点进行右旋,相当于把节点的左节点作为其父节点,即将节点变成一个右节点。 插入:① 被插入的节点是根节点,直接将其涂为黑色。② 被插入节点的父节点是黑色的,不做处理,节点插入后仍是红黑树。③ 被插入节点的父节点是红色的,一定存在非空祖父节点,根据叔叔节点的颜色分类处理。 删除:① 被删除的节点没有子节点,直接将其删除。② 被删除节点只有一个子节点,直接删除该节点,并用其唯一子节点替换其位置。③ 被插入节点有两个子节点,先找出该节点的替换节点,然后把替换节点的数值复制给该节点,删除替换节点。 调整平衡:在插入和删除节点后,通过左旋、右旋或变色使其重新成为红黑树。① 如果当前节点的子节点是一红一黑,直接将该节点设为黑色。② 如果当前节点的子结点都是黑色,且当前节点是根节点,则不做处理。③ 如果当前节点的子节点都是黑色且当前节点不是根节点,根据兄弟节点的颜色分类处理。 JVM 15 P1:运行时数据区 程序计数器 程序计数器是一块较小的内存空间,可以看作当前线程所执行字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、线程恢复等功能都需要依赖计数器完成。程序计数器是线程私有的,各条线程之间互不影响,独立存储。 如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是本地(Native)方法,计数器值则应为空(Undefined)。 此内存区域是唯一一个在《 Java 虚拟机规范》中没有规定任何内存溢出情况的区域。 Java 虚拟机栈 Java 虚拟机栈是线程私有的,每当有新的线程创建时就会给它分配一个栈空间,当线程结束后栈空间就被回收,因此栈与线程拥有相同的生命周期。栈主要用来实现方法的调用与执行,每个方法在执行的时候都会创建一个栈帧用来存储这个方法的局部变量、操作栈、动态链接和方法出口等信息。当一个方法被调用时,会压入一个新的栈帧到这个线程的栈中,当方法调用结束后会弹出这个栈帧,回收掉调用这个方法使用的栈空间。 该区域有两类异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。如果 JVM 栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常(HotSpot 不可以动态扩展,不存在此问题)。 本地方法栈 本地方法栈与虚拟机栈的作用相似,不同的是虚拟机栈为虚拟机执行 Java 方法(字节码)服务,而本地方法栈是为虚拟机栈用到的本地(Native)方法服务。调用本地方法时虚拟机栈保持不变,动态链接并直接调用指定的本地方法。 《 Java 虚拟机规范》对本地方法栈中方法所用语言、使用方式与数据结构无强制规定,具体的虚拟机可根据需要自由实现,例如 HotSpot 直接将虚拟机栈和本地方法栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度异常和栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。 Java 堆 Java 堆是虚拟机所管理的内存中最大的一块。堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此区域的唯一目的就是存放对象实例,Java 里几乎所有的对象实例都在这里分配内存。 Java 堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。但对于大对象(例如数组),多数虚拟机实现出于简单、存储高效的考虑会要求连续的内存空间。 Java 堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的 JVM 都是按照可扩展来实现的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,虚拟机将抛出 OutOfMemoryError 异常。 方法区 方法区和 Java 堆一样是各个线程共享的内存区域,它用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 JDK 8 之前使用永久代来实现方法区,这种设计导致了 Java 应用容易遇到内存溢出问题,因为永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小。JDK 6 时 HotSpot 的开发团队就准备放弃永久代,改用本地内存来实现方法区,JDK 7 时已经把原本放在永久代的字符串常量池、静态变量等移出,到了 JDK8 时永久代被完全废弃,改用在本地内存中实现的元空间来代替,把 JDK 7 中永久代剩余内容(主要是类型信息)全部移到元空间。 《 Java 虚拟机规范》对方法区的约束很宽松,除了和 Java 堆一样不需要连续的内存和可以选择固定大小或可扩展外,还可以选择不实现垃圾回收。垃圾回收行为在该区域出现较少,主要回收目标是针对常量池的回收和对类型的卸载,一般来说该区域的回收效果比较难令人满意,尤其是类型的卸载,条件十分苛刻。如果方法区无法满足新的内存分配需求时,将抛出 OutOfMemoryError 异常。 运行时常量池 运行时常量池是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。一般来说,除了保存 Class 文件中描述的符号引用外,还会把符号引用翻译出来的直接引用也存储在运行时常量池中。 运行时常量池相对于 Class 文件常量池的另一个重要特征是具备动态性,Java 语言并不要求常量一定只有编译期才能产生,也就是说并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被利用的较多的是String 类的 intern() 方法。 由于运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。 直接内存 直接内存不是 JVM 运行时数据区的一部分,也不是《 Java 虚拟机规范》中定义的内存区域,但是这部分内存也被频繁使用,而且也可能导致内存溢出异常。 JDK 1.4 中新加入了 NIO 模型,引入了一种基于通道与缓冲区的 IO 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,避免了在 Java 堆和 Native堆中来回复制数据。 本机直接内存的分配不会收到 Java 堆大小的限制,但还是会受到本机总内存大小以及处理器寻址空间的限制,一般配置虚拟机参数时会根据实际内存去设置 -Xmx 等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError 异常。 P2:对象创建的过程 当 JVM 遇到一条字节码 new 指令时,首先将检查该指令的参数能否在常量池中定位到一个类的符号引用,并检查这个引用代表的类是否已被加载、解析和初始化,如果没有就必须先执行类加载过程。 在类加载检查通过后虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,分配空间的任务实际上等于把一块确定大小的内存块从 Java 堆中划分出来。假设 Java 堆内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点指示器,分配内存就是把该指针向空闲方向挪动一段与对象大小相等的距离,这种方式叫"指针碰撞"。 如果 Java 堆中的内存不是规整的,那么虚拟机就必须维护一个列表记录哪些内存块是可用的,在分配时从列表中找到一块足够大的空间划分给对象实例并更新列表上的记录,这种方式叫做"空闲列表"。 选择哪种分配方式由堆是否规整决定,堆是否规整又由所用垃圾回收器是否带有空间压缩整理能力决定。因此使用 Serial、ParNew 等带压缩整理的收集器时,系统采用指针碰撞;当使用 CMS 这种基于清除算法的垃圾收集器时,理论上只能采用空间列表分配内存。 分配内存的线程安全问题:对象创建在虚拟机中十分频繁,即使修改一个指针所指向的位置在并发情况下也不是线程安全的,可能出现正给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决该问题有两个方法:① 虚拟机采用 CAS 加失败重试的方式保证更新操作的原子性。② 把内存分配的动作按照线程划分在不同空间进行,即每个线程在 Java 堆中预先分配一小块内存,叫做本地线程分配缓冲 TLAB,哪个线程要分配内存就在对应的 TLAB 分配,只有 TLAB 用完了分配新缓冲区时才需要同步。 内存分配完成后虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值,保证对象的实例字段在 Java 代码中可以不赋初始值就直接使用,使程序能访问到这些字段的数据类型对应的零值。之后虚拟机还要对对象进行必要设置,例如对象是哪个类的实例、如何找到类的元数据信息等。 至此从虚拟机的视角来看一个新的对象已经产生了,但从程序的角度来说对象创建才刚开始。此时构造方法,即 Class 文件中的 init 方法还没有执行,所有字段都为默认零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。一般来说 new 指令后会接着执行 init 方法,按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全被构造出来。 P3:对象的内存布局 在 HotSpot 虚拟机中,对象在堆内存中的存储布局可分为三个部分。 对象头 对象头包括两类信息,第一类是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID等,这部分数据叫做"Mark Word"。 对象头的另一部分是类型指针,即对象指向它的类型元数据的指针,JVM 通过该指针来确定对象是哪个类的实例。并非所有虚拟机实现都必须在对象数据上保留类型指针,查找对象的元数据不一定要经过对象本身。此外如果对象是一个 Java 数组,在对象头还必须有一块用于记录数组长度的数据。 实例数据 实例数据部分是对象真正存储的有效信息,即程序员在代码里所定义的各种类型的字段内容。存储顺序会受到虚拟机分配策略参数和字段在源码中定义顺序的影响。相同宽度的字段总是被分配到一起存放,在满足该前提条件的情况下父类中定义的变量会出现在子类之前。 对齐填充 这部分不是必然存在的,仅仅起占位符的作用。由于 HotSpot 虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,而对象头已经被设为正好是 8 字节的整数倍,因此如果对象实例数据部分没有对齐,就需要对齐填充来补全。 P4:对象的访问定位 Java 程序会通过栈上的 reference 数据来操作堆上的具体对象,而具体对象访问方式是由虚拟机决定的,主流的访问方式主要有使用句柄和直接指针两种。 使用句柄 如果使用句柄访问,Java 堆中将可能会划分出一块内存作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。 优点是 reference 中存储的是稳定句柄地址,在对象被移动(处于垃圾收集过程中)时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改。 直接指针 如果使用直接指针访问的话,Java 堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话就不需要多一次间接访问的开销。 优点就是速度更快,节省了一次指针定位的时间开销,HotSpot 主要使用的就是直接指针来进行对象访问。 P5:内存溢出异常 Java 堆溢出 Java 堆用于存储对象实例,我们只要不断创建对象,并且保证GC Roots到对象有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆容量的限制后就会产生OOM异常。例如在 while 死循环中一直 new 创建实例。 Java 堆内存的 OOM 是实际应用中最常见的 OOM 情况,常规的处理方法是先通过内存映像分析工具对 Dump 出来的堆转储快照进行分析,确认内存中导致 OOM 的对象是否是必要的,即分清楚到底是出现了内存泄漏还是内存溢出。 如果是内存泄漏,可进一步通过工具查看泄漏对象到 GC Roots 的引用链,找到泄露对象是通过怎样的引用路径、与哪些GC Roots相关联才导致垃圾收集器无法回收它们,一般可以准确定位到对象创建的位置进而找出产生内存泄漏代码的具***置。 如果不是内存泄漏,即内存中的对象确实都是必须存活的那就应当检查 JVM 的堆参数设置,与机器的内存相比是否还有向上调整的空间。再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运行期的内存消耗。 虚拟机栈和本地方法栈溢出 由于HotSpot虚拟机不区分虚拟机和本地方法栈,因此设置本地方法栈大小的参数没有意义,栈容量只能由 -Xss 参数来设定,存在两种异常: StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。例如一个递归方法不断调用自己。 该异常有明确错误堆栈可供分析,容易定位到问题所在。 OutOfMemoryError:如果 JVM 栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。HotSpot 虚拟机不支持虚拟机栈的扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现OOM异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。 运行时常量池溢出 String类的intern方法是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的 String 对象的引用,否则会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。 在 JDK6及之前常量池都分配在永久代,因此可以通过 -XX:PermSize 和 -XX:MaxPermSize 限制永久代的大小,间接限制常量池的容量。在 while 死循环中不断调用intern方法,之后将导致运行时常量池溢出。 在 JDK7 及之后版本不会导致该问题,因为存放在永久代的字符串常量池已经被移至 Java 堆中。 方法区溢出 方法区的主要职责是用于存放类型的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。只要不断在运行时产生大量的类去填满方法区,就会导致溢出。例如使用 JDK 的反射或 CGLib 直接操作字节码在运行时生成大量的类会导致溢出。当前的很多主流框架如Spring、Hibernate等对类增强是都会使用CGLib这类字节码技术,增强的类越多,就需要越大的方法区保证动态生成的新类型可以载入内存,也就更容易导致方法区溢出。 JDK 8 之后永久代完全被废弃,取而代之的是元空间,HotSpot 提供了一些参数作为元空间的防御措施: -XX:MaxMetaspaceSize:设置元空间的最大值,默认 -1,表示不限制即只受限于本地内存大小。 -XX:MetaspaceSize:指定元空间的初始大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量空间就适当降低该值,如果释放了很少的空间就适当提高该值。 -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:MinMetaspaceFreeRatio,用于控制最大的元空间剩余容量百分比。 本机直接内存溢出 直接内存的容量大小可通过 -XX:MaxDirectMemorySize 指定,如果不去指定则默认与 Java 堆的最大值一致。 由直接内存导致的内存溢出,一个明显的特征是在 Heap Dump 文件中不会看见有什么明显的异常情况,如果发现内存溢出后产生的Dump 文件很小,而程序中又直接或间接使用了直接内存(典型的间接使用就是 NIO),那么就可以考虑检查直接内存方面的原因。 P6:判断对象是否是垃圾 在堆中存放着所有对象实例,垃圾收集器在对堆进行回收前,首先要判断对象是否还存活着。 引用计数算法 在对象中添加一个引用计数器,如果有一个地方引用它计数器就加1,引用失效时计数器就减1,如果计数器为0则该对象就是不再被使用的。该算法原理简单,效率也高,但是在 Java中很少使用,因为它存在对象之间互相循环引用的问题,导致计数器无法清零。 可达性分析算法 当前主流语言的内存管理子系统都是使用可达性分析算法来判断对象是否存活的。这个算法的基本思路就是通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象到GC Roots之间没有任何引用链相连,则此对象是不可能再被使用的。 可作为GC Roots的对象: 在虚拟机栈中引用的对象,如线程被调用的方法堆栈中的参数、局部变量等。 在方法区中类静态属性引用的对象,如类的引用类型静态变量。 在方法区中常量引用的对象,如字符串常量池中的引用。 在本地方法栈中 JNI 即 Native 方法引用的对象。 JVM 内部的引用,如基本数据类型对应的 Class 对象,一些常驻异常对象,系统类加载器等。 所有被 synchronized 同步锁持有的对象。 P7:引用类型 无论通过引用计数还是可达性分析判断对象是否存活,都和引用离不开关系。在 JDK1.2 之前引用的定义是:如果 reference 类型数据存储的数值代表另外一块内存的起始地址,那么就称该 reference 数据是代表某块内存、某个对象的引用。在 JDK 1.2之后 Java 对引用的概念进行了扩充,按强度分为四种: 强引用:最传统的引用定义,指代码中普遍存在的引用赋值。任何情况下只要强引用存在,垃圾收集器就永远不会回收被引用的对象。 软引用:描述一些还有用但非必需的对象。只被软引用关联的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围中进行二次回收,如果这次回收还没有足够的内存才会抛出 OOM 异常。 弱引用:描述非必需对象,引用强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器开始工作时无论当前内存是否足够都会回收只被弱引用关联的对象。 虚引用:也称幽灵引用或幻影引用,是最弱的引用关系。一个对象是否有虚引用存在,完全不会对其生存时间造成影响,也无法通过虚引用来取得一个对象实例。该引用的唯一目的就是为了能在这个对象被垃圾收集器回收时收到一个系统通知。 P8:GC 算法 标记-清除算法 原理:分为标记和清除两个阶段,首先标记出所有需要回收的对象,在标记完成之后统一回收掉所有被标记的对象,或者标记存活的对象并统一回收所有未被标记的对象。标记过程就是判断对象是否属于垃圾的过程。 特点:① 执行效率不稳定,如果堆中包含大量对象且其中大部分是需要被回收的,这时必须进行大量标记和清除,导致效率随对象数量增长而降低。② 内存空间碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时无法找出足够的连续内存而不得不提前触发另一次垃圾收集。 标记-复制算法 原理:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的空间用完了,就将还存活着的对象复制到另一块,然后再把已使用过的内存空间一次清理掉。 特点:① 实现简单、运行高效,解决了内存碎片问题。② 代价是将可用内存缩小为原来的一半,浪费了过多空间。 HotSpot 的新生代划分: 把新生代划分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次分配内存只使用 Eden 和其中一块 Survivor。发生垃圾收集时将 Eden 和 Survivor 中仍然存活的对象一次性复制到另一块 Survivor 上,然后直接清理掉 Eden 和已用过的那块 Survivor 空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,即每次新生代中可用空间为整个新生代的90%。 标记-整理算法 原理:标记-复制算法在对象存活率较高时要进行较多的复制操作,效率将会降低。并且如果不想浪费空间,就需要有额外空间进行分配担保,应对被使用内存中所有对象都100%存活的极端情况,所以老年代一般不使用此算法。老年代使用标记-整理算法,标记过程与标记-清除算法一样,只是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。 特点:标记-清除与标记-整理的本质差异在于前者是一种非移动式回收算法而后者是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险策略:① 如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,是一种极为负重的操作,而且这种移动必须全程暂停用户线程才能进行。② 如果不移动对象就会导致空间碎片问题,只能依赖更复杂的内存分配器和内存访问器来解决。所以是否移动对象都存在弊端,移动则内存回收时更复杂,不移动则内存分配时更复杂。 P9:垃圾收集器 经典垃圾收集器:指 JDK 11之前的全部可用垃圾收集器。 Serial 最基础、历史最悠久的收集器,该收集器是一个使用复制算法的单线程工作收集器,单线程的意义不仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调它进行垃圾收集时必须暂停其他所有工作线程直到收集结束。 Serial 是虚拟机运行在客户端模式下的默认新生代收集器,优点是简单高效,对于内存受限的环境它是所有收集器中最小的;对于单核处理器或处理器核心较少的环境来说,Serial 收集器由于没有线程交互开销,因此可获得最高的单线程收集效率。 ParNew 实质上是 Serial 的多线程版本,除了使用多线程进行垃圾收集外其余行为完全一致。 ParNew 是虚拟机运行在服务端模式下的默认新生代收集器,一个重要原因是除了 Serial 外只有它能与 CMS 配合。自从 JDK 9 开始,ParNew 加 CMS 收集器的组合就不再是官方推荐的服务端模式下的收集器解决方案了,官方希望他能被 G1 完全取代。 Parallel Scavenge 新生代收集器,基于标记-复制算法,是可以并行的多线程收集器,与 ParNew 类似。 特点是它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能缩短收集时用户线程的停顿时间,而 Parallel Scavenge 的目标是达到一个可控制的吞吐量,吞吐量就是处理器用于运行用户代码的时间与处理器消耗总时间的比值。自适应调节策略也是它区别于 ParNew 的一个重要特性。 Serial Old Serial 的老年代版本,同样是一个单线程收集器,使用标记-整理算法。 Serial Old 是虚拟机在客户端模式下的默认老年代收集器,用于服务端有两种用途:一种是 JDK 5 及之前与 Parallel Scavenge 搭配使用,另一种是作为CMS 发生失败时的预案。 Parellel Old Parallel Scavenge 的老年代版本,支持多线程收集,基于标记-整理算法实现。这个收集器直到 JDK 6 才开始提供,在注重吞吐量优先的场景可以有效考虑Parallel Scavenge 加 Parallel Old 组合。 CMS 以获取最短回收停顿时间为目标的收集器,如果希望系统停顿时间尽可能短以给用户带来更好的体验就可以使用 CMS。 基于标记-清除算法,过程相对复杂,分为四个步骤:初始标记、并发标记、重新标记、并发清除。 其中初始标记和重新标记仍然需要 STW(Stop The World,表示系统停顿),初始标记仅是标记 GC Roots 能直接关联到的对象,速度很快。并发标记就是从 GC Roots 的直接关联对象开始遍历整个对象图的过程,耗时较长但不需要停顿用户线程,可以与垃圾收集线程并发运行。重新标记则是为了修正并发标记期间因用户程序运作而导致标记产生变动的那一部分对象的标记记录,该阶段停顿时间比初始标记稍长,但远比并发标记短。最后是并发清除,清理标记阶段判断的已死亡对象,由于不需要移动存活对象,因此该阶段也可以与用户线程并发。 由于整个过程中耗时最长的并发标记和并发清除阶段中,垃圾收集器都可以和用户线程一起工作,所以从总体上说CMS 的内存回收过程是与用户线程并发执行的。 CMS 是 HotSpot 追求低停顿的第一次成功尝试,但还存在三个明显缺点:① 对处理器资源非常敏感,在并发阶段虽然不会导致用户线程暂停,但会降低总吞吐量。② 无法处理浮动垃圾,有可能出现并发失败而导致另一次 FullGC。③ 由于基于标记-清除算法,因此会产生大量空间碎片,给大对象分配带来麻烦。 G1 开创了收集器面向局部收集的设计思路和基于Region的内存布局,是一款主要面向服务端的收集器,最初设计目标是替换CMS。 G1 之前的收集器,垃圾收集的目标要么是整个新生代,要么是整个老年代或整个堆。而 G1 可以面向堆内存任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收受益最大,这就是 G1 的 MixedGC 模式。 不再坚持固定大小及数量的分代区域划分,而是把 Java 堆划分为多个大小相等的独立区域(Region),每一个 Region 都可以根据需要扮演新生代的 Eden 空间、Survivor 空间或老年代空间。收集器能够对扮演不同角色的 Region 采用不同的策略处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。 跟踪各个 Region 里面的垃圾堆积的价值大小,价值即回收所获得的空间大小以及回收所需时间的经验值,在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值收益最大的 Region。这种回收方式保证了 G1 在有限的时间内获取尽可能高的收集效率。 G1的运作过程: 初始标记:标记 GC Roots 能直接关联到的对象并修改 TAMS 指针的值,让下一阶段用户线程并发运行时能正确地在可用 Region 中分配新对象。该阶段需要 STW 但耗时很短,是借用进行 MinorGC 时同步完成的。 并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆的对象图,找出需要回收的对象。该阶段耗时长,但可与用户线程并发执行,当对扫描完成后还要重新处理 SATB 记录的在并发时有引用变动的对象。 最终标记:对用户线程做一个短暂暂停,用于处理并发阶段结束后仍遗留下来的少量 SATB 记录。 筛选回收:对各个 Region 的回收价值和成本排序,根据用户期望的停顿时间指定回收计划,可自由选择任意多个 Region 构成回收集然后把决定回收的那一部分的存活对象复制到空的 Region 中,再清理掉整个旧的 Region 的全部空间。该操作必须暂停用户线程,由多条收集器线程并行完成。 可以由用户指定期望的停顿时间是 G1 的一个强大功能,但该值不能设得太低,一般设置为100~300毫秒比较合适。G1不会存在内存空间碎片的问题,但 G1 为了垃圾收集产生的内存占用和程序运行时的额外执行负载都高于CMS。 低延迟垃圾收集器:指 Shenandoah 和 ZGC,这两个收集器几乎整个工作过程全都是并发的,只有初始标记、最终标记这些阶段有短暂停顿,停顿的时间基本上是固定的。 Shenandoah 相比 G1 内存布局同样基于 Region,默认回收策略也是优先处理回收价值最大的 Region。但在管理堆内存方面,与 G1 有不同:① 支持并发整理,G1 的回收阶段不能与用户线程并发。②默认不使用分代收集,不会有专门的新生代 Region 或老年代 Region。③ 摒弃了在 G1 中耗费大量内存和计算资源去维护的记忆集,改用名为连接矩阵的全局数据结构来记录跨 Region 的引用关系。 ZGC JDK11中新加入的具有实验性质的低延迟垃圾收集器,和 Shenandoah 的目标高度相似,都希望在尽可能对吞吐量影响不大的前提下实现在任意堆大小下都可以把垃圾收集器的停顿时间限制在 10ms 以内的低延迟。 基于 Region 内存布局,不设分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理,以低延迟为首要目标。内存布局也采用基于 Region 的堆内存布局,但不同的是 ZGC 的 Region 具有动态性,是动态创建和销毁的,并且区容量大小也是动态变化的。 P10:内存分配与回收策略 以 Seial + Serial Old 客户端默认收集器组合为例: 对象优先在 Eden 区分配 大多数情况下对象在新生代 Eden 区分配,当 Eden 区没有足够空间进行分配时虚拟机将发起一次 MinorGC。 可通过 -XX:Xms 和 -XX:Xmx 设置堆大小, -Xmn 设置新生代的大小, -XX:SurvivorRatio 设置新生代中 Eden 和 Survivor的比例。 大对象直接进入老年代 大对象是指需要大量连续内存空间的对象,最典型的是很长的字符串或者元素数量很庞大的数组。大对象容易导致内存明明还有不少空间时就提前触发垃圾收集以获得足够的连续空间才能安置它们,当复制对象时大对象就意味着高额内存复制开销。 HotSpot 提供了 -XX:PretenureSizeThreshold 参数,大于该值的对象直接在老年代分配,避免在 Eden 和 Survivor 之间来回复制产生大量内存复制操作。 长期存活对象进入老年代 虚拟机给每一个对象定义了一个对象年龄计数器,存储在对象头。对象通常在 Eden 诞生,如果经历过第一次 MinorGC 仍然存活并且能被 Survivor 容纳,该对象就会被移动到 Survivor 中并将年龄设置为 1。对象在 Survivor 中每熬过一次 MinorGC 年龄就加 1 ,当增加到一定程度(默认15)就会被晋升到老年代。对象晋升老年代的年龄阈值可通过 -XX:MaxTenuringThreshold 设置。 动态对象年龄判定 为了适应不同程序的内存状况,虚拟机并不永远要求对象年龄达到阈值才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 的一半,年龄不小于该年龄的对象就可以直接进入老年代,无需等到 -XX:MaxTenuringThreshold 参数设置的年龄。 空间分配担保 发生 MinorGC 前,虚拟机必须先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次 MinorGC可以确定是安全的。 如果不成立,虚拟机会先查看 -XX:HandlePromotionFailure 参数的值是否允许担保失败,如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果满足将冒险尝试一次 MinorGC,如果不满足或不允许担保失败就会改成一次 FullGC。 之所以说冒险是因为新生代使用复制算法,为了内存利用率只使用其中一个 Survivor 作为备份,因此当出现大量对象在 MinorGC 后仍然存活的情况时需要老年代进行分配担保,把 Survivor 无法容纳的对象直接送入老年代。 P11:故障处理工具 jps:虚拟机进程状况工具 jps 即 JVM Process Status,参考了 UNIX 命令的命名格式,功能和 ps 命令类似:可以列出正在运行的虚拟机进程,并显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID(LVMID)。LVMID 与操作系统的进程 ID(PID)是一致的,使用 Windows 的任务管理器或 UNIX 的 ps 命令也可以查询到虚拟机进程的 LVMID,但如果同时启动了多个虚拟机进程,无法根据进程名称定位就必须依赖 jps 命令。 jps 还可以通过 RMI 协议查询开启了 RMI 服务的远程虚拟机进程状态,参数 hostid 为 RMI 注册表中注册的主机名。 jstat:虚拟机统计信息监视工具 jstat 即 JVM Statistic Monitoring Tool,是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译器等运行时数据,在没有 GUI 界面的服务器上是运行期定位虚拟机性能问题的常用工具。 一些参数的含义:S0 和 S1 表示两个 Survivor 区,E 表示新生代,O 表示老年代,YGC 表示 Young GC 次数,YGCT 表示Young GC 耗时,FGC 表示 Full GC 次数,FGCT 表示 Full GC 耗时,GCT 表示所有 GC 总耗时。 jinfo:Java 配置信息工具 jinfo 表示 Configuration Info for Java,作用是实时查看和调整虚拟机各项参数,使用 jps 的 -v 参数可以查看虚拟机启动时显式指定的参数列表,但如果想知道未被显式指定的参数的系统默认值就只能使用 jinfo 的 -flag 选项进行查询。jinfo 还可以把虚拟机进程的 System.getProperties() 的内容打印出来。 jmap:Java 内存映像工具 jmap 表示 Memory Map for Java,jamp 命令用于生成堆转储快照,还可以查询 finalize 执行队列、Java 堆和方法区的详细信息,如空间使用率,当前使用的是哪种收集器等。和 jinfo 命令一样,有部分功能在 Windows 平台下受限,除了生成堆转储快照的 -dump 选项和用于查看每个类实例的 -histo 选项外,其余选项都只能在 Linux 使用。 jhat:虚拟机堆转储快照分析工具 jhat 表示 JVM Heap Analysis Tool,JDK 提供 jhat 命令与 jmap 搭配使用来分析 jamp 生成的堆转储快照。jhat 内置了一个微型的 HTTP/Web 服务器,生成堆转储快照的分析结果后可以在浏览器中查看。 jstack:Java 堆栈跟踪工具 jstack 表示 Stack Trace for Java,jstack 命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等。线程出现停顿时通过 jstack 查看各个线程的调用堆栈,就可以获知没有响应的现场到底在后台做什么或等待什么资源。 除了上述的基础故障处理工具,还有一些可视化故障处理工具,例如 JHSDB 基于服务性代理的调试工具、JConsole Java 监视与管理控制台、VisualVM 多合一故障处理工具、Java Mission Control 可持续在线监控工具。 P12:类加载机制和初始化时机 在 Class 文件中描述的各类信息最终都需要加载到虚拟机后才能运行和使用。JVM 把描述类的数据从 Class 文件加载到内存,并对数据进行校验、解析和初始化,最终形成可以被虚拟机直接使用的 Java类型,这个过程被称为虚拟机的类加载机制。与其他在编译时需要连接的语言不同,Java 中类型的加载、连接和初始化都是在程序运行期间完成的,这种策略让 Java 进行类加载时增加了性能开销,但却为 Java 应用提供了极高的扩展性和灵活性,Java 可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。 一个类型从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期将会经历加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、解析和初始化三个部分统称为连接。加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 语言的动态绑定特性。 关于何时需要开始类加载的第一个阶段"加载",《 Java 虚拟机规范》没有强制约束,但对于初始化严格规定了有且只有6种情况: 遇到 new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类型没有初始化则需要先触发初始化。典型场景有:① 使用new关键字实例化对象。② 读取或设置一个类型的静态字段。③ 调用一个类型的静态方法。 对类型进行反射调用时,如果类型没有初始化则需要先触发初始化。 当初始化类时,如果其父类没有初始化则需要先触发父类的初始化。 当虚拟机启动时,用户需要指定一个要执行的主类即包含 main 方法的类,虚拟机会先初始化该类。 当使用 JDK 7 新加入的动态语言支持时,如果 MethodHandle 实例的解析结果为指定类型的方法句柄且这个句柄对应的类没有进行过初始化,则需要先触发其初始化。 当一个接口定义了默认方法时,如果该接口的实现类发生初始化,那接口要在其之前初始化。 除了这六种情况外其余所有引用类型的方式都不会触发初始化,称为被动引用。被动引用的实例:① 子类使用父类的静态字段时,只有直接定义这个字段的父类会被初始化。② 通过数组定义使用类。③ 常量在编译期会存入调用类的常量池,不会初始化定义常量的类。 接口的加载过程和类真正有所区别的是当初始化类时,如果其父类没有初始化则需要先触发其父类的初始化,但在一个接口初始化时并不要求其父接口全部完成了初始化,只有在真正使用到父接口时(如引用接口中定义的常量)才会初始化。 P13:类加载过程 加载 加载是类加载的第一个阶段,在该阶段虚拟机需要完成三件事:① 通过一个类的全限定类名来获取定义此类的二进制字节流。② 将这个字节流所代表的静态存储结构转化为方法区的运行时数据区结构。③ 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。 加载结束后,虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区中了,方法区中的数据存储格式完全由虚拟机自定义实现。类型数据安置在方法区之后,会在 Java 堆中实例化一个 Class 对象,这个对象将作为程序员访问方法区中类型数据的外部接口。加载与连接的部分动作是交叉进行的,加载尚未完成时连接可能已经开始。 验证 验证是连接的第一步,目的是确保 Class 文件的字节流中包含的信息符合约束要求,保证这些信息不会危害虚拟机的安全。Java 语言本身是安全的,但如果虚拟机不检查输入的字节流,对其完全信任的话,很可能因为载入了有错误或有恶意企图的字节码流而导致整个系统受攻击甚至崩溃。 验证主要包含了四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。 验证对于虚拟机的类加载机制来说是一个非常重要但非必需的阶段,因为验证只有通过与否的区别,只要通过了验证其后就对程序运行期没有任何影响了。如果程序运行的全部代码都已被反复使用和验证过,在生产环境的就可以考虑关闭大部分类验证措施缩短类加载时间。 准备 准备是正式为类变量分配内存并设置零值的阶段,该阶段进行的内存分配仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。如果变量被final修饰,编译时 Javac 会为变量生成 ConstantValue 属性,那么在准备阶段虚拟机就会将该变量的值设为程序员指定的值。 解析 解析是将常量池内的符号引用替换为直接引用的过程。 符号引用:符号引用以一组符号描述引用目标,可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。与虚拟机内存布局无关,引用目标并不一定是已经加载到虚拟机内存中的内容。 直接引用:直接引用是可以直接指向目标的指针、相对偏移量或者能间接定位到目标的句柄。和虚拟机的内存布局直接相关,引用目标必须已在虚拟机的内存中存在。 解析部分主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符合引用进行。 初始化 初始化是类加载过程的最后一步,直到该阶段,JVM 才真正开始执行类中编写的代码。 准备阶段时变量已经赋过一次系统零值,而在初始化阶段会根据程序员的编码去初始化类变量和其他资源。 初始化阶段就是执行类构造器 <client> 方法的过程,该方法是 Javac 编译器自动生成的。 P14:类加载器和双亲委派模型 类加载阶段中"通过一个类的全限定名来获取描述该类的二进制字节流"的动作被设计为放到 JVM 外部实现,以便让应用程序自己决定如何获取所需的类,实现这个动作的代码就是类加载器。 比较两个类是否相等:对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。只有在两个类是由同一个类加载器加载的前提下才有意义,否则即使两个类来源于同一个 Class 文件,被同一个 JVM 加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 从 JVM 的角度看只存在两种不同的类加载器:一种是启动类加载器,由 C++ 语言实现,是虚拟机自身的一部分;另一种是其他所有类加载器,由 Java 语言实现,独立存在于虚拟机外部且全部继承自抽象类 java.lang.ClassLoader。 自 JDK1.2 起 Java 一直保持着三层类加载器、双亲委派的类加载结构。 启动类加载器:负载加载存放在 JAVA_HOME/lib 目录,或者指定路径中存放的能够被虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,如果用户需要把加载请求委派给启动类加载器,直接使用 null 代替即可。 扩展类加载器:负载加载 JAVA_HOME/lib/ext 目录,或者系统变量所指定的路径中的类库。这种扩展机制在 JDK 9 后被模块化所取代,由于扩展类加载器是由 Java 编写的,开发者可以直接在程序中使用扩展类加载器来加载 Class 文件。 应用程序类加载器:也称系统类加载器,负载加载用户类路径上的所有类库,同样可以直接在代码中使用。如果应用程序中没有自定义类加载器,一般情况下该类加载器就是程序中默认的类加载器。 双亲委派模型 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应该有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承关系来实现的,而通常使用组合关系来复用父加载器的代码。 如果一个类加载器收到了类加载请求,它不会自己去尝试加载这个类,而首先将该请求委派给自己的父加载器完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成请求时,子加载器才会尝试自己完成加载。 好处是 Java 中的类跟随它的类加载器一起具备了一种带有优先级的层次关系,可以保证某个类在程序的各个类加载器环境中都是同一个类,对于保证程序的稳定运行极为重要。 P15:Java 程序运行的过程 通过 Javac 编译器将 .java 代码转为 JVM 可以加载的 .class 字节码文件。 Javac 编译器是由 Java 语言编写的程序,从 Javac 代码的总体结构看,编译过程可以分为 1 个准备过程和 3 个处理过程:① 准备过程:初始化插入式注解处理器。② 解析与填充符号表过程:进行词法、语法分析,将源代码的字符流转为标记集合,构造出抽象语法树。填充符号表,产生符号地址和符号信息。③ 插入式注解处理器的注解处理过程。④ 分析与字节码生成过程,包括标注检查,对语法的静态信息进行检查;数据流及控制流分析,对程序动态运行过程进行检查;解语法糖,将简化代码编写的语法糖还原为原有的形式;字节码生成,将前面各个步骤的信息转换为字节码。 Javac 属于前端编译器,完成了从程序到抽象语法树或中间字节码的生成,在此之后还有一组内置于 JVM 内部的后端编译器,即即时编译器或提前编译器,来完成代码优化以及从字节码生成本地机器码的过程。 通过即时编译器 JIT 把字节码文件编译成本地机器码。 Java 程序最初都是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为"热点代码",热点代码的检测主要有基于采样和基于计数器两种方式,为了提高热点代码的执行效率,在运行时虚拟机会把这些代码编译成本地机器码,并尽可能对代码优化,在运行时完成这个任务的后端编译器被称为即时编译器。 客户端编译器的执行过程:① 平***立的前端将字节码构造成一种高级中间代码表示 HIR。② 平台相关的后端从 HIR 中产生低级中间代码表示 LIR。③ 在平台相关的后端使用线性扫描算法在 LIR 上分配寄存器,并在 LIR 上做窥孔优化,然后产生机器代码。 服务端编译器专门面向服务端的典型应用场景,并为服务器的性能配置针对性调整过的编译器,也是一个能容忍很高优化复杂度的高级编译器,它会执行大部分经典的优化动作,如无用代码消除、循环表达式外提、消除公共子表达式、基本块重排序等,还会实施一些与 Java 语言特性相关的优化,如范围检查消除、空值检查消除等,也可能根据解释器或客户端编译器提供的性能监控信息进行一些不稳定的预测性激进优化。 还可以通过静态的提前编译器 AOT 直接把程序编译成与目标机器指令集相关的二进制代码。 并发 20 P1:Java 内存模型 Java 线程的通信由 JMM 控制,JMM 的主要目的是定义程序中各种变量的访问规则,关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节。此处的变量包括实例字段、静态字段和构成数组元素的对象,但不包括局部变量与方法参数,因为它们是线程私有的,不存在多线程竞争问题。为了获得更好的执行效率,JMM 没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器是否要进行调整代码执行顺序这类优化措施,JMM 遵循一个基本原则:只要不改变程序执行结果,编译器和处理器怎么优化都行。例如编译器分析某个锁只会单线程访问就消除该锁,某个 volatile 变量只会单线程访问就把它当作普通变量。 JMM 规定了所有变量都存储在主内存中,每条线程还有自己的工作内存,工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作都必须在工作空间中进行,而不能直接读写主内存中的数据。不同线程之间也无法直接访问对方工作内存中的变量,两个线程之间的通信必须经过主内存,JMM 通过控制主内存与每个线程的工作内存之间的交互来提供内存可见性保证。 关于主内存与工作内存之间的交互,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存等实现细节,JMM 定义了 8 种原子操作: lock:作用于主内存变量,把变量标识为一条线程独占的状态。 unlock:作用于主内存变量,把处于锁定状态的变量释放出来,释放后的变量才能被其他线程锁定。 read:作用于主内存变量,把变量值从主内存传到工作内存。 load:作用于工作内存变量,把 read 从主存中得到的值放入工作内存的变量副本。 use:作用于工作内存变量,把工作内存中的变量值传给执行引擎,每当虚拟机遇到需要使用变量值的字节码指令时执行该操作。 assign:作用于工作内存变量,把从执行引擎接收的值赋给工作内存变量,每当虚拟机遇到给变量赋值的字节码指令时执行该操作。 store:作用于工作内存变量,把工作内存中的变量值传送到主内存。 write:作用于主内存变量,把 store 从工作内存取到的变量值放入主内存变量中。 如果要把一个变量从主内存拷贝到工作内存,就要按顺序执行 read 和 load ,如果要把变量从工作内存同步回主内存,就要按顺序执行 store 和 write 。JMM 只要求这两种操作必须按顺序执行,但不要求连续,也就是说 read 和 load、store 和 write 之间可插入其他指令。这种定义十分严谨但过于复杂,之后 Java 将内存操作简化为 lock、unlock、read 和 write 四种,但这只是语言描述上的等价化简。 P2:as-if-serial 和 happens-before as-if-serial as-if-serial 的语义是:不管怎么重排序,单线程程序的执行结果不能被改变,编译器和处理器必须遵循 as-if-serial 语义。 为了遵循 as-if-serial 语义,编译器和处理器不会对存在数据依赖关系的操作重排序,因为这种重排序会改变执行结果。但是如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。 as-if-serial 语义把单线程程序保护了起来,给了程序员一种幻觉:单线程程序是按程序的顺序执行的,使程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。 happens-before 先行发生原则,是 JMM 中定义的两项操作之间的偏序关系,它是判断数据是否存在竞争,线程是否安全的重要手段。 JMM 将 happens-before 要求禁止的重排序按是否会改变程序执行结果分为两类。对于会改变结果的重排序 JMM 要求编译器和处理器必须禁止这种重排序,对于不会改变结果的重排序,JMM 对编译器和处理器不做要求。 JMM 存在一些天然的 happens-before 关系,无需任何同步器协助就已经存在。如果两个操作的关系不在此列,并且无法从这些规则推导出来,它们就没有顺序性保障,虚拟机可以对它们随意进行重排序。 程序次序规则:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。 管程锁定规则:一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。 volatile 规则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。 线程启动规则:线程对象的 start 方法先行发生于此线程的每一个动作。 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。 线程中断规则:对线程 interrupt 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。 对象终结规则:一个对象的初始化完成先行发生于它的 finalize 方法的开始。 传递性:如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C 。 区别 as-if-serial 保证单线程程序的执行结果不被改变,happens-before 保证正确同步的多线程程序的执行结果不被改变。这两种语义的目的都是为了在不改变程序执行结果的前提下尽可能提高程序执行的并行度。 P3:指令重排序 重排序指从源代码到指令序列的重排序,在执行程序时为了提高性能,编译器和处理器通常会对指令进行重排序,分为三种: 编译器优化的重排序:编译器在不改变单线程程序语义的前提下可以重排语句的执行顺序。 指令级并行的重排序:如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作操作看上去可能是乱序执行。 从 Java 源代码到最终实际执行的指令序列,会分别经历编译器优化重排序、指令级并行重排序和内存系统重排序,这些重排序可能会导致多线程程序出现内存可见性问题。 对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器重排序,JMM 的处理器重排序规则会要求 Java 编译器在生成指令序列时,插入特定类型的内存屏障指令,即一组用于实现对内存操作顺序限制的处理器指令,通过内存屏障指令来禁止特定类型的处理器重排序。JMM 属于语言级的内存模型,它确保在不同的编译器和处理器平台上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。 P4:原子性、可见性和有序性 原子性 由 JMM 直接保证的原子性变量操作包括 read、load、assign、use、store 和 write,基本数据类型的访问都是具备原子性的,例外就是 long 和 double 的非原子性协定,允许虚拟机将没有被 volatile 修饰的 64 位数据的操作划分为两次 32 位的操作。 如果应用场景需要更大范围的原子性保证,JMM 还提供了 lock 和 unlock 操作满足这种需求,尽管 JVM 没有把这两种操作直接开放给用户使用,但是却提供了更高层次的字节码指令 monitorenter 和 monitorexit 来隐式地使用这两个操作,这两个字节码指令反映到 Java 代码中就是 synchronized 关键字。 可见性 可见性就是指当一个线程修改了共享变量的值时,其他线程能够立即得知修改。JMM 通过在变量修改后将值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式实现可见性,无论是普通变量还是volatile变量都是如此,区别是 volatile 保证新值能立即同步到主内存以及每次使用前立即从主内存刷新,因此说 volatile 保证了多线程操作变量的可见性,而普通变量则不能保证。 除了 volatile 之外,还有两个关键字能实现可见性,分别是 synchronized 和 final,同步块的可见性是由"对一个变量执行unlock 前必须先把此变量同步回主内存中,即先执行 store 和 write"这条规则获得的。final 的可见性是指:被 final 修饰的字段在构造器中一旦被初始化完成,并且构造器没有把"this"引用传递出去,那么其他线程就能看到 final 字段的值。 有序性 有序性可以总结为:在本线程内观察所有操作是有序的,在一个线程内观察另一个线程,所有操作都是无序的。前半句是指"as-if-serial 语义",后半句是指"指令重排序"和"工作内存与主内存同步延迟"现象。 Java 提供了 volatile 和 synchronized 保证线程间操作的有序性,volatile 本身就包含了禁止指令重排序的语义,而 synchronized 则是由"一个变量在同一个时刻只允许一条线程对其进行lock操作"这条规则获得的,该规则决定了持有同一个锁的两个同步块只能串行进入。 P5:volatile 关键字 JVM 提供的最轻量级的同步机制,JMM 为 volatile 定义了一些特殊的访问规则,当一个变量被定义为 volatile 后具备两种特性: 保证此变量对所有线程的可见性 可见性是指当一条线程修改了这个变量的值,新值对于其他线程来说是立即可以得知的。而普通变量并不能做到这一点,普通变量的值在线程间传递时均需要通过主内存来完成。 volatile 变量在各个线程的工作内存中不存在一致性问题,但 Java 的运算操作符并非原子操作,这导致 volatile 变量运算在并发下仍是不安全的。 禁止指令重排序优化 使用 volatile 变量进行写操作,汇编指令操作是带有 lock 前缀的,相当于一个内存屏障,后面的指令不能重排到内存屏障之前的位置。只有一个处理器时不需要使用内存屏障,但如果有两个或更多的处理器访问同一块内存,且其中有一个在观测另一个,就需要使用内存屏障来保证一致性了。 使用 lock 前缀的指令在多核处理器中会引发两件事:① 将当前处理器缓存行的数据写回到系统内存。② 这个写回内存的操作会使其他在CPU里缓存了该内存地址的数据无效。 这种操作相当于对缓存中的变量做了一次 store 和 write 操作,可以让 volatile 变量的修改对其他处理器立即可见。 静态变量 i 执行多线程 i++ 的不安全问题 通过反编译会发现一个自增语句是由 4 条字节码指令构成的,依次为getstatic、iconst_1、iadd、putstatic,当getstatic把 i 的值取到操作栈顶时,volatile保证了 i 的值在此刻是正确的,但是在执行iconst_1、iadd这些指令时,其他线程可能已经改变了i的值,而操作栈顶的值就变成了过期的数据,所以 putstatic 指令执行后就可能把较小的 i 值同步回了主内存。 即使编译出来只有一条字节码指令也不能意味着这条指令就是一个原子操作,一条字节码指令在解释执行时,解释器要运行很多行代码才能实现它的语义。如果是编译执行,一条字节码指令也可能转化成若干条本地机器码指令。 适用场景 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。 变量不需要与其他状态变量共同参与不变约束。 volatile的内存语义 从内存语义角度来说,volatile的写-读与锁的释放-获取具有相同的内存效果。 写内存语义:当写一个volatile变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。 读内存语义:当读一个volatile变量时,JMM 会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。 volatile指令重排序的特点 当第二个操作是volatile 写时,不管第一个操作是什么都不能重排序,确保写之前的操作不会被编译器重排序到写之后。 当第一个操作是volatile 读时,不管第二个操作是什么都不能重排序,确保读之后的操作不会被编译器重排序到读之前。 当第一个操作是volatile 写,第二个操作是 volatile 读时不能重排序。 JSR-133 增强 volatile 语义的原因 在旧的内存模型中,虽然不允许 volatile 变量之间重排序,但允许 volatile 变量与普通变量重排序,可能导致内存不可见问题。为了提供一种比锁更轻量级的线程通信机制,严格限制了编译器和处理器对 volatile 变量与普通变量的重排序,确保 volatile 的写-读和锁的释放-获取具有相同的内存语义。 P6:final 关键字 final 可以保证可见性,被 final 修饰的字段在构造器中一旦被初始化完成,并且构造器没有把 this 的引用传递出去,那么在其他线程中就能看见 final 字段的值。 JSR-133 增强 final语义的原因 在旧的 JMM 中,一个严重的缺陷就是线程可能看到 final 域的值会改变。比如一个线程看到一个 int 类型 final 域的值为0,此时该值是还未初始化之前的零值,过一段时间之后该值被某线程初始化后这个线程再去读这个 final 域的值会发现值变为1。 为了修复该漏洞,JSR-133 通过为 final 域增加写和读重排序规则,提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造方法中没有逸出),那么不需要使用同步就可以保证任意线程都能看到这个final域在构造方法中被初始化之后的值。 写 final 域重排序规则 禁止把 final 域的写重排序到构造方法之外,编译器会在final域的写之后,构造方法的 return之前,插入一个Store Store屏障。该规则可以确保在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保障。 对于引用类型,增加了约束:在构造方法内对一个 final 引用的对象的成员域的写入,与随后在构造方法外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。 读 final 域重排序规则 在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM 禁止处理器重排序这两个操作。编译器会在读final域操作的前面插入一个Load Load 屏障,该规则可以确保在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用。 P7:synchronized 关键字 最基本的互斥同步手段就是 synchronized 关键字,它是一种块结构的同步语法。synchronized 经过 Javac 编译后,会在同步块的前后分别形成 monitorenter 和 monitorexit 两个字节码指令,这两个字节码指令都需要一个引用类型的参数来指明要锁定和解锁的对象,对于同步普通方法,锁是当前实例对象;对于静态同步方法,锁是当前类的Class对象;对于同步方法块,锁是 synchronized 括号里的对象。 根据《 Java 虚拟机规范》的要求,在执行 monitorenter 指令时,首先要去尝试获取对象的锁。如果这个对象没有被锁定,或者当前线程已经持有了那个对象的锁,那么就把锁的计数器的值增加 1,而在执行 monitorexit 指令时会将锁计数器的值减 1。一旦计数器的值为 0,锁随即就被释放了。如果获取锁对象失败,那当前线程就应该被阻塞等待,直到请求锁定的对象被持有它的线程释放为止。 被 synchronized 修饰的同步块对一条线程来说是可重入的,并且同步块在持有锁的线程执行完毕并释放锁之前,会无条件地阻塞后面其他线程的进入。从执行成本的角度看,持有锁是一个重量级的操作。在主流 JVM 实现中,Java 的线程是映射到操作系统的原生内核线程之上的,如果要阻塞或唤醒一条线程,则需要操作系统帮忙完成,这就不可避免陷入用户态到核心态的转换中,进行这些状态转换需要耗费很多的处理器时间。 每个 Java 对象都有一个关联的 monitor 监视器,当这个对象由同步块或同步方法调用时,执行方法的线程必须先获取到对象的监视器才能进入同步块或同步方法。例如有两个线程 A 和 B 竞争锁资源对应的 monitor,当线程 A 竞争到锁时,会将 monitor 中的 owner 设置为 A,把线程 B 阻塞并放到等待竞争资源的 ContentionList 队列。ContentionList 中的部分线程会进入 EntryList,EntryList 中的线程会被指定为 OnDeck 竞争候选者线程,如果获得了锁资源将进入 Owner 状态,释放锁资源后进入 !Owner 状态。被阻塞的线程会进入 WaitSet。 不公平的原因 所有收到锁请求的线程首先自旋,如果通过自旋也没有获取锁资源将被放入 ContentionList 队列,该做法对于已经进入队列的线程是不公平的。 为了防止 ContentionList 尾部的元素被大量线程进行 CAS 访问影响性能,Owner 线程会在释放锁时将 ContentionList 的部分线程移动到 EntryList 并指定某个线程为 OnDeck 线程,Owner 并没有将锁直接传递给 OnDeck 线程而是把锁竞争的权利交给它,该行为叫做竞争切换,牺牲了公平性但提高了性能。 P8:锁优化 JDK 6 对 synchronized 做了很多优化,引入了适应自旋、锁消除、锁粗化、偏向锁和轻量级锁等提高锁的效率,锁一共有 4 个状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,这种只能升级不能降级的锁策略是为了提高获得锁和释放锁的效率。 自旋锁与自适应自旋 互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给 JVM 的并发性能带来了很大压力。同时虚拟机开发团队也注意到了在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂机和恢复线程并不值得。现在绝大多数的个人电脑和服务器都是多核心处理器系统,如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程稍等一会,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环,这项技术就是所谓的自旋锁。 自旋锁在 JDK 1.4中就已经引入,只不过默认是关闭的,在 JDK 6中就已经改为默认开启了。自旋等待不能代替阻塞,自旋等待本身虽然避免了线程切换的开销,但它要占用处理器时间,所以如果锁被占用的时间很短,自旋的效果就会非常好,反之只会白白消耗处理器资源。因此自旋的时间必须有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程。自旋次数的默认次数是 10 次。 在 JDK 6 中对自旋锁的优化,引入了自适应自旋。自旋的时间不再是固定的了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过锁,那在以后要获取这个锁时将有可能之间省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行时间的增长以及性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越精准。 锁消除 锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上的数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。 锁粗化 原则上我们在编写代码时,总是推荐将同步块的作用范围限制得尽量小,只在共享数据得实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变少,即使存在锁竞争,等待锁得线程也能尽可能快拿到锁。 大多数情况下这种原则是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之外的,那么即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能消耗。 如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。 偏向锁 它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用 CAS 操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都去掉,连 CAS 操作都不去做了。 偏向锁的意思就是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。 当一个线程访问同步代码块并获取锁时,会在对象头和帧栈中的锁记录里存储锁偏向的线程 ID,以后该线程再进入和退出同步代码块不需要进行 CAS 操作来加锁和解锁,只需要简单地测试一下对象头的"Mark Word"里是否存储着指向当前线程的偏向锁。如果测试成功表示线程已经获得了锁,如果失败则需要再测试一下"Mark Word"中偏向锁的标识是否设置成了 1 即表示当前使用偏向锁,如果设置了就尝试使用 CAS 将对象头的偏向锁指向当前线程,否则使用 CAS 方式竞争锁。 偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销需要等待全局安全点即此时没有正在执行的字节码,它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态则将对象头设为无锁状态。如果线程还活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的"Mark Word"要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。 轻量级锁 轻量级是相对于操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就被称为重量级锁。轻量级锁并不是用来代替重量级锁的,它设计的初衷是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。 在代码即将进入同步块的时候,如果此同步对象没有被锁定,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的Mark Word的拷贝。然后虚拟机将使用 CAS 操作尝试把对象的 Mark Word 更新为指向锁记录的指针,如果这个更新操作成功了,即代表该线程拥有了这个对象的锁,并且锁标志位将转变为"00",表示此对象处于轻量级锁定状态。 如果这个更新操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是则说明当前线程以及拥有了这个对象的锁,直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态变为"10",此时Mark Word中存储的就是指向重量级锁的指针,后面等待锁的线程也必须进入阻塞状态。 解锁操作也同样是通过 CAS 操作来进行,如果对象的 Mark Word 仍然指向线程的锁记录,那就用 CAS 操作把对象当前的 Mark Word 和线程复制的 Mark Word 替换回来。假如能够替换成功,那整个同步过程就顺利完成了,如果替换失败,则说明有其他线程尝试过获取该锁,就要在释放锁的同时唤醒被挂起的线程。 偏向锁、轻量级锁和重量级锁的区别 偏向锁的优点是加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距,缺点是如果线程间存在锁竞争会带来额外锁撤销的消耗,适用于只有一个线程访问同步代码块的场景。 轻量级锁的优点是竞争的线程不会阻塞,提高了程序的响应速度,缺点是如果线程始终得不到锁会自旋消耗CPU,适用于追求响应时间和同步代码块执行非常快的场景。 重量级锁的优点是线程竞争不使用自旋不会消耗CPU,缺点是线程会被阻塞,响应时间很慢,适应于追求吞吐量、同步代码块执行较慢的场景。 P9:Lock 接口 自 JDK 5 起 Java类库提供了 juc 并发包,其中的 Lock 接口成为了一种全新的互斥同步手段。基于Lock 接口,用户能够以非块结构来实现互斥同步,从而摆脱了语言特性的束缚,改为在类库层面去实现同步。 重入锁 ReentrantLock 是 Lock 接口最常见的一种实现,它与 synchronized 一样是可重入的,在基本用法上也很相似,不过它增加了一些高级功能,主要包括以下三项: 等待可中断:是指持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待而处理其他事情。可中断特性对处理执行时间非常长的同步块很有帮助。 公平锁:是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的,ReentrantLock在默认情况下也是非公平的,但可以通过带有布尔值的构造器要求使用公平锁。不过一旦使用了公平锁,将会导致性能急剧下降,明显影响吞吐量。 锁绑定多个条件:是指一个 ReentrantLock 对象可以同时绑定多个 Condition 对象。在 synchronized中,锁对象的 wait 跟它的notify/notifyAll 方法配合可以实现一个隐含的条件,如果要和多于一个的条件关联时就不得不额外添加一个锁,而 ReentrantLock 可以多次调用 newCondition 方法。 一般优先考虑使用synchronized:① synchronized 是 Java 语法层面的同步,足够清晰和简单。② Lock 应该确保在 finally 中释放锁,否则一旦受同步保护的代码块中抛出异常,则有可能永远不会释放持有的锁。这一点必须由程序员自己来保证,而使用 synchronized 可以由 JVM 来确保即使出现异常锁也能被正常释放。③ 尽管在 JDK 5 时ReentrantLock 的性能领先于 synchronized,但在 JDK 6 进行锁优化之后,二者的性能基本能够持平。从长远来看 JVM 更容易针对synchronized进行优化,因为 JVM 可以在线程和对象的元数据中记录 synchronized 中锁的相关信息,而使用Lock的话 JVM 很难得知具体哪些锁对象是由特定线程持有的。 ReentrantLock 的可重入实现 以非公平锁为例,通过 nonfairTryAcquire 方法获取锁,该方法增加了再次获取同步状态的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求则将同步状态值进行增加并返回 true,表示获取同步状态成功。 成功获取锁的线程再次获取锁,只是增加了同步状态值,这就要求 ReentrantLock 在释放同步状态时减少同步状态值。如果该锁被获取了 n 次,那么前 n-1 次 tryRelease 方法必须都返回fasle,只有同步状态完全释放了才能返回 true,该方法将同步状态是否为 0 作为最终释放的条件,当同步状态为 0 时,将占有线程设置为null,并返回 true 表示释放成功。 对于非公平锁只要 CAS 设置同步状态成功则表示当前线程获取了锁,而公平锁则不同。公平锁使用 tryAcquire 方法,该方法与nonfairTryAcquire 的唯一区别就是判断条件中多了对同步队列中当前节点是否有前驱节点的判断,如果该方法返回 true 表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。、 P10:读写锁 ReentrantLock 是排他锁,在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一个读锁和一个写锁,通过分离读写锁使并发性相比一般的排他锁有了很大提升。 除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。只需要在读操作时获取读锁,写操作时获取写锁即可,当写锁被获取时后续的读写操作都会被阻塞,写锁释放之后所有操作继续执行,编程方式相对于使用等待/通知机制的实现方式变得简单。 读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。读写锁的自定义同步器需要在同步状态即一个整形变量上维护多个读线程和一个写线程的状态。如果在一个 int 型变量上维护多种状态,就一定要按位切割使用这个变量,读写锁将变量切分成了两个部分,高 16 位表示读,低 16 位表示写。 写锁是一个支持重入的排他锁,如果当前线程已经获得了写锁则增加写状态,如果当前线程在获取写锁时,读锁已经被获取或者该线程不是已经获得写锁的线程则当前线程进入等待状态。写锁的释放与 ReentrantLock 的释放过程类似,每次释放均减少写状态,当写状态为 0时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。 读锁是一个支持重入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问时,读锁总会被成功地获取,而所做的只是线程安全地增加读状态。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程获取则进入等待状态。读锁的每次释放均会减少读状态,减少的值是(1<<16),读锁的每次释放是线程安全的。 锁降级指的是写锁降级成为读锁,如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级指的是把持住当前拥有的写锁,再获取到读锁,随后释放先前拥有的写锁的过程。 锁降级中读锁的获取是必要的,主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程 A 获取了写锁修改了数据,那么当前线程是无法感知线程 A 的数据更新的。如果当前线程获取读锁,即遵循锁降级的步骤,线程 A 将会被阻塞,直到当前线程使用数据并释放读锁之后,线程 A 才能获取写锁进行数据更新。 P11:AQS 队列同步器 队列同步器是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 类型的成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作。 使用方式 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法 getState、setState 和 compareAndSetState 来进行操作,因为它们能够保证状态的改变是安全的。子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型地同步组件。 和锁的关系 同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所关注的领域。 同步队列 AQS 中每当有新的线程请求资源时,该线程都会进入一个等待队列,只有当持有锁的线程释放锁资源后该线程才能持有资源。等待队列通过双向链表实现,线程会被封装在链表的 Node 节点中,Node 的等待状态包括:CANCELLED 表示线程已取消、SIGNAL 表示线程需要唤醒、CONDITION 表示线程正在等待、PROPAGATE 表示后继节点会传播唤醒操作,只会在共享模式下起作用。 两种模式 独占模式表示锁会被一个线程占用,其他线程必须等到持有锁的线程释放锁后才能获取到锁继续执行,在同一时间内只能有一个线程获取到这个锁,ReentrantLock 就采用的是独占模式。 共享模式表示多个线程获取同一个锁的时候有可能会成功,ReadLock 就采用的是共享模式。 独占模式通过 acquire 和 release 方法获取和释放锁,共享模式通过 acquireShared 和 releaseShared 方法获取和释放锁。 独占式的获取和释放流程 在获取同步状态时,同步器调用 acquire 方法,维护一个同步队列,使用 tryAcquire 方法安全地获取线程同步状态,获取状态失败的线程会构造同步节点并通过 addWaiter 方法被加入到同步队列的尾部,并在队列中进行自旋。之后会调用 acquireQueued 方法使得该节点以死循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞节点被中断实现,移出队列或停止自旋的条件是前驱节点是头结点并且成功获取了同步状态。 在释放同步状态时,同步器调用 tryRelease 方法释放同步状态,然后调用 unparkSuccessor 方法(该方法使用 LockSupport 唤醒处于等待状态的线程)唤醒头节点的后继节点,进而使后继节点重新尝试获取同步状态。 只有当前驱节点是头节点时才能够尝试获取同步状态原因 头节点是成功获取到同步状态的节点,而头节点的线程释放同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点。 维护同步队列的FIFO原则,节点和节点在循环检查的过程中基本不相互通信,而是简单地判断自己的前驱是否为头节点,这样就使得节点的释放规则符合FIFO,并且也便于对过早通知的处理(过早通知是指前驱节点不是头结点的线程由于中断而被唤醒)。 共享式的获取和释放流程 在获取同步状态时,同步器调用 acquireShared 方法,该方法调用 tryAcquireShared 方法尝试获取同步状态,返回值为 int 类型,当返回值大于等于 0 时表示能够获取到同步状态。因此在共享式获取锁的自旋过程中,成功获取到同步状态并退出自旋的条件就是该方法的返回值大于等于0。 释放同步状态时,调用 releaseShared 方法,释放同步状态后会唤醒后续处于等待状态的节点。对于能够支持多线程同时访问的并发组件,它和独占式的主要区别在于 tryReleaseShared 方法必须确保同步状态安全释放,一般通过循环和 CAS 来保证,因为释放同步状态的操作会同时来自多个线程。 P12:线程 现代操作系统在运行一个程序时会为其创建一个进程,而操作系统调度的最小单位是线程,线程也叫轻量级进程。在一个进程中可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上告诉切换,让使用者感觉到这些线程在同时执行。 生命周期 ①NEW:初始状态,创建后还没有启动的线程处于这种状态,此时还没有调用 start 方法。②RUNNABLE:Java 线程将操作系统中的就绪和运行两种状态统称为 RUNNABLE,此时线程有可能正在等待操作系统分配CPU时间片,也有可能正在执行③BLOCKED:阻塞状态,阻塞状态与等待状态的区别是阻塞状态在等待一个排它锁,在程序等待进入同步区域时线程将进入这种状态。④WAITING:等待状态,表示线程进入等待状态,处于该状态的线程不会被分配CPU时间片,进入该状态表示当前线程需要等待其他线程通知或中断。会导致线程陷入等待状态的方法:没有设置超时参数的wait 和 join方法、LockSupport的 park 方法。⑤TIME_WAITING:限期等待状态,该状态不同于 WAITING,可以在指定时间内自行返回。会导致线程陷入限期等待状态的方法:设置了超时参数的 wait 和 join 方法、LockSupport 的 parkNanos 和 parkUntil 方法。⑥TERMINATED:终止状态,表示当前线程已经执行完毕或异常退出。 实现方式 ①继承 Thread 类并重写 run方法。优点是实现简单,缺点是不能继承其他类,功能单一。②实现 Runnable 接口并重写 run 方法,并将该实现类作为参数传入 Thread 构造器。优点是避免了单继承的局限性,适合多个相同程序代码的线程共享一个资源,实现解耦操作,代码和线程独立。③实现 Callable 接口并重写 call 方法,包装成 FutureTask 对象并作为参数传入Thread构造器。优点是可以获取线程执行结果的返回值。④可以通过线程池创建。 方法 ① wait是Object类的方法,调用wait方法的线程会进入WAITING状态,只有等待其他线程的通知或被中断后才会解除阻塞,调用wait方***释放锁资源。② sleep 是 Thread 类的方法,调用 sleep 方***导致当前线程进入休眠状态,与 wait 不同的是该方法不会释放锁资源,进入的是 TIMED-WAITING 状态。③ yiled 方***使当前线程让出 CPU 时间片给优先级相同或更高的线程,回到 RUNNABLE 状态,与其他线程一起重新竞争CPU时间片。④ join 方法用于等待其他线程运行终止,如果当前线程调用了另一个线程的 join 方法,则当前线程进入阻塞状态,当另一个线程结束时当前线程才能从阻塞状态转为就绪态,等待获取CPU时间片。底层使用的是wait,也会释放锁。 守护线程 守护线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作,当 JVM 中不存在非守护线程时,JVM 将会退出,可以通过 setDaemon(true) 将线程设置为daemon线程,但必须在线程启动之前设置。守护线程被用于完成支持性工作,但是在 JVM 退出时守护线程中的 finally 块并不一定会被执行,因为当 JVM 中没有非守护线程时需要立即退出,所有守护线程都将立即终止,因此不能依靠 finally 确保执行关闭或清理资源的逻辑。 P13:线程间通信 通信是指线程之间以何种机制来交换信息,在命令式编程中线程之间的通信机制有两种,共享内存和消息传递。在共享内存的并发模型里线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息传递的并发模型里线程之间没有公共状态,线程之间必须通过发送消息来显示通信。Java 并发采用共享内存模型,线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。 volatile 和 synchronized 关键字 volatile 可以修饰字段,告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回主内存,它能保证所有线程对变量访问的可见性。 synchronized 可以修饰方法或以同步块的形式使用,它主要确保多个线程在同一个时刻只能有一个线程处于方法或同步块中,保证了线程对变量访问的可见性和排他性。 等待/通知机制 等待通知机制是指一个线程 A 调用了对象 O 的 wait 方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify 或 notifyAll 方法,线程 A 收到通知后从 wait 方法返回,进而执行后序操作。两个线程通过对象 O 完成交互,对象上的 wait 和 notify/notifyAll 就如同开关信号,用来完成等待方和通知方之间的交互工作。 管道 IO 流 管道 IO 流和普通文件IO 流或网络 IO 流的不同之处在于它主要用于线程之间的数据传输,传输的媒介为内存。管道流主要包括了四种具体实现:PipedOutputStream、PipedInputStream、PipedReader 和 PipedWriter,对于 Piped 类型的流必须要通过 connect 方法进行绑定。 Thread.join 如果一个线程执行了某个线程的 join 方法,这个线程就会阻塞等待执行了 join 方法的线程终止之后才返回,这里涉及了等待/通知机制。join 方法的底层是通过 wait 方法实现的,当线程终止时会调用自身的 notifyAll 方法,通知所有等待在该线程对象上的线程。 ThreadLocal ThreadLoacl 是线程变量,内部以 ThreadLocal 为键,任意对象为值的存储结构实现。该结构绑定在每个线程上,存储的值在每个线程中都是一个唯一副本,每个线程可以通过 ThreadLocal 对象访问自己唯一的值。 这种存储结构叫 ThreadLocalMap ,是 ThreadLocal 的一个静态内部类,是一个弱引用集合,它的存值、取值实现类似于 HashMap,使用 set 设置值,使用 get 获取值。使用弱引用的目的是为了节约资源,如果执行过程中发生了 GC,ThreadLocal 是 null 但由于 ThreadLocalMap 生命周期和线程一样,不会被回收,这时候就会导致 ThreadLocalMap 的 key 不存在而 value 还在的内存泄漏问题,解决办法是使用完 ThreadLocal 后执行remove操作。 P14:ConcurrentHashMap JDK 8 之前 ConcurrentHashMap 用于解决 HashMap 的线程不安全和 HashTable 的并发效率低下问题,HashTable 之所以效率低下是因为所有线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器一部分数据,那么多线程访问容器不同数据段的数据时线程间就不会存在锁竞争,从而有效提高并发效率,这就是 ConcurrentHashMap 的锁分段技术。首先将数据分成 Segment 数据段,然后给每一个数据段配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。 get 操作实现简单高效,先经过一次再散列,然后使用这个散列值通过散列运算定位到 Segment,再通过散列算法定位到元素。get 的高效在于这个过程不需要加锁,除非读到空值才会加锁重读。get 方法里将要使用的共享变量都定义为 volatile 类型,volatile 保证了多线程的可见性,可以多线程读,但是只能保证单线程写,在 get 操作里只需要读所以不用加锁。 put 操作必须加锁,put 方法首先定位到 Segment,然后进行插入操作,第一步判断是否需要对 Segment 里的 HashEntry 数组进行扩容,第二步定位添加元素的位置,然后将其放入数组。 size 操作用于统计元素的数量,必须统计每个 Segment 的大小然后求和,在统计结果累加的过程中,之前累加过的 count 变化的几率很小,因此 ConcurrentHashMap 的做法是先尝试两次通过不加锁的方式统计结果,如果统计过程中容器大小发生了变化则再通过加锁的方式统计所有 Segment 的大小。判断容器是否发生变化是根据 modCount 变量确定的。 JDK 8 开始 JDK 8 的实现摒弃了 Segment 分段概念,使用 Synchronized 和 CAS 来保证线程安全。 get 操作同样不需要同步控制,put 操作时如果没有出现哈希冲突,就使用 CAS 方式来添加元素,如果出现了哈希冲突就使用 synchronized 加锁的方式添加元素。 P15:CAS 操作 CAS 表示 Compare And Swap,比较并交换,CAS 需要三个操作数,分别是内存位置 V、旧的预期值 A 和准备设置的新值 B。CAS 指令执行时,当且仅当 V 符合 A 时,处理器才会用 B 更新 V 的值,否则它就不执行更新。但不管是否更新都会返回 V 的旧值,这些处理过程是原子操作,执行期间不会被其他线程打断。 在 JDK 5 后,Java 类库中才开始使用 CAS 操作,该操作由 Unsafe 类里的 compareAndSwapInt 等几个方法包装提供。HotSpot 在内部对这些方法做了特殊处理,即时编译的结果是一条平台相关的处理器 CAS 指令。Unsafe 类不是给用户程序调用的类,因此在 JDK 9 之前只有 Java 类库可以使用 CAS,譬如 juc 包里的 AtomicInteger类中 compareAndSet 等方法都使用了Unsafe 类的 CAS 操作来实现。 尽管 CAS 既简单又高效,但这种操作无法涵盖互斥同步的所有场景,并且 CAS 从语义上来说存在一个逻辑漏洞:如果 V 初次读取的时候是 A,并且在准备赋值的时候检查到它的值仍为 A,这依旧不能说明它的值没有被其他线程更改过,因为这段时间内假设它的值先改为了 B 又改回 A,那么 CAS 操作就会误认为它从来没有被改变过。这个漏洞称为 ABA 问题,juc 包提供了一个 AtomicStampedReference,原子更新带有版本号的引用类型,它可以通过控制变量值的版本来解决 ABA 问题。这个类并不常用,大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决该问题,改用传统的互斥同步可能会比原子类更高效。 P16:原子操作类 Java 从 JDK 5 开始提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。到 JDK 8 该包共有17个类,依据作用分为四种:原子更新基本类型类、原子更新数组类、原子更新引用类以及原子更新字段类,atomic 包里的类基本都是使用 Unsafe 实现的包装类。 原子更新基本类型 AtomicInteger 原子更新整形、 AtomicLong 原子更新长整型、AtomicBoolean 原子更新布尔类型。 getAndIncrement 以原子方式将当前的值加 1,首先在 for 死循环中取得 AtomicInteger 里存储的数值,第二步对 AtomicInteger 当前的值进行加 1 操作,第三步调用 compareAndSet 方法进行原子更新,该操作先检查当前数值是否等于 expect,如果等于则说明当前值没有被其他线程修改,则将值更新为 next,否则会更新失败返回 false,程序会进入 for 循环重新进行 compareAndSet 操作。 atomic 包中只提供了 三种基本类型的原子更新,atomic 包里的类基本都是使用 Unsafe 实现的,Unsafe 只提供三种 CAS 方法:compareAndSwapInt、compareAndSwapLong 和 compareAndSwapObject,例如原子更新 Boolean 时是先转成整形再使用 compareAndSwapInt 进行 CAS,所以原子更新 char、float、double 也可以用类似思路实现。 原子更新数组 AtomicIntegerArray,原子更新整形数组里的元素、 AtomicLongArray 原子更新长整型数组里的元素、 AtomicReferenceArray 原子更新引用类型数组里的元素。 原子更新引用 AtomicReference 原子更新引用类型、AtomicMarkableReference 原子更新带有标记位的引用类型,可以绑定一个 boolean 类型的标记位、 AtomicStampedReference 原子更新带有版本号的引用类型,关联一个整数值用于原子更新数据和数据的版本号,可以解决 ABA 问题。 原子更新字段 AtomicIntegerFieldUpdater 原子更新整形字段的更新器、 AtomicLongFieldUpdater 原子更新长整形字段的更新器AtomicReferenceFieldUpdater 原子更新引用类型字段的更新器。 由于原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater 创建一个更新器,并且需要设置想要更新的类和字段。并且更新类的字段必须使用 public volatile 修饰。 JDK 8 更新的类 DoubleAccumulator 、 LongAccumulator、DoubleAdder、LongAdder、Striped64。 P17:并发工具类 等待多线程完成的 CountDownLatch CountDownLatch 允许一个或多个线程等待其他线程完成操作,构造器接收一个 int 类型的参数作为计数器,如果要等待 n 个点就传入 n。每次调用 countDown 方法时计数器减 1,await 方***阻塞当前线程直到计数器变为0,由于 countDown方法可用在任何地方,所以 n 个点既可以是 n 个线程也可以是一个线程里的 n 个执行步骤。 同步屏障 CyclicBarrier 同步屏障的作用是让一组线程到达一个屏障或同步点时被阻塞,直到最后一个线程到达屏障时,屏障才会解除,所有被拦截的线程才会继续运行。构造器中的参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 自己已到达屏障,然后当前线程被阻塞。还支持在构造器中传入一个 Runable 类型的任务,当线程到达屏障时会优先执行该任务。适用于多线程计算数据,最后合并计算结果的应用场景。 CountDownLacth 的计数器只能用一次,而 CyclicBarrier 的计数器可使用 reset 方法重置,所以 CyclicBarrier 能处理更为复杂的业务场景,例如计算错误时可用重置计数器重新计算。 控制并发线程数的 Semaphore 信号量用来控制同时访问特定资源的线程数量,它通过协调各个线程以保证合理使用公共资源。信号量可以用于流量控制,特别是公共资源有限的应用场景,比如数据库连接。Semaphore 的构造器参数接收一个 int 值,表示可用的许可数量即最大并发数。使用acquire 方法获得一个许可证,使用 release 方法归还许可,还可以用 tryAcquire 尝试获得许可。 线程间交换数据的 Exchanger 交换者是用于线程间协作的工具类,用于进行线程间的数据交换。它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过 exchange 方法交换数据,如果第一个线程先执行exchange方法它会阻塞等待第二个线程执行exchange方法,当两个线程都到达同步点时这两个线程就可以交换数据,将本线程生产出的数据传递给对方。应用场景包括遗传算法、校对工作等。 P18:线程池 好处 ① 降低资源消耗,复用已创建的线程降低线程创建和消耗的开销。② 提高响应速度,当任务到达时,任务可以不需要等到线程创建就可以立即执行。③ 提高线程的可管理性,线程是稀缺资源,如果无限制地创建不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。 当提交一个新任务到线程池时的处理流程 ① 判断核心线程池里的线程是否都在执行任务,如果不是则创建一个新的工作线程来执行任务,此时 workCount < corePoolSize,这一步需要获取全局锁。② 如果核心线程池已满,判断工作队列是否已满,如果没有就将新提交的任务存储在工作队列中,此时 workCount >= corePoolSize。③ 如果工作队列已满,判断线程池的线程是否都处于工作状态,如果没有则创建一个新的工作线程来执行任务,此时 workCount < maximumPoolSize,这一步也需要获取全局锁。④ 如果线程池已满,按照拒绝策略来处理任务,此时 workCount > maximumPoolSize。 线程池采取这种设计思路是为了在执行 execute 方法时尽可能地避免获取全局锁,在线程池完成预热之后,即当前运行的线程数大于等于corePoolSize 时,几乎所有的 execute 方法都是执行步骤 2,不需要获取全局锁。 线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后还会循环获取工作队列中的任务来执行。线程池中的线程执行任务分为两种情况:①在 execute 方法中创建一个线程时会让这个线程执行当前任务。②这个线程执行完任务之后,就会反复从工作队列中获取任务并执行。 可以使用 execute 和 submit 方法向线程池提交任务。execute 用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功了。submit 用于提交需要返回值的任务,线程池会返回一个 Future 类型的对象,通过该对象可以判断任务是否执行成功,并且可以通过该对象的 get 方法获取返回值,get 会阻塞当前线程直到任务完成,带超时参数的 get 方***在阻塞当前线程一段时间后立即返回,这时任务可能还没有完成。 创建线程池的参数 ① corePoolSize:线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池的基本大小时就不再创建。② workQueue:工作队列,用于保存等待执行任务的阻塞队列。③ maximumPoolSize:线程池最大数量,如果工作队列已满,并且创建的线程数小于最大线程数,则线程池还会创建新的线程执行任务,如果使用无界阻塞队列该参数无意义。④ threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。⑤ handler:拒绝策略,默认策略下使用 AbortPolicy 直接抛出异常,CallerRunsPolicy 表示重新尝试提交该任务,DiscardOldestPolicy 表示抛弃队列里最近的一个任务并执行当前任务,DiscardPolicy 表示直接抛弃当前任务不处理。⑥ keepAliveTime:线程活动的保持时间,线程池工作线程空闲后保持存活的时间,所以如果任务很多,且每个任务的执行时间较短,可以调大时间提高线程的利用率。⑦ unit:线程活动保持时间的单位。 关闭线程池 可以通过调用 shutdown 或 shutdownNow 方法关闭线程池,原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法中断线程,所以无法响应中断的任务可能永远无法终止。区别是 shutdownNow 首先将线程池的状态设为 STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设为 SHUTDOWN,然后中断所有没有正在执行任务的线程。通常调用 shutdown 来关闭线程池,如果任务不一定要执行完则可以调用 shutdownNow。 合理设置线程池 首先可以从以下角度分析:①任务的性质:CPU密集型任务、IO密集型任务和混合型任务。②任务的优先级:高、中和低。③任务的执行时间:长、中和短。④任务的依赖性:是否依赖其他系统资源,如数据库连接。 性质不同的任务可以用不同规模的线程池分开处理,CPU密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2 * Ncpu。混合型的任务,如果可以拆分,将其拆分为一个 CPU 密集型任务和一个 IO 密集型任务,只要这两个任务执行的时间相差不是太大那么分解后的吞吐量将高于串行执行的吞吐量,如果相差太大则没必要分解。 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 处理。 执行时间不同的任务可以交给不同规模的线程池处理,或者使用优先级队列让执行时间短的任务先执行。 依赖数据库连接池的任务,由于线程提交 SQL 后需要等待数据库返回的结果,等待的时间越长 CPU 空闲的时间就越长,因此线程数应该尽可能地设置大一些提高CPU的利用率。 建议使用有界队列,能增加系统的稳定性和预警能力,可以根据需要设置的稍微大一些。 P19:阻塞队列 阻塞队列支持阻塞的插入和移除,当队列满时,阻塞插入元素的线程直到队列不满。当队列为空时,获取元素的线程会被阻塞直到队列非空。阻塞队列常用于生产者和消费者的场景,阻塞队列就是生产者用来存放元素,消费者用来获取元素的容器。 Java中的阻塞队列 ArrayBlockingQueue,由数组组成的有界阻塞队列,默认情况下不保证线程公平,有可能先阻塞的线程最后才访问队列。 LinkedBlockingQueue,由链表结构组成的有界阻塞队列,队列的默认和最大长度为 Integer 的最大值。 PriorityBlockingQueue,支持优先级排序的无界阻塞队列,默认情况下元素按照顺序升序排序。可以自定义 compareTo 方法指定元素排序规则,或者初始化时指定 Comparator 对元素排序,不能保证同优先级元素的顺序。 DelayQueue,支持延时获取元素的无界阻塞队列,使用优先级队列实现。创建元素时可以指定多久才能从队列中获取当前元素,只有延迟期满时才能从队列中获取元素,适用于缓存系统和定时任务调度。 SynchronousQueue,不存储元素的阻塞队列,每一个 put 操作必须等待一个 take 操作。默认使用非公平策略,也支持公平策略,适用于传递性场景,吞吐量高于 ArrayBlockingQueue 和 LinkedBlockingQueue。 LinkedTransferQueue,由链表组成的无界阻塞队列,相对于其他阻塞队列多了 tryTransfer 和 transfer 方法。transfe方法:如果当前有消费者正在等待接收元素,可以把生产者传入的元素立刻传输给消费者,如果没有消费者等待接收元素,会将元素放在队列的尾节点并等到该元素被消费者消费了才返回。tryTransfer 方法用来试探生产者传入的元素能否直接传给消费者,如果没有消费者等待接收元素则返回 false,和transfer 的区别是无论消费者是否消费都会立即返回。 LinkedBlockingDeque,由链表组成的双向阻塞队列,可以从队列的两端插入和移出元素,在多线程同时入队时减少了竞争。 实现原理 使用通知模式实现,当生产者往满的队列里添加元素时会阻塞生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。例如 JDK 中的 ArrayBlockingQueue 使用了 Condition 实现。当往队列里插入一个元素,如果队列不可用,那么阻塞生产者主要通过LockSupport 的 park 方法实现,park 在不同的操作系统中使用不同的方式实现,在 Linux 下使用的是系统方法 pthread_cond_wait 实现。 P20:Executor 框架 Java 的线程既是工作单元,也是执行机制。从 JDK 5开始把工作单元与执行机制分离开来,工作单元包括 Runnable 和 Callable,而执行机制由 Exectuor 框架提供。 在 HotSpot 的线程模型中,Java 线程被一对一映射为本地操作系统线程,Java 线程启动时会创建一个本地操作系统线程,当该 Java线程终止时,这个操作系统线程也会被回收,操作系统会调度所有线程并将它们分配给可用的CPU。 在上层,Java 多线程程序通常把应用分解为若干任务,然后使用用户级的调度器即 Executor 框架将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。 Executor 框架主要由三部分组成:①任务,包括被执行任务需要实现的接口,Runnable 或 Callable。②任务的执行,包括任务执行机制的核心接口 Executor 以及继承自 Executor 的 ExecutorService 。③异步计算的结果,包括接口 Future 和实现 Future 接口的 FutureTask 类。 ThreadPoolExecutor ThreadPoolExecutor 是 Executor框架最核心的类,是线程池的实现类,主要有三种。 ① FixedThreadPool,可重用固定线程数的线程池,corePoolSize 和 maximumPoolSize都被设置为创建时的指定参数 nThreads,当线程池中的线程数大于 corePoolSize 时,keepAliveTime 为多余的空闲线程等待新任务的最长时间,超过这个时间后的多余线程将被终止,将其设置为 0L 时多余空闲线程将被立即终止。该线程池使用的工作队列是无界阻塞队列 LinkedBlockingQueue,适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,适用于负载比较重的服务器。 ② SingleThreadExecutor,使用单个线程的线程池,corePoolSize 和 maximumPoolSize都被设置为 1,其他参数和 FiexedThreadPool相同。适用于需要保证顺序执行各个任务,并且在任意时间点不会有多个线程是活动的的应用场景。 ③ CachedThreadPool,一个根据需要创建线程的线程池,corePoolSize 被设置为0,maximumPoolSize 被设置为 Integer 最大值。该线程池使用的工作队列是没有容量的 SynchronousQueue,但由于 maximumPoolSize 设为 Integer最大值,如果主线程提交任务的速度高于线程处理的速度,线程池会不断创建新线程,极端情况下会创建过多线程而耗尽CPU和内存资源。适用于执行很多短期异步任务的小程序,或者负载较轻的服务器。 ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,主要用来在给定的延迟之后运行任务,或者定期执行任务。其功能与 Timer 类似但更强大和灵活。Timer对应的是单个后台线程,而 ScheduledThreadPoolExecutor 可以在构造器中指定多个对应的后台线程数。 主要有两种:① ScheduledThreadPool:创建固定线程个数的线程池,适用于需要多个后台线程执行周期任务,同时需要限制后台线程数量的应用场景。② SingleThreadScheduledExecutor:只包含一个线程的线程池,适用于单个后台线程执行周期任务,同时需要保证顺序执行任务的应用场景。 实现原理是将待调度任务放入一个DelayQueue 中,调度任务主要有三个 long 类型的参数,time 表示这个任务将要被执行的具体时间,sequenceNumber 表示这个任务被添加到线程池的序号,period 表示任务执行时间间隔。DelayQueue封装了一个PriorityQueue,队列按照 time 进行排序,如果相同则比较sequenceNumber,越小的排在前面,即如果两个任务的执行时间相同,先提交的任务先被执行。
分享
8
先马后看
你的笑的炫
南开大学·2023届

新浪微博 21年毕业生招聘启动

请注意是21年毕业生哦~~ 微博客户端开发(Android,iOS 和 前端) 岗位职责: 1、负责新浪微博客户端的功能设计和研发; 2、负责维护微博客户端,分析和解决用户碰到的问题; 3、负责客户端的性能优化以及各种辅助系统的设计和研发。 任职要求: 1、2021年毕业统招大学本科及以上学历; 2、熟悉Android/iOS/前端开发,对计算机技术有浓厚的兴趣,热衷于追求技术极致与创新(有项目经验优先); 3、具备扎实的理论基础,熟悉常用的数据结构、网络知识、算法以及数据库技术; 4、具有良好的沟通能力、学习能力、创新能力和分析解决问题的能力; 5、具备一定的产品意识,对社交网络产品有一定的理解。 简历请发到:wangyu29@staff.weibo.com  
分享
评论
校招情报局
Kawah
河北工业大学·2022届

关于银行秋招的一点所思所悟

首先,这肯定不是一篇标准的经验分享文,而且我也觉得因为每个人的专业、经历都不同,所以可借鉴之处也有限,想写点东西纯属是因为我感觉秋招找工作其实与我们上大学或考研选学校选专业一样,都颇费周折,有很多想法,有很多阻碍,最后得到结果,因此也有很多感想。 先自我介绍一下吧,我是一个双非小硕,位置广州,长的不高也不帅(很多人在找工作时可以凭借形象如鱼得水,这是令人羡慕不来的优势),专业是经济学类的,曾一心想往证券靠,奈何双非的硬件在证券公司承认度不高,可以归结为自己太菜,进不去,于是转向银行,因此我主要谈谈找银行工作的经历,我共拿到九个银行的offer,分别是: 招商银行深圳分行 中国银行深圳分行 深圳农商银行 香港集友深圳分行 交通银行广东省分行 工商银行广州分行 农业银行广东省分行 广州农商银行 广发银行广州分行 与我面试的银行数目相当,还有一些到终面了因为先签了就没有去面,如: 中国银行广东省分行 浦发银行深圳分行 中国农业发展银行等 好友戏称我为“银行收割机”,厉害的同学可能也会对这些银行嗤之以鼻,因为这些offer也尽是一些分行,没多大含金量,如果有同学不嫌弃这些银行,尚可继续往下看。 九月初我还在实习,在与好友交流中发现秋招早已如火如荼的拉开帷幕,甚至有些行业比如互联网的招聘都接近尾声了,好友也整日奔波各大宣讲会,身边有同学都已经早早拿到普华的offer,不禁有些心慌。当时还对实习单位的留用抱有幻想,是家券商的研究所,当我得知很难在目前的实习单位留用时,我便毅然离开返回了学校准备秋招了。 由于本科毕业也没正经的参加过秋招,对各种信息来源渠道和流程也不甚了解,刚开始还显得手足无措,但这个都简单,不懂找度娘就好了。我主要用的是应届生求职网以及结合学校的就业信息网,同时也关注中华英才网、智联招聘、猎聘,想给学校就业信息网做的微信公众号点个赞,里面联合了广东省各大高校的就业招聘信息,我通常在里面关注到宣讲会信息。但身边有好多同学不大关注宣讲会,可能是懒得出门或者想着大部分的宣讲会只是来做广告搞宣传的,通常在网上把简历一投就了事了。在我看来,大部分的宣讲会还是值得去的,理由有两点:一是你可以直观的了解到企业的基本情况,可能比你在网上查到的要详细的多,比如职业发展路径、薪酬待遇之类,在你抉择去不去这个地方时这些信息还是可以作比较重要的参考的。二是大多宣讲会后会有一个简单的初面,很多银行的提前批就是紧跟着这个初面之后,如果是仅仅在网上投递简历,除非比较硬核,不然都要错过这个提前批的。 跑宣讲会或面试前的一些基本准备是正装、简历、以及一份得体有针对性的自我介绍。正装这套行头通常要花费不少,一块跑宣讲会的同伴一双皮鞋大几千,我觉得这点上倒不用一丝不苟,作为学生,量力而为吧,我就自己搭的黑裤子和白衬衫,把自己收拾的干净精神就好,穿的太休闲似乎让人感觉你对面试不够重视。简历因为之前找实习有准备,所以直接拿来添些内容就用了。整个求职过程中,也偶有看到他人的简历,有次和一个挺厉害的同学交换简历看,人家简历满满当当的铺满了一页纸,相较之下因为我的简历有较多的空格留白,显得空洞许多。可我反倒觉得这样层次感强一点,我喜欢简洁清晰,简历的标题和内容分别选用黑色和灰色字体,商务蓝的分隔线,以及白色背景,虽然有留白,但能让人看到我想展示的东西。自我介绍可以提前写好,也可以针对不同的单位区别准备,但千万要记熟能够自然流利的表达,刚开始面试时,连我自己都觉得是在背稿,这应该是面试的大忌,因此有次还被面试官打断,尴尬之极。 回想经历过的面试,都如昨日发生历历在目。第一场面试是江苏银行深圳分行,在大学城,搭地铁然后换摩拜,像个路痴一样找半天才找到地方。那会对银行业好像没什么了解,江苏银行招的基本是对公岗,不用坐柜,起薪14还是15万,然后还提供食宿,当时就觉得是个好去处,如果能去就不想其他地方了。在越重视的面试前就越是紧张,可完全不重视又不能集中注意力做好应对,所以一份平和的心态真的很重要。由于在宣讲会教室坐的位置比较靠前,面试的安排是依座位次序,很快就到我的面试了。我真的很容易紧张,紧张的时候就很难放松对面试官保持微笑,以显示自己的自信,我尽量很平静的介绍完自己,也大概是我叫什么,来自哪里,在学校有什么成绩,经历了哪些实习以及表达很想加入你们。面试官和我聊了几句,问了一些实 ** 的问题,但很快结束了,末了告之当晚就会出结果,留意查收短信之类的。那晚等到12点,故意将手机放置一旁,跟同学说感觉不好,估计要挂,心里却抱着一丝希望,不然为什么末了叫我留意查收短信呢,但结果凉了。 后来在面中国银行深圳分行遇到一个985的本科小姐姐,聊起来,得知人家进了,二面是即兴演讲和无领导讨论,同学说还是拼不过学校啊。有次在工商银行的宣讲会又遇到她,现场比较嘈杂,我们是排着队去面试,我刚好在她后边,听见她自我介绍。具体内容已经记不大清了,但当时有个感觉,这个小姐姐的自我介绍真是精彩,真是优秀,她采用的是对自己特征描述型的自我介绍,比方说我是一个怎样的人,几个名词,完了后边就举事例佐证了,而且人家的事例都有明确有力的数字,比方说我还记得她说曾在某保险公司月业绩达到多少多少。相比之下,我准备的自我介绍就如同在念简历,平淡的与食堂的清汤面一样,回去的路上忍不住跟同伴讨论,回去真是要把自我介绍好好改改。后边也仿了小姐姐那种自我介绍的形式准备了一份,可在实际面试中始终感觉不如人家的自然,显得生硬,几番周折才改成比较满意的形式,我觉得这几乎就是你面试过程最重要的东西了。一份合适的,自然的,能展现出你优势的,表达你的诚恳和意愿的自我介绍可以迅速抓住面试官的注意力,之后的面试通常在我自我介绍的时候,面试官都饶有兴趣的看着我,完了才看简历,我觉得我应该是改对方向了。 面试这回事如果不是找同学私下练练的话,还是要提早多出去实战,像在面试之前我是没有过无领导小组讨论的经验的。经历的第一场无领导小组讨论是招商银行深圳分行的一面,因为与同伴一块去的,我比给我安排的时间提早了一些过去,结果他们的面试顺序类似先到先面,还省了不少等待的功夫。领了铭牌,携手名字,候场,进场,一圈的座位,大家依次坐下。面试官提醒我们将名牌朝他们位置的方向摆放,他们坐一向,面前放着电脑。我猜是有一个评分系统,谁发言时,他们就开始打分了,也有少部分企业会用手机摄像,用作事后评估抉择吧。开始了,讨论的主题大概是互联网金融对招商银行信用卡的冲击与比较,我发现自己还挺喜欢这种话题讨论然后出个主意形式的活动,一直都属于脑洞比较大的那种。但似乎没啥经验,而且有些紧张,组内的小伙伴,也是我一个同伴率先发言了,说带了表可以给大家计时,我才意识到这里边有好几种角色,计时的,总结发言的,控场带节奏的(也就是所谓的leader吧),以及吃瓜群众如我。大家好像都沉着自信又积极踊跃,一个话音刚落,一句“我来说一下吧”又起,转眼大半的人都发表过意见。我好似局外人没能插进去,表面镇定,其实焦急紧张的不行,甚至当我第一声开口隐约感觉有些破音了。我将这个归结与经验不够,多经历了几次后,就感觉好多了。在深圳农商银行的无领导讨论上,恰好是我感兴趣的话题,关于公司的经营运作之类的主题,记得隐约在讨论中成了leader,然后也代表小组总结发言,最后发言完我觉得是稳了。 还有一种面试形式是即兴演讲,广发银行那场搞得就是这个。想起那天还挺有趣的,那天共有四场面试,深农商、广农商、广发行还有个保险公司,推掉了保险公司,赶了三场。后面参加的宣讲会和面试多了就不觉得往外跑是很辛苦的事,反而觉得和小伙伴一块出去是蛮有意思的经历,像一起去参加活动,在参加活动中还认识了不少本校和外校的人,可以让自己脱离熟悉的圈子,接触外界。宅学校久了就会形成固定的圈子,每天见同样的人,做差不多的事,然后就要变井底之蛙,不思进取了,去面试见见其他优秀的人,受受打击反倒能刺激刺激自己吧。就像广发银行那场面试,同组的小姐姐的演讲就像是先写了稿似的,流利又具逻辑。我大概是在第五次序发言吧,多对多的形式,在一个椭圆桌办公室,面试官坐一排,面试者坐一排,先前是抽了一个话题的,然后给了几分钟准备时间,就开始了。我的话题是“加班”,还好是比较常见的话题,关于这种即兴演讲,其实我是不大擅长的,有个同学也面这个,然后回来说自己当场卡壳了,不知道说什么了,结果场面很尴尬,结果也是。我也时常容易遇到这种情况,还记得有个校园活动,让上去演讲,抽到的主题是“初恋”,这本也不是偏的话题,但就是突然脑子不知道该说什么了,结果给我的尴尬史又重重添上了一笔。后边与隔壁宿舍谈天时,我说,即兴演讲或者说不管什么演讲,一定记得把话往自己身上引,自己对自己是最了解的,对自己的经历也是最熟悉的。先发表一下对话题的看法,举一个自己与话题相关的事例去佐证你的看法,然后联系到这家面试单位,顺便表表忠心之类的。关于“加班”我便末了说了句,倘若有幸进入贵司,遇到此情况,我会怎样怎样,把这些稳定又得体的说完,即满足了演讲时长,又显得内容充实有逻辑。 终面一般是半结构化面试,多对一的形式,自我介绍完了,面试官针对他们感兴趣的地方问一些问题。刚开始我也纠结会不会问专业性问题之类的,毕竟真的来答可能就将学渣内在泄露了,但银行的招聘基本都不限专业,理工科,甚至艺术类的求职者也屡见不鲜,所以问的问题大多是针对简历。大多人都讲,银行培训体系那么完善,干的活技术含量也不高,难道我还干不了吗,为啥不录用我。当然,对于大多数人来讲,去胜任银行的基层岗位肯定是没问题的,但招聘毕竟是选拔性录用形式,一个岗位虽然招的人多,但报名的人更多,所以怎样表现出自己比其他人更适合做这份工作可能才是需要考虑的问题。那求职者的专业背景五花八门,怎样才是能够显示比其他人更适合呢,或者说面试官的筛选标准是哪些,在我看来,可能有几点是比较重要的。 首先是一个口头表达能力,其实大多数前台岗位最看重的也都是口头表达能力,毕竟工作内容最多的就是与人沟通交流了,一个话都说不利索的人可能也不大适合去从事前台岗位。但大多数的人在这方面我觉得相差不是很大,你和身边的人能正常交流吗,能,那你就能和你的客户正常交流。为什么在面试中不能表现出良好的表达与沟通能力,我觉得很大程度上是因为紧张和准备不充分。面对陌生的环境,陌生的人,尤其是多个时,的确会形成一种有压力的气氛,压力的大小也因人而异。我记得很多次面试时,面试官常常故意冷峻着脸,随意的打断我,刻意造成一种压力环境,估计也是一种压力测试吧。后来我发现惯用这些套路的都是一些如智联招聘派出的职业HR,他们协助用人单位担任招聘环节的面试官。明白了这点之后,就像知道了对方的小秘密,无形中你们之间这种角色地位仿佛变得平衡,这是一个boss,而自己是一个玩家,这样想让我可以甩掉无谓的紧张。充分的准备是应对的另一法宝,面试官与你之间是信息不对称的关系,而你是掌握信息的一方,针对面试官询问的关于自身的问题其实都可以私下做一定准备,这些问题也无非是一些关于你的校园活动、实习经历的事,做了哪些事,收获了什么,有什么成功的案例等等。放平了心态,做足了准备,表达能力自然就上去了。 然后是简历上的干货,也就是个人光鲜的履历。朋友经常酸我,我又没有像你你得过什么什么,参加过什么什么,所以觉得这是实力问题。其实我也觉得,银行里边这种基层工作大多数人都干的了的,不一定要多好多好的履历,所以你如果让人看到你的亮点的话,其实发出的光是差不多的,关键在于怎么去找自己的亮点。我有一个国奖的荣誉,我觉得是在履历中最有分量的一个,在面试的前半程发现竟没有一个面试官提起过,让我着实郁闷。后边我突然想到,会不会是面试官他们也分不清这些奖的区别,或者没听过,之后的面试我特意多一嘴介绍这个奖的一个获得比例,同时也在简历上注明,发现立竿见影,问起的人果然多了。面试官也只是一个普通的职业人,对各行各业的事儿哪能那么清楚,展现自己的专业的地方有时反而是好事儿,隔行如隔山,只要你能让人觉得你在这个地方取得了成功,是优秀的,那么就达到面试官的筛选条件了。 原本想记录感悟的,写着写着又有些教条,像介绍经验了,最终要成行散神也散的杂文了吧,但就这样瞎嘀咕着都五千来字了,要是写论文能像这样就好了。有次我跟同学解释为啥我面银行比较顺畅,我对她讲,可能是我看起来比较老实,听话,领导不都喜欢听话的下属么,银行业是个加班泛滥的行业,我看起来以后也是能任劳任怨的加班加点吧。可能也确实是这样吧,至少在面试时,我尽量让自己表现得十分诚恳,有意愿,但在表现真诚时我常常出现一个毛病,说话声音小。有一次面一个商业保理公司,人家对我说我看起来比较内向、羞涩,指的就是那会我跟他交流时说话又慢又小声吧,不是那种蚊子声,只是不是听起来就很外向得爽朗声。其实除了银行也面过其他的地方,证券、基金之类,结果不是很如意。我有时候想,求职不就是要找一份适合自己的工作么,毕竟人家招聘的人在这个行业呆这么久了,人家不要你,说明你不适合那份工作,银行要我,或许是挺适合我吧,希望自己能够在工作岗位上做出成绩,取得成果吧。
分享
1
原味笔面经
Blair
广东海洋大学·2022届

【运营面经】腾讯暑期实习提前批PCG个面面经凉经(21届)

1. 面试信息 (1) 面试岗位:腾讯PCG 腾讯看点 产品运营(2021届) (2)timeline:3.04 官网投递----3.05 被捞个面 ---- 3.06 面试 ---- 3.08出结果(没过) (3)面试形式:个面;腾讯会议面试时长:45分钟 (4)总体感觉:涉及相关产品/业务的提问比较少,基于个人经历的提问比较多 (5)个人背景双非渣本;绩点:前2%(但是互联网应该不重视这个) 4段实习(2段腾讯,2段500强,主要有用研、数据分析、品牌策划、运营等),有深度参与的,也有简单的水实习 (菜鸡中的菜鸡)继上一轮凉凉之后,又被捞去面试了!这次一定认真准备!球球一切顺利 2. 面试第一部分:基于简历提问 (1) 自我介绍:个面自我介绍可以长一些,重点说一下自己岗位相关的实习(详细度高一些)——但是腾讯面基本上看不到bg在面试前也不知道是什么部门 (2)深挖简历上的实习经历和项目,做了哪些工作/怎么做的/有哪些成果(参考STAR法则做介绍)大概会有2-3个问题是针对实习经历进行提问的,一定要提前进行简历和实习经历的复盘和总结。 如果实习是市场研究、用户研究、商业分析等偏观点输出的工作,面试官还会提问相关的工作之后的一些成果(详细的个人思考、观点) (3)如何进行一天的工作安排(涉及时间管理等内容) (4)工作/项目中遇到过最困难的事情 我的建议:这道题其实考的是你工作中做过的最有价值的事情是什么,困难那么多,要选择那个自己解决得最漂亮、最圆满、方案实施效果最佳的项目或者经历进行讲解 ,适当突出自己的相关特长和能力(这个问题我回答的并不是很好,因为我选择了自己心理感受上最困难的题目,但是相关的解决方案却不是非常的完善) 3. 其他个人问题/个人考察/互联网认知考察 (1)推荐一本书/电影 没有事先准备这个问题,答得很没有逻辑,不过这种是套路题,面试前在网上搜索相关分享进行准备即可。 最好选一个知名度高一些的电影,可以从产品、观众群进行分析,不要只从自己的心理感受(为什么好看)进行分析。 (2)用的最多的产品是什么,能不能结合它做一个分析 我认为这里有两个考核点(我瞎猜的):面试官看你平时是个什么样的人(比如面泛文娱相关的业务部门,那说用的最多的是某漫画app/某网文app/某游戏app,都会是比较不错的选择) 第二个考核点:面试官会考察你对日常使用的产品有没有以产品或者运营的角度,进行深度的思考,建议这里的回答要参考产品分析的框架(这个我也没做准备,之前写过的产品测评都是比较冷门的,这次答了知乎,分析的不够深入,也是一个bug) (3)平时关注互联网吗?还是只喜欢打游戏 因为我的简历上比较突出的经历是ieg的经历,写了挺多关于游戏的内容,所以面试官问了我这个问题。但我当时并不知道捞我的面试官是哪个bg的,所以只好一方面强调自己对个别游戏品类的深度体验,另一方面也补充了自己对互联网其他相关的了解和认知(特别提到了在pcg的短暂项目经历,刚好面试官就是pcg的哈哈哈哈) (4)给你1-2分钟的准备时间,如果面试官是投资人/购买者,如何将自己销售出去,给一个方案。建议围绕【个人品牌打造】进行回答, 自己的明确用户群是谁? 自己的核心价值/不可替代性是什么? 自己能提供哪些东西? 自己的投资价值是什么? 如何把自己销售出去(渠道) 说完这个之后,面试官会让你给自己打分,也会分享点评。 面试官对我的评价是:没有围绕一两个点进行核心、突出的讲解,有点分散化(个人认为是表述的问题,所有回答都要框架化) (5)有什么想问的问题吗? 我这里回问了两个:是什么bg什么业务的(听完之后表示自己对该产品有一定的了解) 准备该业务相关的岗位,应该如何提高自己,面试官挺好的,给了详细的建议,包括看什么书、要有哪些思维等等。本来想等进了下一轮再做分享,惨痛经历再次说明:一定不能毫无准备就去面试!!!一定要认真地复盘简历、做好每一道题的回答思路!!!双手奉上详细面经攒人品! 祝各位面试顺利,收割offer!
分享
15
原味笔面经
哈维
东北林业大学·2022届

19年秋招渣硕测开面经

先说下我自己的基本情况吧,20届的计算机技术双非渣硕无论文,小厂实习打杂5个月。研究生期间主要学的是机器学习,深度学习之类的深坑算法吧。一开始打算投算法岗的,后来看论坛发现投算法的全是手持顶会论文或者竞赛大牛的985大佬,无奈本人比较渣就转投开发和测开,由于实习期间接触到一点点关于自动化测试的小任务,所以主要投的还是测开岗位。因为8月份就开始投简历了,一开始是投的互联网公司比较多,到后来各大银行也都开始招聘就主要去投银行了,想轻松一点吧所以还是偏向于去银行的😅。家离广州比较近,而且广州的房价没有深圳恐怖,所以就比较想留在广州的。秋招都是海投的,一共投了40多个简历,农行发offer比较早,后来的公司就佛系面试了。。 由于一开始没打算找开发,所以重新复习Java是从投简历开始复习的,主要就是复习一下基础知识、看面经、看牛客上面的专题训练,然后笔试的算法题基本上都是使用Java完成的。笔试的算法题就没有记录,同学们可以从牛客上面搜各个大厂的笔试算法题和解法,我自己是完成了一遍剑指offer,然后就是一边笔试一边练了。。由于投的测试岗比较多,所以把牛客上面的测试专题也都看了一下。有些公司没有记录,就发一下记录的公司面试吧。。 大疆(测试工程师): 大疆开始得非常早,所以就直接投了一下。 笔试:单选20、多选10、一道编程1计算机通信、软件工程、测试理论基础 一面:电话面,自我介绍,然后聊了一下项目,之后就开始闲聊了。。。大概半个小时吧。 二面:也是电话面,这次就问了不了解电子产品的测试,测试无人机怎么测试,了没了解过摄影。(没玩过无人机和摄影,实在是不太会回答。。) 二面之后挂了,大疆的测试应该比较少涉及开发这边的吧,我也不太清楚。。。 字节跳动(开发,软件质量方向): 先做算法题,算法题忘记了。。太菜了,只有一面。后来北京那边捞了一下说考不考虑去北京,没打算,遂拒。 1、Hashmap和Hashtable的区别,HashMap实现安全 2、selenium的工作原理 3、测试相关的技术和信息 4、网页访问的流程5、网络5层模型,应用层协议,网络层协议 6、tcp连接过程 7、对测试开发有什么看法 8、题:数组中出现1次的两个数字 9、python的装饰器 10、HTTP状态码 Cvte(测试开发): 一面就凉了。 1、手写一段Mysql查询代码 两个表联合最大分数 2、Mysql主键和外键 3、Http和https Http的请求方式 4、session和cookie 5、Get和post的区别 6、有哪些测试方法 7、代码题 两个数的最大和 所有组合写出来(两次代码都考虑不全面) 8、怎么测试刚才写的代码 农行(测试开发岗): 农行也开始得很早,线下笔试的,就是行测+英语+计算机基础知识,还算比较简单。发offer比较快,各方面都比较符合个人的情况,所以就签了这个offer。 面试:群面3对5,一开始是面试官问一个问题,每个人轮流答,大概两三个问题,然后就面试官再单独一个人各问一个问题。 1、自我介绍 2、测试生命周期测试模型 3、Java接口和抽象类 4、Sql连接 5、C的堆栈溢出 6、排序最优哪些各种不相关排序 小米(测试开发): 线上视频面试的,感觉面得还可以,然后问面试官评价一下我的表现,说挺好的。。然后一面之后没有下文了???? 一面: 1、项目 2、自动化测试是做什么的 3、输入一个网址,处理的过程服务器怎么处理http请求 4、有bug开发不认怎么处理 5、有之前留下来的代码看不懂怎么办。 6、算法题:算法字符串中找出最长的数字串。 唯品会(测试开发): 唯品会最后发了offer,但是薪资不符合预期就拒了。面试一天搞定。 一面: 1、项目难点,怎么检测测试项目返回的的状态(我实习做的网页自动化测试相关的项目) 2、数据挖掘项目(实习参与的数据挖掘相关的项目),每个项目都问 3、Java相关的 Gc新生代和老年代 4、Exception 种类 5、fullgc 6、数据库连接需要注意什么 7、join 8、算法题某个数三次开方的第7位小数 9、为什么选择测试 笔试很高分,怎么学习测试的 二面(应该是部门经理): 1、问项目聊人生(他不是很懂数据挖掘,就让我解释一下大概是做什么的) 2、有没有投别的岗位,大数据? 3、拿到哪些offer 4、为什么选择测试 Hr面: 常规问题,问一下家庭状况之类的。 中信卡(测试开发方向): 中信卡也是一天搞定三面,每次面试完5-10分钟告诉你是否通过这一轮的面试,通过就继续等下一轮就行了。 一面: 1、聊项目 2、Java 集合有哪些 3、HashMap和HashTable的区别 4、HashMap的实现 5、MySQL索引有哪些 6、GC实现,新生代HotSpot次数默认 7、GC回收的算法 8、测试的生命周期 二面2V1: 1、聊项目 2、白盒测试和黑盒测试区别 3、测试和自动化测试区别 4、大部分都是聊项目 Hr面: 1、讲解一个项目 2、实习的收获 3、觉得自己的优点和缺点 4、对中信信用卡有了解吗 5、想在哪个行业工作 微众(系统测试): 微众面试比较佛系吧,自我介绍完了之后,面试官就开始说他们那边测试真的只是纯测试,而且基本上进去就要做纯测试1,2年不会变的,估计想着研究生估计也不会去吧,然后就没有下文了。 招行佛山(信息科技岗): 一面: 就看看本硕的学校,和在校成绩。哪里人,去佛山的意愿大不大。5分钟。 笔试: 大部分都是行测吧,还有英语阅读。没有计算机相关的。 二面: 聊一下项目,因为投的是软开,但是项目都是数据挖掘的,问我转不转大数据那边,后来就问了一下数据挖掘的问题。 1、过拟合怎么处理了。 2、熟悉哪一些分类器。大概差不多就是这种基础问题吧,大概都忘了。。一共10多分钟吧。 终面: 因为前面签了offer所以就面得很佛系。。多对多,大概10来个面试官、然后5个同学一起面试吧。 1、自我介绍 2、职业发展比较看重哪一方面。 3、忘记了反正就是这一类型的,没有问技术。 大概记得就这么多了,还有腾讯爸爸的面试,面得比较渣就没记录,后来其他地方的腾讯捞了一下,说不考虑,就与腾讯无缘了。。还有一些通过笔试没去参加面试的,广发卡、招银网络、京东等等。同学们凑合参考参考吧,有问题可以私信我。
分享
4
原味笔面经
爱码小哥
南京农业大学·2022届

算法岗面经(阿里头条网易爱奇艺等)

基本都是提前批面的,整理了一下,发出来给妹子攒攒人品~ 更新一下基本情况 985小硕,在头条实习过一段时间,无顶会,有几个竞赛的top(基本都是cv相关的),github有一个1k star的项目 --------------------------------------------- 在实验室做的cv,春招投cv各种挫败。加上实习做的推荐,觉得挺有意思的,所以秋招转战机器学习,基本都是投推荐算法。(因为对自己水平没把握,所以海投了不少)。除了阿里美团快手,基本都拿到了offer,运气爆棚。感谢妹子,感谢杨超越 机器学习 阿里 阿里是第一个正经面试,然而一面凉。场景题答的太乱了。也怪自己。 自我介绍 介绍实习做的事情 C++ operator new和new operator的区别 C++引用和指针的区别 SGD和BGD区别,还知道哪些优化算法?动量的作用是什么? xgboost的gbdt的区别 k-means的时间复杂度 场景题:怎么给新上架的商品做冷启动 场景题:设计一个10亿用户和10亿商品的推荐系统 手写代码:用朴素贝叶斯实现垃圾文本分类 今日头条 一面 自我介绍 问项目,问的很细。速度、性能如何做的优化 Faster rcnn、yolo、ssd的区别 LSTM的结构与前向传播 现代cpu算力在什么量级 手写代码:全排列 二面 问项目,主要问创新点在哪里 推导PCA 概率题:13个人生日都不是同一天的概率,要求给出表达式和最终结果(不用计算器估算) 场景题:推荐系统模型收敛的很好,但是多样性可能不好的情况下如何解决。 非递归中序遍历二叉树 三面 聊学校参加的比赛 聊实习做的事情,有什么能改进的点 一道题:假设有一组基向量b1,b2,...,bn,现在有一个向量x,希望能用这组基向量中的三个表示,也即x=w1bi+w2bj+w3bkx = w_1b_i + w_2b_j + w_3b_kx=w1bi+w2bj+w3bk,问如何求解这个问题 四面 一个圆上随机三个点组成锐角三角形的概率,要求数学推导 一个无序数组,定义一个***作为:相邻的三个数进行循环左移,比如789循环左移后为897,问能否仅使用该***作使得数组升序。如果不能,总结一下能和不能的规律。 讲一个项目。 网易云音乐 一面 自我介绍 手写代码:合并两个有序链表 线性回归和逻辑回归区别,推导逻辑回归 ID3、C4.5、CART的区别,写信息增益、信息增益率、基尼系数的公式 树有几种剪枝的方式,各有什么优缺点 解释一下排序的稳定性,冒泡排序是否稳定,复杂度多少 二面 各种问实习 gbdt和xgboost区别 adaboost和gbdt区别 过拟合怎么解决 CNN参数量计算 如何评价一个分类器,auc的工程计算方式,roc曲线 总监面 问实习项目 推导FM、FFM 问了一点强化学习概念 美团 去酒店面了一面,后来通知去二面。想着美团也不招人就给鸽了。 一面 自我介绍 问实习,召回怎么做的,精排模型是什么 过拟合的原因和解决方法 Batch norm的原理 dropout原理 讲一下tensorflow的分布式版本 手写代码,实现double power(double base, int exp) 爱奇艺 8月的时候上海openday现场面的。 一面 问实习,在特征签名的问题上说了半天 怎么做ab实验,ab实验的原理 特征工程怎么做的 如何判断特征的有效性 Deep model在推荐中的应用是否了解,讲一个 二面 问实习,聊了一下场景不同下,推荐系统的关注点有什么不同 手写代码:求二叉树深度和宽度 携程 一面是去的现场集体面试,人多到爆。所以一面就进去聊了15min 一面 问实习做的事 懂不懂深度学习,在推荐系统中的应用是否了解 二面 问实习做的事,还给提了不少很中肯的意见。 xgboost和gbdt区别 聊最近的推荐系统相关的模型。XDeepFM,DIN之类的 招行卡中心 春招的时候拿的直通终面,在学校面的一面。不要四处跑,美滋滋。二面是个主管和hr一起面的。 一面 介绍一个觉得最成功的项目 劝我转开发。。。 二面 介绍一个项目 说说自己的职业规划 银联 我猜我可能是面银联里唯一一个写了代码的人。 一面群面 互联网金融,危害性排序 二面 问实习 手写代码:2sum,3sum,n sum(讲思路) CV相关 依图 春招找实习面的。依图的题确实挺非主流的。 一面 自我介绍 手写代码:一个01串,每次删除只能删除连续且相等的子串,允许进行两次删除,问最多能删掉多少数字 概率题:一副扑克牌,去掉大小王。打乱发牌,发到第一张A的时候停止,问下一张牌是黑桃Q和黑桃A的概率哪个更大。 二面 手写代码:蛇形输出矩阵 场景题:主要就是一些评价指标如何计算,如何划分训练验证集,focal loss,偏序关系等 聊项目 海康 一面电话面 问项目,项目背景,难样本挖掘是怎么做的 问论文的创新点 二面 问项目 手写代码:求两个旋转矩阵的IOU 综面 讲论文的创新点 参加比赛的算法相比论文中又做了什么改进 虹软 一面 聊项目 问了一些深度学习基础知识 二面 聊项目 LeNet、vgg、resnet等经典网络的发展 GAN的损失函数 DQN 快手 二面完过了3个礼拜没消息,到今天才有hr约hr面。。。鸽掉了 一面 自我介绍 问项目 Batch norm,具体训练测试的时候是怎么做的 手写代码:一个三棱柱。6个点涂4种颜色。要求同一条棱两端的点颜色不能一样。问一共有多少种涂色方案。 二面 问项目具体实施细节 又问了一次batch norm svm了解吗,什么样的函数能做核函数 手写代码:p的概率生成0,1-p的概率生成1.如何等概率生成0和1.如何等概率生成0~n 让求一个不定积分。。 cvte 一面 自我介绍 讲一下逻辑回归,逻辑回归能用来做回归吗? 讲一下svm过拟合的原因及解决方法 L1、L2正则讲一下 说一下直方图均衡化 说一下二值化,大津法怎么做的 介绍一个项目 二面 场景题:老师写的板书的电子化怎么做 场景题:自动批改试卷怎么做 2轮hr 因为我没填补充简历,基本就是把补充简历里的问了个遍 vivo 一面技术一面hr,时间略久远。已经记不得问了什么了,基本就是结合简历上的项目问的。
分享
8
原味笔面经
寡人有疾
南开大学·2022届

小白的支付宝面试经验分享之作品集的重要性

今天,我想和大家分享一下支付宝的实习申请过程 网申的时候内推可以加分哦,可以从一些求职机构的微信群或者牛客获取。 支付宝的面试真是五关斩六。 从一面到三面为设计面 四面是交叉面 五面是HR面试 (一面):支付宝交互设计师主要问作品集。首先,自我介绍,然后介绍了我最满意的作品之一。要着重介绍设计的过程和自己在工作中扮演的角色。 (二面):注重学习经历,我大学学了哪些课程,哪些具体知识。由设计主管面的,相对严肃一点。还是先自我介绍一下,简单介绍一下我最喜欢的作品之一。然而,作品的介绍不像之前一面问详细的设计过程。 面试官更关注你的学习经历和硬实力。主要想了解你在学校学过什么课程,在这些课程中你学到了什么,以及你如何将这些知识融入到你的设计中。比如:交互设计的原则是什么?如何将它们应用到你自己的作品中? (三面):主要是简单的认识你,喜欢比较轻松 (四面):面试你的是其他部门的设计师。他们应该希望不同的部门进行比较。这与你的工作细节和设计过程密切相关。与交互设计的细节相比,他们更注重细节 1你的设计理念基于什么样的洞察力? 2你的设计理念如何证明它的有效性? (五面):HR也会谈作品集,但主要问题是作品集的设计过程,遇到的困难是什么,互动的细节是否达成 建议大家提早几个月开始准备作品集哦!
分享
3
原味笔面经
__柔情。
中南大学·2022届

内推有什么好处?

内推:顾名思义,内部推荐。指在求职中,不通过常规的简历投递渠道(包括但不限于网申、双选会、宣讲会现场投递)等方式,而是通过已经在某企业就职的内部员工,将各方面条件优秀的求职者的简历直接投递到HR或部门负责人手中的一种招聘手段。 ▶ 内推只是另一种投递简历的方式 有很多同学会陷入一种误区,认为在同等条件水平下(此处包括但不限于学校、学历、技术能力、投递时间等客观因素),内推的同学会比通过常规渠道投递简历的同学更有优势。 事实上,无论你是通过内推还是通过常规的简历投递渠道投递简历,最终你还是要面对面试(而且有些内推你还需要面对笔试)。【而在进行面试的时候,面试官是不会在意你投递简历的方式究竟是内推还是网申的,这时候考察的仅仅是你的真才实学。】 因此,如果现在还有同学抱着“我技术不是特别好,只能靠内推来争取更大优势”这样幼稚的幻想,可以清醒一下了。 ▶ 内推并不是“有推必应” 此处针对大家在网上寻找到内推人,并把自己的简历发送给内推人,请求这些“陌生人”帮你进行内推。 试想,一个在互联网公开平台上发布内推信息的内推人,他可能每天会收到上百份简历,而他作为内推人(且只是部门员工,不是专业HR的情况下),每份简历可能只能大致浏览一下,选择自己认为符合条件的简历转发给HR,不符合条件的看过也就算了。 非专业HR的内推人每天会有很多自己的工作和琐事,也有自己的生活。专业HR尚且做不到100%地回应(此处特指回绝)所有参加招聘的同学,更何况这些顺手帮忙内推的内推人呢? 因此,请同学们明白:【不是参加了内推就能够得到回应。】 ▶ 关于内推码 事实上,比之发简历给内推人,让内推人帮忙内推这种形式,内推码内推有些简单粗暴。全网空投一个码,捡到就可以填在网申简历上,并称之为“内推”。 这种内推形式,求职者与内推人几乎是没有沟通的,且这种形式的内推一般是不会免除笔试的。 说句大白话,除了投递时间比网申早一点之外,基本上没有任何别的大差别了。 当然,这种形式的内推优势也是有的,毕竟早投递早笔试早面试早占坑位,早起的鸟儿有虫吃,赶早总不是坏事。 但是【到了笔试/面试,并不会有人在意你究竟是内推还是网申,一切都倚仗你的真才实学,你真正的技术能力。】 ▶ 内推的好处 上面说了那么多内推的“坏话”,想必有同学会质疑,那我们为什么还要参加内推?直接网申不就好了吗?内推存在了这么多年,且一直有越来越“火”的趋势,说明它的的确确是有好处的。 第一,时间早,坑位多。 内推批次的时间普遍会比正式开放网申的时间早,有的早一周,有的可能早一个月。而每家公司每年的坑位是固定的,越早去应聘,留给你的坑位就越多。早起的鸟儿有虫吃,内推可以帮你打一个时间差,在大多数坑位还在,而只有少数人参加的时候,帮你抢占先机。 第二,有免简历筛选/笔试的机会。 一般的内推会承诺可以免除简历筛选,也有一些内推是承诺可以免除笔试的(多见于发简历给内推人,内推人筛选后再推给HR的情况,极少出现在内推码形式中),免除笔试是非常大的优势,毕竟一般情况下,正式校招笔试会刷掉至少50%以上的人。 第三,多一次机会。 内推最大的好处,莫过于能够多一次机会了。因为几乎所有公司的内推和校招都是分开进行的两个流程,内推不过的同学,网申还可以再次投递简历,再次进行笔面试流程。 但这里也要提醒大家一点,内推的时候态度认真一点,因为你的信息可能就会被记录在人才库中,信息不要有误。 ▶ 对内推的态度 1、不要对内推抱有不切实际的幻想,内推带来的优势无法帮你弥补技术上的不足,只有实力,+一点点的运气与机缘,才能助你拿到满意的offer; 2、有内推的机会,还是要去尝试。毕竟内推最大的好处是多一次机会,即使是第一轮面试就被刷了,那也是一次难能可贵的实战经历。 3、不要因为内推受挫而自怨自艾自暴自弃,请返回本文最前面查看“内推”的定义,内推本质上是为了选拔最最拔尖的那一部分人才,作为普通优秀的我们,有时候会在内推批次受挫,这不是因为你太菜,而是因为天外有天。因此不必在内推批次受挫时感到过于难过,自暴自弃甚至怀疑人生,正确面对挫折,总结提升自己,争取能够在接下来的“战斗”中获得好的结果,这才是面对失败的正确态度。 无论是内推还是网申,无论你是站着投简历还是趴着投简历,【最终决定你是否能被企业录取的唯一标准,只是你的实力是否足够强大】。所以,与其挖空心思四处寻找内推,不如静下心来,好好梳理自己的简历,提升求职能力。
分享
3
校招情报局
妙奇青小莲
广东金融学院·2022届

工商银行面经

今天面试了工行安徽分行管培岗位。第一轮为问题回答+半结构化,第二轮群面是集体做任务。 第一轮 问题的背景是关于快递量激增造成的垃圾处理难题,需要根据材料总结问题来源,然后自己提出5条解决方案。3分钟读题,在前一位面试者出来之前都可以继续准备。半结构化面试,主要还是根据简历提问,所以简历信息一定要尽量真实,自己要足够熟悉。我的问题涉及我实习时写的一篇报告,以及国外留学和境内学习的区别。 第二轮 由前程无忧的工作人员主持,30+的面试者分为7组一组4到5人,每组桌上有5块七巧板,整个考场里有7套完整的七巧板。每组在开始时会得到一张任务卡,这个任务卡每组不同且不可交换,还有一张图形卡,需要根据图形卡上的图形进行拼凑。每完成一项任务可以得到一定的分数。40分钟结束,每组会有一个总分,而且会算全部7个队伍的得分总和,所以既要提高自己的分数也要帮助别的队伍,你的所有表现都会有人做记录,应该会有相应得分。整个过程相当混乱,如果有能力做整体的组织者会能够对全体提供很大的帮助。面试完交流得知有一个组的任务就是这个但是他们自己没看懂……
分享
评论
原味笔面经
不念
江西财经大学·2022届

一年半经验前端社招——拼多多(已面完hr)

在鹅厂也呆了一年多了,业务一般般,而且很忙(晚上10点+,每天不定时随时oncall)。希望能换个平台寻求更好的发展和更高的技术视野,也希望能找到轻松一些的工作,所以出来看机会了。个人选择的原则:满足工作生活平衡、付出收益平衡、业务未来可观三者之一。近来有一批面试,整理完每一篇后,会持续更新《一年半经验前端社招》系列的文章下面的题目,都会标明每一题的性质,也会给出一些参考思路 描述:对概念、过程的描述,纯理论性问答题为主 举例:说出应用场景,或者是自己团队实践的情况 伪代码:写代码,但不需要跑起来,甚至可以随便写伪代码,主要目的是描述思路 编程:真正的写代码,需要跑起来,有测试用例,要看到效果 hr面的话,是一些日常、项目回顾(技术细节少说)、职业规划、为什么跑路、为什么选择这边、目前薪资和职级、期望薪资。hr面基本类似,后面会出一个hr面专题 公司面试难度评估:✭✭✭✩✩ 【1面】 pdd用的是他们自己家的面试系统,该有的功能都有,web-ide体验稍微差一些。 react16新生命周期,有什么变化【描述】 两个static、一个didcatch捕获错误的、一个getsnapshot react16之前的那些不好的生命周期怎么过度到react16的新生命周期【描述】【举例】 getDriverStateFromProps替代componentWillReceiveProps,加上逻辑对比上次state和props来决定state。willupdate换成getSnapshotBeforeUpdate,willmount直接写成初始state(react16的state不先写出来是null,你需要先在class组件里面写一下state = {...}) componentWillReceiveProps用到了this,getDriverStateFromProps也要用,怎么办【伪代码】 把this.xxx存到state里面,第二个参数是state,里面有xxx(有点挫,懂的人应该都有同样的感受吧,如果是函数组件,一个useRef保存一下即可)。另外的方法,如果和内部变量无关,把它抠到class组件外面去 编程题:['aaafsd', 'aawwewer', 'aaddfff'] => 'aa'(ide没有调试功能,也不能打开控制台,我只能写好了让面试官去运行。无调试,靠想象)【编程】 编程题:['aa/bb/sd', 'aa/bb/wwewer', 'aa/bb/ddfff'] => 'aa/bb'(无调试,靠想象)【编程】 接上题改一下,不用一分钟即可解决 怎么理解ts【描述】 类型检查、ide友好提示、文档、利于维护 ts的type和interface什么区别【描述】 经典问题,网上可搜,主要是列举出两者的特点,对比一下 ssr怎么实现,你们怎么做【描述】【举例】 将动态渲染逻辑做到后端去,并把最终html结果直接返回。我们这边是数据动静分离+部分ssr直出,重要的数据ssr,比较慢的接口还是放前端 你们有没有统一构建的cli,怎么实现【描述】【举例】 基于react全家桶,ts、eslint、埋点上报、sw都可配置,根据配置生成代码模版,开箱即用 你们项目有ci吗,怎么做,提交的时候会做什么事情【描述】【举例】 通过接入公司内部某ci,配置yaml文件,每次监听git hook,并作出对应的行为如安装、lint、部署、搬运、生成change log等等。提交的时候,检查lint、修复autofixable的问题,存在修不了的问题报错,提交失败 e2e测试、自动化测试【描述】 概念性问题,网上容易搜到 git rebase什么作用【描述】 概念性问题,网上容易搜到 一面的面试官很友好,虽然戴口罩,但也可以感觉到满满的笑容。面试过程中,我的问题还没问够,他说你可以问2面面试官 【2面】 面试官说系统有问题,开不了摄像头,于是加了微信,微信电话+系统在线写代码结合。不得不说,pdd这个系统很严格,我拖一下窗口就说作弊警告,然后接了一下面试官微信电话又作弊警告,面试官说问题不大,不慌。面试官很严格,全程一本正经,虽然没看见人,但强大的气场让我有一点心凉的感觉 项目介绍【描述】 项目难点【描述】【举例】 一定要拿出最熟悉最自信最能体现自己的项目,这一块是确定面试表现的关键环节。面试中这里可以问个半小时以上。包装假项目的、吹牛的,到这里基本就可以区分出来了。还有项目比较浅的,到这里如果进入尬聊或者冷场,很可能就挂了的。反正我是可以保证自己掌控主动权的,让面试官跟我思路走 实现一个redux【编程】 10几行经典redux,途中会顺便问一下函数式编程、纯函数、副作用这些,网上搜“函数式编程”即可知道这些概念了 如果是用ts写,怎么写【编程】 改成ts版本,如果比较熟悉redux+ts的,很快写出来。如果不太熟,熟悉泛型也可以根据表现,很快写出近似的。我说我没有ide提示,不能保证裸写没问题。面试官说没事,只是看看你ts熟悉程度 最后 pdd钱给的很多,算是top水平的了,base低的可以去pdd搞一波,快速提升base和pkg。但是pdd单休,平时加班很晚,项目节奏很快很紧凑,所以还是看个人取舍吧。我的话,因为pdd在上海就不考虑了,hr表示也理解。 我自己没有搭建个人网站,一般就用用别人的平台发发文章,后面会看看。掘金和github可以搜 lhyt 找到我文章和个人沉淀
分享
5
原味笔面经

超级简历 APP

从简历直达offer,快人一步拿高薪

最新内推
35 名用户可以帮你内推
16 名用户可以帮你内推
13 名用户可以帮你内推
10 名用户可以帮你内推
9 名用户可以帮你内推
推荐投递
华为
科锐福克斯
科锐福克斯
北京科美划一科技有限公司
北京科美划一科技有限公司
北京科美划一科技有限公司