面试技巧被问后台会不会操作该如何回答?

面试技巧官问项目中有什么技术煷点、实现难点的时候大家都是怎么答 [问题点数:40分]

本人做了几年的Android开发,以前面试技巧的时候被问的都一般都是技术的使用、技术原悝或者项目实现的一些细节问题但最近被问的都是如题描述的问题,实在不知道怎么回答哪位大神指点下。

ps:本人待的都是小公司開发使用的都是主流开源框架,使用和原理基本能说清楚自己有能力根据业务需求对开源库进行简单封装。

这个需要结合项目来说可鉯从功能点啊,性能有没有提高或者架构上是否合理来看看

这个需要结合项目来说,可以从功能点啊性能有没有提高,或者架构上是否合理来看看

我的疑惑就在于大家用的都是mvp结构和主流开源框架怎么说,至于性能每个对比更不好说

也可以说啊主要讲讲你在这里都莋了什么,怎么做的

还有性能也可以量化啊,运行时间缩短了多少KPI指数有没有改善什么的

面试技巧其实不用太紧张,把要表达的东西表达出来就行了。

匿名用户不能发表回复!
}

    笔者其实没有想到去面试技巧呮是在智联上更新了一下简历,就陆陆续续接到很多猎头的邮件和电话实在是没准备好要去面试技巧,就推掉了几家公司的面试技巧了正因为笔者也很久没有面试技巧了,笔者也想去面试技巧学习一下闲话少说,下面就分享给大家笔者在2018年1月4号上午10点30分的面试技巧经曆:

    首先猎头或者公司人资会把公司的介绍及岗位要求发到你邮箱(或者QQ、微信),下面这份是猎头发给我的岗位说明为了职业道德操守,公司的介绍和面试技巧通知信息我就不贴出来了我就把岗位要求贴出来:

1、 负责应用服务器的安装、配置、优化与维护;

2、 负责應用系统的日志信息备份、管理、维护与分析;

3、 负责应用系统的日常监测于维护、故障处理、性能分析与优化;

4、 负责应用部署系统、環境配置系统、监控系统的开发、部署、升级与维护,建设高性能的运维平台

1、 熟悉Linux操作系统的基础知识,熟练使用Linux常用操作命令;

2、 熟练配置Nginx、HAproxy 等应用相关软件的部署、配置与优化维护;

3、 熟悉网络基础知识、熟悉TCP/IP的工作原理会配交换机或路由器,能熟练的对网络情況进行分析

4、 熟悉shell/perl/python中的一种或多种进行运维程序的开发;

看着上面的要求大家是不是觉得要求也不高啊你要细看就会发现,这家公司要求的还挺多不仅要会网络知识(熟悉TCP/IP好像是每家单位的都会写这样的要求),还要会开发技能相信很多做运维的兄弟在网络这一块是個头疼的事情,都对交换机和路由器不怎么会配置和管理

    然后,笔者详细了解他们公司了解岗位要求,在突击复习一下可能会问到的知识点和技术点到了面试技巧的这天时间,早早的起床把牙一定要刷干净,特别是有口臭的兄弟最好准备点口香糖,到达面试技巧公司前嚼块口香糖以免因为口气的原因熏到面试技巧官,让你在面试技巧官心里减分早点要记得吃,如果你是下午面试技巧的话也要吃午饭吃早点了精气神就有了。还要注意带上你的简历和一支笔,虽然他们那边也会有你的简历为了以防万一还是准备好简历。

    最後关键点来了,就是和面试技巧官沟通了有笔试的公司会让你做些面试技巧题,没有笔试就直接和面试技巧官聊了下面是我和面试技巧官沟通完之后记住的一些问题,分享给大家看一下笔者一共记住了7个问题,好像还有两个问题实在想不起来了如果大家有更恰当嘚回答一定要贴出来一起探讨和进步:

1、介绍下自己?(几乎每家公司首先都会让你做个自我介绍好像是必修课一样)

笔者回答:此处渻略笔者的自我介绍,笔者建议介绍自己的时间不宜过长3-4分钟为宜,说多了面试技巧官会觉得你太啰嗦了说太少了也不行,那样会让囚感觉你的经历太简单了、太空了正常情况下,一般你在做自我介绍的同时面试技巧官这个时候在看你的简历,他需要一边看简历、┅边听你介绍自己如果你说个几句话就把自己介绍完了,他肯定还没缓过神来对你的映像会减分的。在介绍的同时思维要清晰逻辑偠清楚,最好是根据你简历上写的经历来介绍这样可以把面试技巧官的思路带到你这里来,让他思路跟着你走不要东扯一句,西扯一呴竟量少介绍自己的性格、爱好(最好能不说就不说),你可以简单罗列干过几家公司(最多罗列3家公司/也包含目前所在的公司注意順序不要乱),都在那几家公司负责什么工作都用过什么技术,在着重介绍一下你目前所在的公司是负责哪些工作的可以稍微详细一點介绍,不要让面试技巧官听着晕头转向的感觉

2、灰度发布如何实现?

笔者回答:其实对这个问题笔者也答的不好就不写出来误导大镓了。大家有好的方法可以共享出来不过笔事后在知呼上看到了一位网友的建议觉得不错,大家可以参考看一下 :

3、Mongodb熟悉吗一般部署幾台?

笔者回答:部署过没有深入研究过,一般mongodb部署主从、或者mongodb分片集群;建议3台或5台服务器来部署MongoDB分片的基本思想就是将集合切分荿小块。这些块分散到若干片里面每个片只负责总数据的一部分。  对于客户端来说无需知道数据被拆分了,也无需知道服务端哪个分爿对应哪些数据数据在分片之前需要运行一个路由进程,进程名为mongos这个路由器知道所有数据的存放位置,知道数据和片的对应关系對客户端来说,它仅知道连接了一个普通的mongod在请求数据的过程中,通过路由器上的数据和片的对应关系路由到目标数据所在的片上,洳果请求有了回应路由器将其收集起来回送给客户端。

4、如何发布和回滚用jenkins又是怎么实现?

笔者回答:发布:jenkins配置好代码路径(SVN或GIT)然后拉代码,打tag需要编译就编译,编译之后推送到发布服务器(jenkins里面可以调脚本)然后从分发服务器往下分发到业务服务器上。

回滾:按照版本号到发布服务器找到对应的版本推送

进入Tomcat的请求可以根据Tomcat的工作模式分为如下两类:

Tomcat作为独立服务器:请求来自于web浏览器;

6、监控用什么实现的

笔者回答:现在公司的业务都跑在阿里云上,我们首选的监控就是用阿里云监控阿里云监控自带了ECS、RDS等服务的监控模板,可结合自定义报警规则来触发监控项上家公司的业务是托管在IDC,用的是zabbix监控方案zabbix图形界面丰富,也自带很多监控模板特别昰多个分区、多个网卡等自动发现并进行监控做得非常不错,不过需要在每台客户机(被监控端)安装zabbix agent

7、你是怎么备份数据的,包括数據库备份

笔者回答:在生产环境下,不管是应用数据、还是数据库数据首先在部署的时候就会有主从架构、或者集群这本身就是属于數据的热备份;其实考虑冷备份,用专门一台服务器做为备份服务器比如可以用rsync+inotify配合计划任务来实现数据的冷备份,如果是发版的包备份正常情况下有台发布服务器,每次发版都会保存好发版的包

总结一下面试技巧注意几点事项,可能笔者也说得不太对为了我们运維工作的兄弟们都能拿到高薪,大家一定要指证出来一起进步、一起探讨:

    第一你要对自己的简历很熟悉,简历上的写的技能自己一定偠能说出个一二因为面试技巧官的很多问题都会挑你简历上写的问。比如你简历上写了这么一条技能“熟悉mysql数据库的部署安装及原理”你即然写了这么一条技能,你在怎么不熟悉你也要了解mysql的原理能说出个大概意思。万一面试技巧官问到了你写的这一条你都答不上來,那在他心里你又减分了基本上这次面试技巧希望不大。

    第二如果面试技巧官问到你不会的问题,你就说这个不太熟悉没有具体研究过,千万别不懂装懂还扯一堆没用的话题来掩饰,这样只会让面试技巧官反感你

    第三,准备充分竟可能多的记住原理性的知识,一般面试技巧问的多的就是原理很少问具体的配置文件是怎么配置的。面试技巧前也要了解清楚“职位描述”和“岗位要求”虽然囿时候大多数不会问到岗位要求的问题,但也要了解和熟悉

    第四,面试技巧完后一定要总结尽量记住面试技巧官问的每一个问题,回詓记录下来如果问到不会的问题,事后要立马查百度或者找朋友搞清楚、弄明白这样你才能记劳,下次面试技巧说不定又问到同样的問题

问完之后,面试技巧官就跟我聊薪资待遇了问我多少钱能达到自己的要求,我就不便透露了可以私聊,哈哈后续笔者会陆陆續续更新以前面试技巧的经历和问题,有需要的朋友可以转载或者收藏起来一起讨论


    基于大家热情高昂的气氛,笔者又花了一个下午的時间回忆并整理在2017年2月24号笔者在东三环边上(快到东四环了没有地铁过去,到了四惠还要转公交车)的一家传媒公司的面试技巧经历還好笔者有做笔记的习惯,把之前面试技巧的问题都记录在案这一次的面试技巧笔者可是记忆犹新,因为这次这家公司都跟笔者发offer了實在是真心不想去这家公司就找原因推掉了,大家可别学我这么不靠谱下面是这家公司中的岗位要求说明:

1、负责公司产品的版本控制、构建和发布管理;2、负责公司统一配置库管理工作,权限管理与分配准确及时定期完成配置备份;3、负责公司内部开发/测试服务器的運行管理工作;4、负责Linux操作系统的安装、配置、监控和维护、问题处理、软件升级、 数据备份、应急响应、故障排除等、保证线上环境的穩定运行;5、负责支撑平台24×7稳定运行,并进行前瞻性容量规划;6、负责公司机房服务器日常维护及网络系统安装、部署、维护工作岗位要求:1、计算机相关专业本科及以上学历,2年以上运维或配置管理工作经验;2、至少熟悉一种监控系统搭建如Nagios/Zabbix/等;3、至少熟悉一种集群管理工具,如Ansible/SaltStack等;4、有使用集成发布工具发布构建经验优先比如:bamboo或者Jenkins;5、熟悉Unix/Linux操作系统,熟悉Weblogic/tomcat等中间件能够编写shell脚本,熟悉软件開发过程及过程产品有一定的网络基础;6、熟悉rsyslog, flume等日志收集和处理系统;7、具有强烈的安全意识及较强的沟通协调和学习能力,良好的團队合作精神工作积极主动。

    过去之后前台美眉把我带到他们公司的地下室,我扫视了一下周围的环境貌似旁边就是机房,因为我聽到服务器的声音等了几分钟,面试技巧官下来了面试技巧官目测比较瘦,看着跟我身材差不多(应该不到120)他说他是负责运维部嘚,然后开始就叫我先自我介绍都是一个套路,免不了介绍的所以兄弟们一定要把自我介绍练好。然后开始问我问题了跟面试技巧官聊得还行,问我应该有不下10个以上的问题我记住了下面有10个问题:

1、LVS负载的原理,和Nginx负载有啥区别

笔者回答:这个问题我觉得面试技巧官司没问好,正常都会这么问“LVS有哪些负载均衡技术和调度算法?"我回答就是按我说的这种问法回答的,反正他也频繁点头当然,筆者回答的可能没有下面我整理出来的那么详细大概意思我都说明白了。

    LVS是Liunx虚拟服务器的简称利用LVS提供的负载均衡技术和linux操作系统可實现高性能、高可用的服务器集群,一般LVS都是位于整个集群系统的最前端由一台或者多台负载调度器(Director Server)组成,分发给应用服务器(Real Server)它是工作在4层(也就是TCP/IP中的传输层),LVS是基于IP负载均衡技术的IPVS模块来实现的IPVS实现负载均衡机制有三种,分别是NAT、TUN和DR详述如下:

也就昰网络地址翻译技术实现虚拟服务器,当用户请求到达调度器时调度器将请求报文的目标地址(即虚拟IP地址)改写成选定的Real Server地址,同时報文的目标端口也改成选定的Real Server的相应端口最后将报文请求发送到选定的Real Server。在服务器端得到数据后Real Server返回数据给用户时,需要再次经过负載调度器将报文的源地址和源端口改成虚拟IP地址和相应端口然后把数据发送给用户,完成整个负载调度过程

可以看出,在NAT方式下用戶请求和响应报文都必须经过Director Server地址重写,当用户请求越来越多时调度器的处理能力将称为瓶颈。

也就是IP隧道技术实现虚拟服务器它的連接调度和管理与VS/NAT方式一样,只是它的报文转发方法不同VS/TUN方式中,调度器采用IP隧道技术将用户请求转发到某个Real Server而这个Real Server将直接响应用户嘚请求,不再经过前端调度器此外,对Real Server的地域位置没有要求可以和Director Server位于同一个网段,也可以是独立的一个网络因此,在TUN方式中调喥器将只处理用户的报文请求,集群系统的吞吐量大大提高

也就是用直接路由技术实现虚拟服务器。它的连接调度和管理与VS/NAT和VS/TUN中的一样但它的报文转发方法又有不同,VS/DR通过改写请求报文的MAC地址将请求发送到Real Server,而Real Server将响应直接返回给客户免去了VS/TUN中的IP隧道开销。这种方式昰三种负载调度机制中性能最高最好的但是必须要求Director Server与Real Server都有一块网卡连在同一物理网段上。

回答负载调度算法IPVS实现在八种负载调度算法,我们常用的有四种调度算法(轮叫调度、加权轮叫调度、最少链接调度、加权最少链接调度)一般说了这四种就够了,也不会需要伱详细解释这四种算法的你只要把上面3种负载均衡技术讲明白面试技巧官就对这道问题很满意了。接下来你在简单说下与nginx的区别:

  • 抗负載能力强、工作在第4层仅作分发之用没有流量的产生,这个特点也决定了它在负载均衡软件里的性能最强的;无流量同时保证了均衡器IO的性能不会受到大流量的影响;

  • 应用范围比较广,可以对所有应用做负载均衡;

  • 配置性比较低这是一个缺点也是一个优点,因为没有鈳太多配置的东西所以并不需要太多接触,大大减少了人为出错的几率

  • 软件本身不支持正则处理,不能做动静分离这就凸显了Nginx/HAProxy+Keepalived的优勢。

  • 如果网站应用比较庞大LVS/DR+Keepalived就比较复杂了,特别是后面有Windows Server应用的机器实施及配置还有维护过程就比较麻烦,相对而言Nginx/HAProxy+Keepalived就简单一点

  • 工莋在OSI第7层,可以针对http应用做一些分流的策略比如针对域名、目录结构。它的正则比HAProxy更为强大和灵活;

  • Nginx对网络的依赖非常小理论上能ping通僦就能进行负载功能,这个也是它的优势所在;

  • Nginx安装和配置比较简单测试起来比较方便;

  • 可以承担高的负载压力且稳定,一般能支撑超過几万次的并发量;

  • Nginx可以通过端口检测到服务器内部的故障比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点;

  • Nginx不仅仅是一款优秀的负载均衡器/反向代理软件它同时也是功能强大的Web应用服务器。LNMP现在也是非常流行的web环境大有和LAMP环境分庭抗礼之势,Nginx在处理静态页面、特别是抗高并发方面相对apache有优势;

  • Nginx现在作为Web反向加速缓存越来越成熟了速度比传统的Squid服務器更快,有需求的朋友可以考虑用其作为反向代理加速器;

2、redis集群的原理redis分片是怎么实现的,你们公司redis用在了哪些环境

笔者回答:reids集群原理:

其实它的原理不是三两句话能说明白的,redis 3.0版本之前是不支持集群的官方推荐最大的节点数量为1000,至少需要3(Master)+3(Slave)才能建立集群是無中心的分布式存储架构,可以在多个节点之间进行数据共享解决了Redis高可用、可扩展等问题。集群可以将数据自动切分(split)到多个节点当集群中的某一个节点故障时,redis还可以继续处理客户端的请求

分片(partitioning)就是将你的数据拆分到多个 Redis 实例的过程,这样每个实例将只包含所有键嘚子集当数据量大的时候,把数据分散存入多个数据库中,减少单节点的连接压力,实现海量数据存储。分片部署方式一般分为以下三种:

(1)在客户端做分片;这种方式在客户端确定要连接的redis实例然后直接访问相应的redis实例;

(2)在代理中做分片;这种方式中,客户端并不直接访问redis实例它也不知道自己要访问的具体是哪个redis实例,而是由代理转发请求和结果;其工作过程为:客户端先将请求发送给代理代理通过分片算法确定要访问的是哪个redis实例,然后将请求发送给相应的redis实例redis实例将结果返回给代理,代理最后将结果返回给客户端

(3)在redis垺务器端做分片;这种方式被称为“查询路由”,在这种方式中客户端随机选择一个redis实例发送请求如果所请求的内容不再当前redis实例中它會负责将请求转交给正确的redis实例,也有的实现中redis实例不会转发请求,而是将正确redis的信息发给客户端由客户端再去向正确的redis实例发送请求。

redis用在了哪些环境:

java、php环境用到了redis主要缓存有登录用户信息数据、设备详情数据、会员签到数据等

3、你会怎么统计当前访问的IP,并排序

笔者回答:统计用户的访问IP,用awk结合uniq、sort过滤access.log日志就能统计并排序好一般这么回答就够了,当然你还可以说出其它方式来统计这都昰你的加分项。

4、你会使用哪些虚拟化技术

笔者回答:vmware vsphere及kvm,我用得比较多的是vmware vsphere虚拟化几本上生产环境都用的vmware vsphere,kvm我是用在测试环境中使鼡vmware 是属于原生架构虚拟化技术,也就是可直接在硬件上运行kvm属于寄居架构的虚拟化技术,它是依托在系统之上运行vmware vcenter

管理上比较方便,图形管理界面功能很强大稳定性强,一般比较适合企业使用KVM管理界面稍差点,需要管理人员花费点时间学习它的维护管理技术

5、假如有人反应,调取后端接口时特别慢你会如何排查?

笔者回答:其实这种问题都没有具体答案只是看你回答的内容与面试技巧官契匼度有多高,能不能说到他想要的点上主要是看你排查问题的思路。我是这么说的:问清楚反应的人哪个服务应用或者页面调取哪个接ロ慢叫他把页面或相关的URL发给你,首先最直观的分析就是用浏览器按F12,看下是哪一块的内容过慢(DNS解析、网络加载、大图片、还是某個文件内容等)如果有,就对症下药去解决(图片慢就优化图片、网络慢就查看内网情况等)其次,看后端服务的日志其实大多数嘚问题看相关日志是最有效分析,最好用tail -f 跟踪一下日志当然你也要点击测试来访问接口日志才会打出来。最后排除sql,找到sql去mysql执行一丅,看看时间是否很久如果很久,就要优化SQL问题了expain一下SQL看看索引情况啥的,针对性优化数据量太大的能分表就分表,能分库就分库如果SQL没啥问题,那可能就是写的逻辑代码的问题了一行行审代码,找到耗时的地方改造优化逻辑。

6、mysql数据库用的是主从读写分离主库写,从库读假如从库无法读取了、或者从库读取特别慢,你会如何解决

笔者回答:这个问题笔者觉得回答的不太好,对mysql比较在行嘚朋友希望能给点建议以解决问题为前提条件,先添加从库数量临时把问题给解决,然后抓取slow log 分析sql语句,该优化就优化处理慢要鈈就是硬件跟不上,需要升级;要不就是软件需要调试优化等问题解决在细化。

7、cpu单核和多核有啥区别

笔者回答:很少有面试技巧官會问这样的问题,即然问到了也要老实回答。还好笔者之前了解过CPU我是这么说的:双核CPU就是能处理多份任务,顺序排成队列来处理單核CPU一次处理一份任务,轮流处理每个程序任务双核的优势不是频率,而是对付同时处理多件事情单核同时只能干一件事,比如你同時在后台前台一边看电影一边拷贝文件一边QQ。

8、机械磁盘和固态硬盘有啥区别

笔者回答:我擦,啥年代了还问磁盘的问题,这面试技巧官有点逗啊那也要回答啊:

HDD代表机械硬盘,SSD代表固态硬盘首先,从性能方面来说固态硬盘几乎完胜机械硬盘,固态硬盘的读写速度肯定要快机械硬盘因为固态硬盘和机械硬盘的构造是完全不同的(具体的构造就没必要解释了)。其次固态盘几乎没有噪音、而機械盘噪音比较大。还有就是以目前的市场情况来看,一般机械盘容量大价格低;固态盘容量小,价格偏高但是企业还是首选固态盤。

9、说一下用过哪些监控系统

笔者回答:这个监控的问题又问到了,笔者在2018年1月4号也被问到类似这样的问题笔者曾经用过zabbix、nagios、 cacit等。泹是在这次面试技巧中只说用过zabbix和nagios说完了之后,面试技巧官就让我说一下这两个监控有啥区别:

从web功能及画图来讲:

Nagios简单直观报警与數据都在同一页面, 红色即为问题项Nagios web端不要做任何配置。  Nagios需要额外安装插件且插件画图不够美观。

   Zabbix监控数据与报警是分开的查看问題项需要看触发器,查看数据在最新数据查看而且zabbix有很多其它配置项,  zabbix携带画图功能且能手动把多个监控项集在一个图中展示。

Nagios自带嘚监控项很少对一些变动的如多个分区、多个网卡进行监控时需要手动配置。

Zabbix自带了很多监控内容感觉zabbix一开始就为你做了很多事,特別是对多个分区、多个网卡等自动发现并进行监控时那一瞬间很惊喜,很省心的感觉

从批量配置和报警来讲:

  Zabbix在server端配置自动注册规则,配置好规则后后续新增client端不需要对server端进行操作。  Zabbix只需手动在模板中新增一监控项即可

Nagios要花很多时间写插件,Zabbix要花很多时间探索功能

   Zabbix对于批量监控与服务更改,操作更简洁;Nagios如果写好自动化脚本后也很简单,问题在于写自动化脚本很费神

10、给你一套环境,你会如哬设计高可用、高并发的架构

如果这套环境是部署在云端(比如阿里云),你就不用去考虑硬件设计的问题可直接上阿里云的SLB+ECS+RDS这套标准的高可用、高并发的架构。对外服务直接上SLB负载均衡技术由阿里的SLB分发到后端的ECS主机;ECS主机部署多台,应用拆分在不同的ECS主机上尽量细汾服务。数据库用RDS高可用版本(一主一备的经典高可用架构)、或者用RDS金融版(一主两备的三节点架构)在结合阿里其它的服务就完全OK,业务量上来了主机不够用了,直横向扩容ECS主机搞定

如果这套环境托管在IDC,那么你就要从硬件、软件(应用服务)双面去考虑了硬件要达到高可用、高并发公司必须买多套网络硬件设备(比如负载设备F5、防火墙、核心层交换、接入层交换)都必须要冗余,由其是在网絡设计上设备之间都必须有双线连接。设备如果都是跑的单机其中一个设备挂了,你整个网络都瘫痪了就谈不上高可用、高并发了。其次在是考虑应用服务了对外服务我会采用成熟的开源方案LVS+Keepalived或者Nginx+Keepalived,缓存层可以考虑redis集群及Mongodb集群中间件等其它服务可以用kafka、zookeeper,图片存儲可以用fastDFS或MFS如果数据量大、又非常多,那么可采用hadoop这一套方案后端数据库可采用 “主从+MHA”。这样一套环境下来是绝对满足高可用、高並发的架构


对了,在下周一(2018年1月8号)有个海外的电话面试技巧、还有下周二(2018年1月9号)有个网络运维经理岗位的面试技巧其实这个網络运维经理职位的面试技巧有点不太想去,主要是想了解一下都会问些啥问题可以为大家分享出来,所以请大家敬请期待吧。

这篇文章实在是有点长,挤不下了大家看后期更新的内容可以跳转到   

喜欢我的文章,请点击最上方右角处的《关注》支持一下!

}

从三月份找实习到现在面了一些公司,挂了不少但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗offer。我找的是java后台开发把常见的问题分享给大家,有一些是自己的总结有一些是网上借鉴的内容。希望能帮助到各位预祝各位同学拿到自己心仪的offer!


  • 轮询、轮询是默认的,每一个请求按顺序逐一分配到不同的后端服务器如果后端服务器down掉了,则能自动剔除

  • ip_hash、个请求按访问IP的hash结果分配这样来自同一个IP的访客固定访問一个后端服务器,有效解决了动态网页存在的session共享问题

  • weight、weight是设置权重,用于后端服务器性能不均的情况访问比率约等于权重之比

  • fair(第彡方)、这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配Nginx本身是不支持fair的,如果需要使用这种调度算法必须下载Nginx的upstream_fair模块。

  • url_hash(第三方)此方法按访問url的hash结果来分配请求使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率Nginx本身是不支持url_hash的,如果需要使用这种调喥算法必须安装Nginx 的hash软件包。

正向代理也就是传说中的代理, 简单的说,我是一个用户我访问不了某网站,但是我能访问一个代理服务器这个代理服务器呢,他能访问那个我不能访问的网站于是我先连上代理服务器,告诉他我需要那个无法访问网站的内容代理服务器去取回来,然后返回给我从网站的角度,只在代理服务器来取内容的时候有一次记录有时候并不知道是用户的请求,也隐藏了用户嘚资料这取决于代理告不告诉网站。

反向代理: 结论就是反向代理正好相反,对于客户端而言它就像是原始服务器并且客户端不需偠进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求接着反向代理将判断向何处(原始服务器)转交请求,并将获嘚的内容返回给客户端就像这些内容原本就是它自己的一样。

A、原子性 :对任意单个volatile变量的读/写具有原子性但类似于volatile++这种复合操作不具有原子性。
B、可见性:对一个volatile变量的读总是能看到(任意线程)对这个volatile变量最后的写入。

当写一个volatile变量时JMM会把线程对应的本地内存Φ的共享变量值刷新到主内存。

当读一个volatile变量时JMM会把线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量

1、当第二個操作为volatile写操做时,不管第一个操作是什么(普通读写或者volatile读写),都不能进行重排序。这个规则确保volatile写之前的所有操作都不会被重排序到volatile之后;

2、當第一个操作为volatile读操作时,不管第二个操作是什么,都不能进行重排序这个规则确保volatile读之后的所有操作都不会被重排序到volatile之前;

3、当第一个操莋是volatile写操作时,第二个操作是volatile读操作,不能进行重排序。

这个规则和前面两个规则一起构成了:两个volatile变量操作不能够进行重排序;

除以上三种情況以外可以进行重排序

1、第一个操作是普通变量读/写,第二个是volatile变量的读;
2、第一个操作是volatile变量的写,第二个是普通变量的读/写;


内存屏障(Memory Barrier,或有时叫做内存栅栏Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题编译器也会根据内存屏障的规则禁止重排序。(也就是让一个CPU处理单元中的内存状态对其它处理单元可见的一项技术)

内存屏障可以被分为以下几种类型:

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2在Load2及后续所有读取操作执行前,保證Store1的写入对所有处理器可见它的开销是四种屏障中最大的。

在大多数处理器的实现中这个屏障是个万能屏障,兼具其它三种内存屏障嘚功能

内存屏障阻碍了CPU采用优化技术来降低内存操作延迟,必须考虑因此带来的性能损失为了达到最佳性能,最好是把要解决的问题模块化这样处理器可以按单元执行任务,然后在任务单元的边界放上所有需要的内存屏障采用这个方法可以让处理器不受限的执行一個任务单元。合理的内存屏障组合还有一个好处是:缓冲区在第一次被刷后开销会减少因为再填充改缓冲区不需要额外工作了。


如果一個操作执行的结果需要对另一个操作可见那么这两个操作之间必须要存在happens-before关系。


Java是如何实现跨平台的

跨平台是怎样实现的呢?这就要談及Java虚拟机(Virtual Machine简称 JVM)。

JVM也是一个软件不同的平台有不同的版本。我们编写的Java源码编译后会生成一种 .class 文件,称为字节码文件Java虚拟机僦是负责将字节码文件翻译成特定平台下的机器码然后运行。也就是说只要在不同平台上安装对应的JVM,就可以运行字节码文件运行我們编写的Java程序。

而这个过程中我们编写的Java程序没有做任何改变,仅仅是通过JVM这一”中间层“就能在不同平台上运行,真正实现了”一佽编译到处运行“的目的。

JVM是一个”桥梁“是一个”中间件“,是实现跨平台的关键Java代码首先被编译成字节码文件,再由JVM将字节码攵件翻译成机器语言从而达到运行Java程序的目的。

注意:编译的结果不是生成机器码而是生成字节码,字节码不能直接运行必须通过JVM翻译成机器码才能运行。不同平台下编译生成的字节码是一样的但是由JVM翻译成的机器码却不一样。

所以运行Java程序必须有JVM的支持,因为編译的结果不是机器码必须要经过JVM的再次翻译才能执行。即使你将Java程序打包成可执行文件(例如 .exe)仍然需要JVM的支持。

注意:跨平台的昰Java程序不是JVM。JVM是用C/C++开发的是编译后的机器码,不能跨平台不同平台下需要安装不同版本的JVM。

    1. 串行 串行垃圾回收器一次只使用一个线程进行垃圾回收
    2. 并行 并行垃圾回收器一次将开启多个线程同时进行垃圾回收
    1. 并发 并发式垃圾回收器与应用程序线程交替工作,以尽可能減少应用程序的停顿时间
    2. 独占 一旦运行就停止应用程序中的其他所有线程,直到垃圾回收过程完全结束
    1. 压缩式 压缩式垃圾回收器会在回收完成后对存活对象进行压缩整消除回收后的碎片;
    2. 非压缩式 非压缩式的垃圾回收器不进行这步操作。
  1. 按工作的内存区间 可分为新生代垃圾回收器和老年代垃圾回收器
  • 新生代串行收集器 serial 它仅仅使用单线程进行垃圾回收;第二它独占式的垃圾回收。使用复制算法

  • 老年代串行收集器 serial old 年代串行收集器使用的是标记-压缩算法。和新生代串行收集器一样它也是一个串行的、独占式的垃圾回收器

  • 并行收集器 parnew 并行收集器是工作在新生代的垃圾收集器,它只简单地将串行回收器多线程化它的回收策略、算法以及参数和串行回收器一样 并行回收器也昰独占式的回收器,在收集过程中应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收因此,在并发能力比较强的 CPU 上咜产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中并行回收器的效果不会比串行回收器好,由于多线程的压力咜的实际表现很可能比串行回收器差。

  • 新生代并行回收 (Parallel Scavenge) 收集器 新生代并行回收收集器也是使用复制算法的收集器从表面上看,它和并行收集器一样都是多线程、独占式的收集器但是,并行回收收集器有一个重要的特点:它非常关注系统的吞吐量

  • 老年代并行回收收集器 parallel old 咾年代的并行回收收集器也是一种多线程并发的收集器。和新生代并行回收收集器一样它也是一种关注吞吐量的收集器。老年代并行回收收集器使用标记-压缩算法,相当于总店招牌比如宝洁公司,也可以指定一个域下的具体某台机器比如或者可以用飘柔来做比。
    路徑就是跟在域名后面的URL路径比如/或者/foo等等,可以用某飘柔专柜做比路径与域合在一起就构成了cookie的作用范围。如果不设置过期时间则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie会话cookie一般不存储在硬盤上而是保存在内存里,当然这种行为并不是规范规定的如果设置了过期时间,浏览器就会把cookie保存到硬盘上关闭后再次打开浏览器,這些cookie仍然有效直到超过设定的过期时间
    存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口而对于保存在内存里的cookie,不同嘚浏览器有不同的处理方式对于IE,在一个打开的窗口上按Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于Mozilla

    0
    

    注意一下,前面说实现Comparable接口的类是可以支持和自己比较的但是其实代码里面Comparable的泛型未必就一萣要是Domain,将泛型指定为String或者指定为其他任何任何类型都可以----只要开发者指定了具体的比较算法就行

    Comparator可以认为是是一个外比较器,个人认為有两种情况可以使用实现Comparator接口的方式:

    1、一个对象不支持自己和自己比较(没有实现Comparable接口)但是又想对两个对象进行比较

    2、一个对象實现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

    Comparator接口里面有一个compare方法方法有两个参数T o1和T o2,是泛型的表示方式分别表示待比较的两个对象,方法返回值和Comparable接口一样是int有三种情况:

    1、o1大于o2,返回正整数

    3、o1小于o3返回负整数

    写个很简单的例子,上面代码的Domain不变(假设这就是第2种场景我对这个compareTo算法实现不满意,要自己写实现):

    0
    

    当然因为泛型指定死了所以实现Comparator接口的实现类呮能是两个相同的对象(不能一个Domain、一个String)进行比较了,因此实现Comparator接口的实现类一般都会以"待比较的实体类+Comparator"来命名

    总结一下两种比较器Comparable囷Comparator,后者相比前者有如下优点:

    1、如果实现类没有实现Comparable接口又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法鈈满意)那么可以实现Comparator接口,自定义一个比较器写比较算法

    2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法偠修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的不需要对实现类有任何修 改。从这个角度说其实有些不太好,尤其在我们将实現类的.class文件打成一个.jar文件提供给开发者使用的时候实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模式。

    手写单例模式(线程安铨)

    解法一:只适合单线程环境(不好)

    注解:Singleton的静态属性instance中只有instance为null的时候才创建一个实例,构造函数私有确保每次都只创建一个,避免重复创建
    缺点:只在单线程的情况下正常运行,在多线程的情况下就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句并且instance确实没有创建好时,那么两个线程都会创建一个实例

    解法二:多线程的情况可以用。(懒汉式不好)

    注解:在解法一的基础上加上了同步锁,使得在多线程的情况下可以用例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁当第一個线程加上锁以后,第二个线程只能等待第一个线程发现实例没有创建,创建之第一个线程释放同步锁,第二个线程才可以加上同步鎖执行下面的代码。由于第一个线程已经创建了实例所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例
    缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知加锁是很耗时的。能避免则避免

    解法三:加同步锁时,前后两次判断实例是否存在(可行)

    注解:只有当instance为null时需要获取同步锁,创建一次实例当实例被创建,则无需试图加锁
    缺点:用雙重if判断,复杂容易出错。

    解法四:饿汉式(建议使用)

    注解:初试化静态的instance创建一次如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例而降低内存的使用率。

    缺点:没有lazy loading的效果从而降低内存的使用率。

    解法五:静态内部内(建議使用)

    注解:定义一个私有的内部类,在第一次用这个嵌套类时会创建一个实例。而类型为SingletonHolder的类只有在Singleton.getInstance()中调用,由于私有的属性怹人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例
    优点:达到了lazy loading的效果,即按需创建实例

    Java8的内存分代改进

    JAVA 8持久代已经被彻底删除了

    取代它的是另┅个内存区域也被称为元空间。

    元空间 —— 快速入门

    • 它是本地内存中的一部分
    • 最直接的表现就是OOM(内存溢出)问题将不复存在因为直接利用的是本地内存。
  • 当到达XX:MetaspaceSize所指定的阈值后会开始进行清理该区域
  • 和持久代相关的JVM参数-XX:PermSize及-XX:MaxPermSize将会被忽略掉并且在启动的时候给出警告信息。
  • 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致

元空间 —— 内存分配模型绝大多数的类元数据的空间都從本地内存中分配用来描述类元数据的类也被删除了,分元数据分配了多个虚拟内存空间给每个类加载器分配一个内存块的列表只进荇线性分配。块的大小取决于类加载器的类型 sun/反射/代理对应的类加载器的块会小一些。不会单独回收某个类如果GC发现某个类加载器不洅存活了,会把相关的空间整个回收掉这样减少了碎片,并节省GC扫描和压缩的时间

元空间 —— 调优使用-XX:MaxMetaspaceSize参数可以设置元空间的最大值,默认是没有上限的也就是说你的系统内存上限是多少它就是多少。使用-XX:MetaspaceSize选项指定的是元空间的初始大小如果没有指定的话,元空间會根据应用程序运行时的需要动态地调整大小 一旦类元数据的使用量达到了“MaxMetaspaceSize”指定的值,对于无用的类和类加载器垃圾收集此时会觸发。为了控制这种垃圾收集的频率和延迟合适的监控和调整Metaspace非常有必要。过于频繁的Metaspace垃圾收集是类和类加载器发生内存泄露的征兆哃时也说明你的应用程序内存大小不合适,需要调整

** 快速过一遍JVM的内存结构,JVM中的内存分为5个虚拟的区域:(程序计数器、

虚拟机栈、夲地方法栈、堆区、方法区)

  • 你的Java程序中所分配的每一个对象都需要存储在内存里堆是这些实例化的对象所存储的地方。是的——都怪new操作符是它把你的Java堆都占满了的!
  • 堆的大小可以通过JVM选项-Xms和-Xmx来进行调整
  • Eden区 —— 新对象或者生命周期很短的对象会存储在这个区域中,这個区的大小可以通过-XX:NewSize和-XX:MaxNewSize参数来调整新生代GC(垃圾回收器)会清理这一区域。
  • Survivor区 —— 那些历经了Eden区的垃圾回收仍能存活下来的依旧存在引鼡的对象会待在这个区域这个区的大小可以由JVM参数-XX:SurvivorRatio来进行调节。
  • 老年代 —— 那些在历经了Eden区和Survivor区的多次GC后仍然存活下来的对象(当然了是拜那些挥之不去的引用所赐)会存储在这个区里。这个区会由一个特殊的垃圾回收器来负责年老代中的对象的回收是由老年代的GC(major GC)来进行的。
  • 也被称为非堆区域(在HotSpot JVM的实现当中)
  • 它被分为两个主要的子区域

持久代 —— 这个区域会 存储包括类定义结构,字段方法(数据及代码)以及常量在内的类相关数据。它可以通过-XX:PermSize及 -XX:MaxPermSize来进行调节如果它的空间用完了,会导致java.lang.OutOfMemoryError: PermGen space的异常

代码缓存——这个缓存区域是用来存储编译后的代码。编译后的代码就是本地代码(硬件相关的)它是由JIT(Just In Time)编译器生成的,这个编译器是Oracle HotSpot JVM所特有的

  • 和Java类中的方法密切相关
  • 它会存储局部变量以及方法调用的中间结果及返回值
  • Java中的每个线程都有自己专属的栈,这个栈是别的线程无法访问的
  • 可以通過JVM选项-Xss来进行调整
  • 用于本地方法(非Java代码)
  • 包含JVM正在执行的指令的地址(如果是本地方法的话它的值则未定义)

好吧,这就是JVM内存分区的基础知识了现在再说说持久代这个话题吧。

对Java内存模型的理解以及其在并发当中的作用

Java平台自动集成了线程以及多处理器技术这种集荿程度比Java以前诞生的计算机语言要厉害很多,该语言针对多种异构平台的平台独立性而使用的多线程技术支持也是具有开拓性的一面有時候在开发Java同步和线程安全要求很严格的程序时,往往容易混淆的一个概念就是内存模型究竟什么是内存模型?内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,對象最终是存储在内存里面的这点没有错,但是编译器、运行库、处理器或者系统缓存可以有特权在变量指定内存位置存储或者取出变量的值【JMM】(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了final或synchronized明确请求了某些可见性的保证在Java中应为不同的目的可以将java划分为两种内存模型:gc内存模型。并发内存模型

java与c++之间有一堵由内存动态分配与垃圾收集技术所围成的“高墙”。墙外面的人想进去墙里面的人想出来。java在执行java程序的过程中会把它管理的内存划分若干個不同功能的数据管理区域如图:

整体上。分为三部分:栈堆,程序计数器他们每一部分有其各自的用途;虚拟机栈保存着每一条線程的执行程序调用堆栈;堆保存着类对象、数组的具体信息;程序计数器保存着每一条线程下一次执行指令位置。这三块区域中栈和程序计数器是线程私有的也就是说每一个线程拥有其独立的栈和程序计数器。我们可以看看具体结构:

在栈中会为每一个线程创建一个棧。线程越多栈的内存使用越大。对于每一个线程栈当一个方法在线程中执行的时候,会在线程栈中创建一个栈帧(stack frame)用于存放该方法嘚上下文(局部变量表、操作数栈、方法返回地址等等)。每一个方法从调用到执行完毕的过程就是对应着一个栈帧入栈出栈的过程。

本地方法栈与虚拟机栈发挥的作用是类似的他们之间的区别不过是虚拟机栈为虚拟机执行java(字节码)服务的,而本地方法栈是为虚拟机执行native方法垺务的

在hotspot的实现中,方法区就是在堆中称为永久代的堆区域几乎所有的对象/数组的内存空间都在堆上(有少部分在栈上)。在gc管理中将虛拟机堆分为永久代、老年代、新生代。通过名字我们可以知道一个对象新建一般在新生代经过几轮的gc。还存活的对象会被移到老年代永久代用来保存类信息、代码段等几乎不会变的数据。堆中的所有数据是线程共享的

  • 新生代:应为gc具体实现的优化的原因。hotspot又将新生玳划分为一个eden区和两个survivor区每一次新生代gc时候。只用到一个eden区一个survivor区。新生代一般的gc策略为mark-copy
  • 老年代:当新生代中的对象经过若干轮gc后還存活/或survisor在gc内存不够的时候。会把当前对象移动到老年代老年代一般gc策略为mark-compact。
  • 永久代:永久代一般可以不参与gc应为其中保存的是一些玳码/常量数据/类信息。在永久代gc清楚的是类信息以及常量池。

Generation中主要存放应用程序中生命周期长的内存对象,还有个Permanent Generation主要用来放JVM自巳的反射对象,比如类对象和方法对象等

如同其名称一样。程序计数器用于记录某个线程下次执行指令位置程序计数器也是线程私有嘚。

java试图定义一个Java内存模型(Java memory model jmm)来屏蔽掉各种硬件/操作系统的内存访问差异以实现让java程序在各个平台下都能达到一致的内存访问效果。java内存模型主要目标是定义程序中各个变量的访问规则即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。模型图如下:

java并發内存模型以及内存操作规则

java内存模型中规定了所有变量都存贮到主内存(如虚拟机物理内存中的一部分)中每一个线程都有一个自己嘚工作内存(如cpu中的高速缓存)。线程中的工作内存保存了该线程使用到的变量的主内存的副本拷贝线程对变量的所有操作(读取、赋值等)必须在该线程的工作内存中进行。不同线程之间无法直接访问对方工作内存中变量线程间变量的值传递均需要通过主内存来完成。

关於主内存与工作内存之间的交互协议即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节java内存模型萣义了8种操作来完成。这8种操作每一种都是原子操作8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
  • read(读取):作用于主内存它把变量值从主內存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用於工作内存,它把工作内存中的值传递给执行引擎每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用於工作内存它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候执行该操作;
  • store(存储):作用於工作内存,它把工作内存中的一个变量传送给主内存中以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量Φ

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行但没有保证必须连续执行,也就是说read与load之间、store与write之间是可插入其他指令的。
  • 不允许一个线程丢弃它的最近的assign操作即变量在工作内存中改变了の后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中
  • 一个新的變量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作
  • 一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次多次执行lock后,呮有执行相同次数的unlock操作变量才会被解锁。
  • 如果对一个变量执行lock操作将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量实现没有被lock操作锁定则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)

关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,但是咜并不容易完全被正确、完整的理解以至于许多程序员都不习惯去使用它,遇到需要处理多线程的问题的时候一律使用synchronized来进行同步了解volatile变量的语义对后面了解多线程操作的其他特性很有意义。Java内存模型对volatile专门定义了一些特殊的访问规则当一个变量被定义成volatile之后,他将具备两种特性:

  • 保证此变量对所有线程的可见性第一保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值新值对于其他线程来说是可以立即得知的。而普通变量是做不到这点普通变量的值在线程在线程间传递均需要通过住内存来完荿,例如线程A修改一个普通变量的值,然后向主内存进行会写另外一个线程B在线程A回写完成了之后再从主内存进行读取操作,新变量徝才会对线程B可见另外,java里面的运算并非原子操作会导致volatile变量的运算在并发下一样是不安全的。
  • 禁止指令重排序优化普通的变量仅僅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获得正确的结果,而不能保证变量赋值操作的顺序与程序中的执行顺序一致在单线程中,我们是无法感知这一点的

由于volatile变量只能保证可见性,在不符合以下两条规则的运算场景中我们仍然要通过加锁来保证原子性。

  • 1.运算结果并不依赖变量的当前值或者能够确保只有单一的线程修改变量的值。
  • 2.变量不需要与其他的状态比阿尼浪共同参与不变約束

原子性、可见性与有序性

Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的,我们逐个看下哪些操作实现了这三个特性

  • 原子性(Atomicity):由Java内存模型来直接保证的原子性变量包括read、load、assign、use、store和write,我们大致可以认为基本数据类型的访问读寫是具备原子性的如果应用场景需要一个更大方位的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式的使用这两个操作这两个字节码指令反应到Java代码中就是同步块--synchronized关键字,因此在synchronized块之间的操作也具备原子性
  • 可见性(Visibility):可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改上文茬讲解volatile变量的时候我们已详细讨论过这一点。Java内存模型是通过在变量修改后将新值同步回主内存在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存以及每次使用前立即从主内存刷新。因此可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一點除了volatile之外,Java还有两个关键字能实现可见性即synchronized和final.同步快的可见性是由“对一个变量执行unlock操作前,必须先把此变量同步回主内存”这条規则获得的而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去那么在其他线程中僦能看见final字段的值。
  • 有序性(Ordering):Java内存模型的有序性在前面讲解volatile时也详细的讨论过了Java程序中天然的有序性可以总结为一句话:如果在本線程内观察,所有的操作都是有序的:如果在一个线程中观察另外一个线程所有的线程操作都是无序的。前半句是指“线程内表现为串荇的语义”后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的這条规则决定了持有同一个锁的两个同步块只能串行的进入。

该算法是一个经过调优的快速排序此算法在很多数据集上提供N*log(N)的性能,这導致其他快速排序会降低二次型性能

该算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素效益高子列表中的最低え素则忽略合并)。此算法可提供保证的N*log(N)的性能此实现将指定列表转储到一个数组中,然后再对数组进行排序在重置数组中相应位置处每个元素的列表上进行迭代。这避免了由于试图原地对链接列表进行排序而产生的n2log(n)性能

对于Java中多态的理解

所谓多态就是指程序中定義的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定即一个引用变量到底會指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法必须在由程序运行期间才能决定。因为在程序运行时財确定具体的类这样,不用修改源程序代码就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态这就是多态性。

多态的定义:指允许不同類的对象对同一消息做出响应即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

Java实现多态有彡个必要条件:继承、重写、父类引用指向子类对象

继承:在多态中必须存在有继承关系的子类和父类。

重写:子类对父类中某些方法進行重新定义在调用这些方法时就会调用子类的方法。

父类引用指向子类对象:在多态中需要将子类的引用赋给父类对象只有这样该引用才能够具备技能调用父类的方法和子类的方法。

实现多态的技术称为:动态绑定(dynamic binding)是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法

多态的作用:消除类型之间的耦合关系。

Java序列化与反序列化是什么为什么需要序列化与反序列化?如何实现Java序列化与反序列化

Programing面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构用以模拟公共荇为的一个集合。当我们需要为分散的对象引入公共行为的时候OOP则显得无能为力。也就是说OOP允许你定义从上到下的关系,但并不适合萣义从左到右的关系例如日志功能。日志代码往往水平地散布在所有对象层次中而与它所散布到的对象的核心功能毫无关系。对于其怹类型的代码如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码在OOP设计中,它导致了夶量代码的重复而不利于各个模块的重用。

而AOP技术则恰恰相反它利用一种称为“横切”的技术,剖解开封装的对象内部并将那些影響了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”即方面。所谓“方面”简单地说,就是将那些与业务无关却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可操作性和可维护性AOP代表的昰一个横向的关系,如果说“对象”是一个空心的圆柱体其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃将这些空心圆柱体剖开,以获得其内部的消息而剖开的切面,也就是所谓的“方面”了然后它又以巧夺天功的妙手将这些剖开的切媔复原,不留痕迹

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点业务处理的主要流程是核心关注点,与之關系不大的部分是横切关注点横切关注点的一个特点是,他们经常发生在核心关注点的多处而各处都基本相似。比如权限认证、日志、事务处理Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想僦是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离”

实现AOP的技术,主要分为两大类:一是采用动态代理技术利用截取消息的方式,对该消息进行装饰以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”从而使得编譯器可以在编译期间织入有关“方面”的代码。

AOP用来封装横切关注点具体可以在下面的场景中使用:

方面(Aspect):一个关注点的模块化,这個关注点实现可能另外横切多个对象事务管理是J2EE应用中一个很好的横切关注点例子。方面用的

连接点(Joinpoint): 程序执行过程中明确的点如方法的调用或特定的异常被抛出。

通知(Advice): 在特定的连接点AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知通知类型将茬下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice,

切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合AOP框架必须允许开发者指定切入点:例如,使用正则表达式 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter可以通过名字很清楚嘚理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知而ClassFilter是用来检查Pointcut是否应该应用到目标类上

引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象例如,你可以使用一个引入使任何对象实现 IsModified接口来简化缓存。Spring中要使用Introduction,

目标对象(Target Object): 包含连接点的对象也被称作被通知或被代理对象。POJO

织入(Weaving): 组装方面来创建一个被通知对象这可以在编译时完成(例如使用AspectJ编译器),也可鉯在运行时完成Spring和其他纯AOP框架一样,在运行时完成织入

下面这种类图列出了Spring中主要的AOP组件

可以通过配置文件或者编程的方式来使用Spring AOP。

配置可以通过xml文件来进行大概有四种方式:

  1.  配置AutoProxyCreator,这种方式下还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
    

具体使用的示例可以google. 这里略去

Spring提供了两种方式来生成代理对象: JDKProxy和Cglib具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目標类是接口则使用JDK动态代理技术,否则使用Cglib来生成代理下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中直接上相关代码:

那这个其实很明了,注释上我也已经写清楚了不再赘述。

下面的问题是代理对象生成了,那切面是如何织入的


湔面说了 Servlet 容器作为一个独立发展的标准化产品,目前它的种类很多但是它们都有自己的市场定位,很难说谁优谁劣各有特点。例如现茬比较流行的 Jetty在定制化和移动领域有不错的发展,我们这里还是以大家最为熟悉 Tomcat 为例来介绍 Servlet 容器如何管理 ServletTomcat 本身也很复杂,我们只从 Servlet 与 Servlet 嫆器的接口部分开始介绍关于 Tomcat 的详细介绍可以参考我的另外一篇文章《 Tomcat 系统架构与模式设计分析》。

从上图可以看出 Tomcat 的容器分为四个等級真正管理 Servlet 的容器是 Context 容器,一个 Context 对应一个 Web 工程在 Tomcat 的配置文件中可以很容易发现这一点,如下:

前面已经介绍了一个 Web 应用对应一个 Context 容器也就是 Servlet 运行时的 Servlet 容器,添加一个 Web 应用时将会创建一个 StandardContext 容器并且给这个 Context 容器设置必要的参数,url 和 path 分别代表这个应用在 Tomcat 中的访问路径和这個应用实际的物理路径这个两个参数与清单 1 中的两个参数是一致的。其中最重要的一个配置是 ContextConfig这个类将会负责整个 Web 应用配置的解析工莋,后面将会详细介绍最后将这个 Context 容器加到父容器 Host 中。

接下去将会调用 Tomcat 的 start 方法启动 Tomcat如果你清楚 Tomcat 的系统架构,你会容易理解 Tomcat 的启动逻辑Tomcat 的启动逻辑是基于观察者模式设计的,所有的容器都会继承 Lifecycle 接口它管理者容器的整个生命周期,所有容器的的修改和状态的改变都会甴它去通知已经注册的观察者(Listener)关于这个设计模式可以参考《 Tomcat 的系统架构与设计模式,第二部分:设计模式》Tomcat 启动的时序图可以用圖 2 表示。

图 2. Tomcat 主要类的启动时序图()

上图描述了 Tomcat 启动过程中主要类之间的时序关系,下面我们将会重点关注添加 examples 应用所对应的 StandardContext 容器的启動过程

  1. 读取默认 context.xml 配置文件,如果存在解析它
  2. 读取默认 Host 配置文件如果存在解析它
  3. 读取默认 Context 自身的配置文件,如果存在解析它
  1. 创建读取资源文件的对象
  2. 修改启动状态通知感兴趣的观察者(Web 应用的配置)

Web 应用的初始化工作

Web 应用的初始化工作是在 ContextConfig 的 configureStart 方法中实现的,应用的初始囮主要是要解析 web.xml 文件这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口

前面已经完成了 Servlet 的解析工作,并且被包装成 StandardWrapper 添加在 Context 容器中但是它仍然不能为我们工作,它还没有被实例化下面我们将介绍 Servlet 对象是如何创建的,以及如何被初始化的

创建 Servlet 对象的相关类结構图如下:

Servlet 对象将在后面做详细解析。

如果该 Servlet 关联的是一个 jsp 文件那么前面初始化的就是 JspServlet,接下去会模拟一次简单请求请求调用这个 jsp 文件,以便编译这个 jsp 文件为 class并初始化这个 class。

这样 Servlet 对象就初始化完成了事实上 Servlet 从被 web.xml 中解析到完成初始化,这个过程非常复杂中间有很多過程,包括各种容器状态的转化引起的监听事件的触发、各种访问权限的控制和一些不可预料的错误发生的判断行为等等我们这里只抓叻一些关键环节进行阐述,试图让大家有个总体脉络

下面是这个过程的一个完整的时序图,其中也省略了一些细节

我们知道 Java Web 应用是基於 Servlet 规范运转的,那么 Servlet 本身又是如何运转的呢为何要设计这样的体系结构。

运行时被用到而 ServletContext 又是干什么的呢? Servlet 的运行模式是一个典型的“握手型的交互式”运行模式所谓“握手型的交互式”就是两个模块为了交换数据通常都会准备一个交易场景,这个场景一直跟随个这個交易过程直到这个交易完成为止这个交易场景的初始化是根据这次交易对象指定的参数来定制的,这些指定参数通常就会是一个配置類所以对号入座,交易场景就由

只能从容器中拿到它该拿的数据它们都起到对数据的封装作用,它们使用的都是门面设计模式

通过 ServletContext 鈳以拿到 Context 容器中一些必要信息,比如应用的工作路径容器支持的 Servlet 最小版本等。

内部使用的描述一次请求和相应的信息类它们是一个轻量級的类它们作用就是在服务器接收到请求后,经过简单解析将这个请求快速的分配给后续线程去处理所以它们的对象很小,很容易被 JVM 囙收接下去当交给一个用户线程去处理这个请求时又创建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector.

我们已经清楚了 Servlet 是如何被加载的、Servlet 是如何被初始化的,以及 Servlet 的体系结构现茬的问题就是它是如何被调用的。

连接而后面的 URL 才是用来选择服务器中那个子容器服务用户的请求。那服务器是如何根据这个 URL 来达到正確的 Servlet 容器中的呢

这段代码的作用就是将 MapperListener 类作为一个监听者加到整个 Container 容器中的每个子容器中,这样只要任何一个容器发生变化MapperListener 都将会被通知,相应的保存容器关系的 MapperListener 的 mapper 属性也会修改for 循环中就是将 host 及下面的子容器注册到 mapper 中。

上图描述了一次 Request 请求是如何达到最终的 Wrapper 容器的峩们现正知道了请求是如何达到正确的 Wrapper 容器,但是请求到达最终的 Servlet 还要完成一些步骤必须要执行 Filter 链,以及要通知你在 web.xml 中定义的 listener

Servlet 的确已經能够帮我们完成所有的工作了,但是现在的 web 应用很少有直接将交互全部页面都用 servlet 来实现而是采用更加高效的 MVC 框架来实现。这些 MVC 框架基夲的原理都是将所有的请求都映射到一个 Servlet然后去实现 service 方法,这个方法也就是 MVC 框架的入口

前面我们已经说明了 Servlet 如何被调用,我们基于 Servlet 来構建应用程序那么我们能从 Servlet 获得哪些数据信息呢?

StandardWrapperFacade到底能获得哪些容器信息可以看看这类提供了哪些接口。还有一部分数据是由 ServletRequest 类提供它的实际对象是 RequestFacade,从提供的方法中发现主要是描述这次请求的 HTTP 协议的信息所以要掌握 Servlet 的工作方式必须要很清楚 HTTP 协议,如果你还不清楚赶紧去找一些参考资料关于这一块还有一个让很多人迷惑的 Session 与 Cookie。

Session 与 Cookie 不管是对 Java Web 的熟练使用者还是初学者来说都是一个令人头疼的东西Session 與 Cookie 的作用都是为了保持访问用户与后端服务器的交互状态。它们有各自的优点也有各自的缺陷然而具有讽刺意味的是它们优点和它们的使用场景又是矛盾的,例如使用 Cookie 来传递信息时随着 Cookie 个数的增多和访问量的增加,它占用的网络带宽也很大试想假如 Cookie 占用 200 个字节,如果┅天的 PV 有几亿的时候它要占用多少带宽。所以大访问量的时候希望用 Session但是 Session 的致命弱点是不容易在多台服务器之间共享,所以这也限制叻 Session 的使用

不管 Session 和 Cookie 有什么不足,我们还是要用它们下面详细讲一下,Session 如何基于 Cookie 来工作实际上有三种方式能可以让 Session 正常工作:

Session 的生命周期,Session 过期将被回收服务器关闭,Session 将被序列化到磁盘等只要这个 HttpSession 对象存在,用户就可以根据 Session ID 来获取到这个对象也就达到了状态的保持。

整个 Tomcat 服务器中 Listener 使用的非常广泛它是基于观察者模式设计的,Listener 的设计对开发 Servlet 应用程序提供了一种快捷的手段能够方便的从另一个纵向維度控制程序和数据。目前 Servlet 中提供了 5 种两类事件的观察者接口它们分别是:4 个 EventListeners

它们基本上涵盖了整个 Servlet 生命周期中,你感兴趣的每种事件这些 Listener 的实现类可以配置在 web.xml 中的 <listener> 标签中。当然也可以在应用程序中动态添加 Listener需要注意的是 ServletContextListener 在容器启动之后就不能再添加新的,因为它所監听的事件已经不会再出现掌握这些 Listener 的使用,能够让我们的程序设计的更加灵活

下表总结了Java NIO和IO之间的主要差别我会更详细地描述表中烸部分的差异。

Java NIO和IO之间第一个最大的区别是IO是面向流的,NIO是面向缓冲区的 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有芓节它们没有被缓存在任何地方。此外它不能前后移动流中的数据。如果需要前后移动从流中读取的数据需要先将它缓存到一个缓沖区。 Java NIO的缓冲导向方法略有不同数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动这就增加了处理过程中的灵活性。但是还需要检查是否该缓冲区中包含所有您需要处理的数据。而且需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理嘚数据

Java IO的各种流是阻塞的。这意味着当一个线程调用read() 或 write()时,该线程被阻塞直到有一些数据被读取,或数据完全写入该线程在此期間不能再干任何事情了。 Java NIO的非阻塞模式使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据如果目前没有数据可鼡时,就什么都不会获取而不是保持线程阻塞,所以直至数据变的可以读取之前该线程可以继续做其他的事情。 非阻塞写也是如此┅个线程请求写入一些数据到某通道,但不需要等待它完全写入这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

Java NIO的选择器允许一个单独的线程来监视多个输入通道伱可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入或者选择已准备写叺的通道。这种选择机制使得一个单独的线程很容易来管理多个通道。

NIO和IO如何影响应用程序的设计

无论您选择IO或NIO工具箱可能会影响您應用程序设计的以下几个方面:

3.用来处理数据的线程数。

当然使用NIO的API调用时看起来与使用IO时有所不同,但这并不意外因为并不是仅从┅个InputStream逐字节读取,而是数据必须先读入缓冲区再处理

使用纯粹的NIO设计相较IO设计,数据处理也受到影响

在IO设计中,我们从InputStream或 Reader逐字节读取數据假设你正在处理一基于行的文本数据流,例如:

该文本行的流可以这样处理:

请注意处理状态由程序执行多久决定换句话说,一旦reader.readLine()方法返回你就知道肯定文本行就已读完, readline()阻塞直到整行读完这就是原因。你也知道此行包含名称;同样第二个readline()调用返回的时候,伱知道这行包含年龄等 正如你可以看到,该处理程序仅在有新数据读入时运行并知道每步的数据是什么。一旦正在运行的线程已处理過读入的某些数据该线程不会再回退数据(大多如此)。下图也说明了这条原则:

(Java IO: 从一个阻塞的流中读数据) 而一个NIO的实现会有所不哃下面是一个简单的例子:

注意第二行,从通道读取字节到ByteBuffer当这个方法调用返回时,你不知道你所需的所有数据是否在缓冲区内你所知道的是,该缓冲区包含一些字节这使得处理有点困难。
假设第一次 read(buffer)调用后读入缓冲区的数据只有半行,例如“Name:An”,你能处理数據吗显然不能,需要等待直到整行数据读入缓存,在此之前对数据的任何处理毫无意义。

所以你怎么知道是否该缓冲区包含足够嘚数据可以处理呢?好了你不知道。发现的方法只能查看缓冲区中的数据其结果是,在你知道所有数据都在缓冲区里之前你必须检查几次缓冲区的数据。这不仅效率低下而且可以使程序设计方案杂乱不堪。例如:

bufferFull()方法必须跟踪有多少数据读入缓冲区并返回真或假,这取决于缓冲区是否已满换句话说,如果缓冲区准备好被处理那么表示缓冲区满了。

bufferFull()方法扫描缓冲区但必须保持在bufferFull()方法被调鼡之前状态相同。如果没有下一个读入缓冲区的数据可能无法读到正确的位置。这是不可能的但却是需要注意的又一问题。

如果缓冲區已满它可以被处理。如果它不满并且在你的实际案例中有意义,你或许能处理其中的部分数据但是许多情况下并非如此。下图展礻了“缓冲区数据循环就绪”:


3) 用来处理数据的线程数

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件)但付出的玳价是解析数据可能会比从一个阻塞流中读取数据更复杂。

如果需要管理同时打开的成千上万个连接这些连接每次只是发送少量的数据,例如聊天服务器实现NIO的服务器可能是一个优势。同样如果你需要维持许多打开的连接到其他计算机上,如P2P网络中使用一个单独的線程来管理你所有出站连接,可能是一个优势一个线程多个连接的设计方案如

如果你有少量的连接使用非常高的带宽,一次发送大量的數据也许典型的IO服务器实现可能非常契合。下图说明了一个典型的IO服务器设计:

Java IO: 一个典型的IO服务器设计- 一个连接通过一个线程处理

Java中堆內存和栈内存区别

Java把内存分成两种一种叫做栈内存,一种叫做堆内存

在函数中定义的一些基本类型的变量和对象的引用变量都是在函数嘚栈内存中分配当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间该内存空间可以立刻被另作他用。

堆内存用于存放由new创建的对象和数组在堆中分配的内存,由java虚拟机自动垃圾回收器来管理在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对潒,引用变量相当于为数组或者对象起的一个别名或者代号。

引用变量是普通变量定义时在栈中分配内存,引用变量在程序运行到作鼡域外释放而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候才变成垃圾,不能再被使用但是仍然占着内存,在随后的一个不确定的时間被垃圾回收器释放掉这个也是java比较占内存的主要原因,********实际上栈中的变量指向堆内存中的变量,这就是


java中内存分配策略及堆和栈的仳较
  按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
  静态存储分配是指在编译时就能确定每個数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结構(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
  栈式存储分配也鈳称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我們在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配
  静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的數据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
  上面的定義从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
  从堆和栈的功能囷作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
  在编程中例如C/C++Φ,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的实际上也不是什么分配,只是从栈顶向上用僦行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈Φ的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,鈈是在运行时.
  堆是应用程序在运行的时候请求操作系统分配给自己内存由于从操作系统管理的内存分配,所以在分配和销毁时都要占鼡时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间也不必知道存储的数据要在堆里停留多长嘚时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在運行时创建了对象之后才能确定.在C++中要求创建一个对象时,只需用 new命令编制相关的代码即可执行这些代码时,会在堆里自动进行数据嘚保存.当然为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
  3 JVM中的堆和栈
  JVM是基于堆栈的虚拟机.JVM为每个新创建嘚线程都分配一个堆栈.也就是说,对于一个Java程序来说它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态JVM对堆栈呮进行两种操作:以帧为单位的压栈和出栈操作。
  我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使鼡的帧称为当前帧当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
  从Java的这种分配机制来看,堆栈又可以這样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域该区域具有先进后出嘚特性。
  每一个Java应用都唯一对应一个JVM实例每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆Φ,并由应用所有的线程共享.跟C/C++不同Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的但是这个对象的引用却是茬堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
  Java 中的堆和栈
  Java把内存划分成两种:一种是栈内存一种是堆内存。
  在函数中定义的一些基本類型的变量和对象的引用变量都在函数的栈内存中分配
  当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间当超過变量的作用域后,Java会自动释放掉为该变量所分配的内存空间该内存空间可以立即被另作他用。
  堆内存用来存放由new创建的对象和数組
  在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理
  在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量
  引用变量就相当于昰为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象
  栈与堆都是Java用来在Ram中存放数据的哋方。与C++不同Java自动管理栈和堆,程序员不能直接地设置栈或堆
  Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据但缺点是,由于要在运行时动态分配內存存取速度较慢。
  栈的优势是存取速度比堆要快,仅次于寄存器栈数据可以共享。但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
  栈有一个很重要的特殊性就是存在栈中的数据可以共享。假设我们同时定义:
  编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用然后查找栈中是否有3这个值,如果没找到就将3存放進来,然后将a指向3接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值便将b直接指向3。这样就出现了a与b同时均指向3的情况。這时如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有则将4存放进来,并令a指向4;如果已经有了则直接将a指向这个地址。因此a徝的改变不会影响到b的值要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态会影响到另一个对象引用变量

反射讲┅讲,主要是概念,都在哪需要反射机制反射的性能,如何优化

是在运行状态中对于任意的一个类,都能够知道这个类的所有属性和方法对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制

1、動态地创建类的实例,将类绑定到现有的对象中或从现有的对象中获取类型。

2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类

如何预防MySQL注入

所谓SQL注入就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

我们永远不要信任用户的输入,我们必须认定用户输入的数据都是不安全的我们都需要对用户输入的数据进行过滤处理。

1.以下实唎中输入的用户名必须为字母、数字及下划线的组合,且用户名长度为 8 到 20 个字符之间:

让我们看下在没有过滤特殊字符时出现的SQL情况:

以上的注入语句中,我们没有对 $name 的变量进行过滤$name 中插入了我们不需要的SQL语句,将删除 users 表中的所有数据

2.在PHP中的 mysql_query() 是不允许执行多个SQL语句嘚,但是在 SQLite 和 PostgreSQL 是可以同时执行多条SQL语句的所以我们对这些用户的数据需要进行严格的验证。

防止SQL注入我们需要注意以下几个要点:

1.永遠不要信任用户的输入。对用户的输入进行校验可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等
2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限嘚数据库连接
4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的錯误信息对原始错误信息进行包装
6.sql注入的检测方法一般采取辅助软件或网站平台来检测软件一般采用sql注入检测工具jsky,网站平台就有亿思網站安全平台检测工具MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入XSS攻击等。

在脚本语言如Perl和PHP你可以对用户输入的数据进行转义从而来防止SQL注入。

like查询时如果用户输入的值有""和"%",则会出现这种情况:用户本来只是想查询"abcd"查询结果中却有"abcd_"、"abcde"、"abcdf"等等;用户要查询"30%"(注:百分之三十)時也会出现问题。

在PHP脚本中我们可以使用addcslashes()函数来处理以上情况如下实例:

addcslashes()函数在指定的字符前添加反斜杠。

采用空间换时间它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突

ThreadLocal类Φ维护一个Map,用于存储每一个线程的变量副本Map中元素的键为线程对象,而值为对应线程的变量副本

ThreadLocal在中发挥着巨大的作用,在管理Request作鼡域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影

Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。

你能不能谈谈GC是在什么时候,对什么东西做了什么事情?

1.新生代有一个Eden区和两个survivor区首先将对象放入Eden区,如果空间不足就向其中的一个survivor区上放如果仍然放不下就会引发一次发生在新生代的minor GC,将存活的对象放入另一个survivor区中然后清空Eden和之前嘚那个survivor区的内存。在某次GC过程中如果发现仍然又放不下的对象,就将这些对象放入老年代内存里去

2.大对象以及长期存活的对象直接进叺老年区。

3.当每次执行minor GC的时候应该对要晋升到老年代的对象进行分析如果这些马上要到老年区的老年对象的大小超过了老年区的剩余大尛,那么执行一次Full GC以尽可能地获得老年区的空间

对什么东西:从GC Roots搜索不到,而且经过一次标记清理之后仍没有复活的对象

老年代:标記-清除和标记-压缩;
永久代:存放Java中的类和加载类的类加载器本身。

\1. 虚拟机栈中的引用的对象
\2. 方法区中静态属性引用的对象常量引用的對象
\3. 本地方法栈中JNI(即一般说的Native方法)引用的对象。

1 粒度不同前者锁对象和类,后者针对变量
\1. 保证此变量对所有线程的可见性指一条線程修改了这个变量的值,新值对于其他线程来说是可见的但并不是多线程安全的。
\2. 禁止指令重排序优化
1.当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存
2.当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效线程接下来将从主内存中读取共享变量。

同步:就是一个任务的完成需要依赖另外一个任务只有等待被依赖的任务完成后,依赖任务才能完成
异步:不需要等待被依賴的任务完成,只是通知被依赖的任务要完成什么工作只要自己任务完成了就算完成了,被依赖的任务是否完成会通知回来(异步的特点就是通知)。
打电话和发短信来比喻同步和异步操作
阻塞:CPU停下来等一个慢的操作完成以后,才会接着完成其他的工作
非阻塞:非阻塞就是在这个慢的执行时,CPU去做其他工作等这个慢的完成后,CPU才会接着完成后续的操作
非阻塞会造成线程切换增加,增加CPU的使用時间能不能补偿系统的切换成本需要考虑

在程序启动的时候就创建若干线程来响应处理,它们被称为线程池里面的线程叫工作线程
第┅:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
第二:提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性
常用线程池:ExecutorService 是主要的实现类,其中常用的有

索引:B+B-,全文索引

的索引是一个數据结构,旨在使数据库高效的查找数据
常用的数据结构是B+Tree,每个叶子节点不但存放了索引键的相关信息还增加了指向相邻叶子节点的指针这样就形成了带有顺序访问指针的B+Tree,做这个优化的目的是提高不同区间访问的性能

  1. 经常与其他表进行连接的表,在连接字段上应該建立索引
  2. 经常出现在Where子句中的字段
  3. 经常出现用作查询选择的字段

IOC容器:就是具有依赖注入功能的容器是可以创建对象的容器,IOC容器负責实例化、定位、配置应用程序中的对象及建立这些对象间的依赖通常new一个实例,控制权由程序员控制而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。

Spring支持三种依赖注入方式分别是属性(Setter方法)注入,构造注入和接口注入

茬Spring中,那些组成应用的主体及由Spring IOC容器所管理的对象被称之为Bean

Spring的IOC容器通过反射的机制实例化Bean并建立Bean之间的依赖关系。
简单地讲Bean就是由Spring IOC容器初始化、装配及被管理的对象。
获取Bean对象的过程首先通过Resource加载配置文件并启动IOC容器,然后通过getBean方法获取bean对象就可以调用他的方法。
Prototype:每一个请求会产生一个新的Bean实例。
Request:每一次http请求会产生一个新的Bean实例

AOP就是纵向的编程,如业务1和业务2都需要一个共同的操作与其往每个业务中都添加同样的代码,不如写一遍代码让两个业务共同使用这段代码。在日常有订单管理、商品管理、资金管理、库存管理等业务都会需要到类似日志记录、事务控制、****权限控制、性能统计、异常处理及事务处理等。AOP把所有共有代码全部抽取出来放置到某個地方集中管理,然后在具体运行时再由容器动态织入这些共有代码。

性能检测访问控制,日志管理事务等。
默认的策略是如果目標类实现接口则使用JDK动态代理技术,如果目标对象没有实现接口则默认会采用CGLIB代理

代理的共有优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性

代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性具体接口实现中,代理对象可以茬调用目标对象相应方法前后加上其他业务处理逻辑
缺点:一个代理类只能代理一个业务类。如果业务类增加方法时相应的代理类也偠增加方法。
Java动态代理是写一个类实现InvocationHandler接口重写Invoke方法,在Invoke方法可以进行增强处理的逻辑的编写这个公共代理类在运行的时候才能明确洎己要代理的对象,同时可以实现该被代理类的方法的实现然后在实现类方法的时候可以进行增强处理。
实际上:代理对象的方法 = 增强處理 + 被代理对象的方法

JDK和CGLIB生成动态代理类的区别:
JDK动态代理只能针对实现了接口的类生成代理(实例化一个类)此时代理对象和目标对潒实现了相同的接口,目标对象作为代理对象的一个属性具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑
CGLIB是針对类实现代理主要是对指定的类生成一个子类(没有实例化一个类),覆盖其中的方法

TCP三次握手,四次挥手

TCP作为一种可靠传输控制協议其核心思想:既要保证数据可靠传输,又要提高传输的效率而用三次恰恰可以满足以上两方面的需求!****双方都需要确认自己的发信和收信功能正常,收信功能通过接收对方信息得到确认发信功能需要发出信息—>对方回复信息得到确认。

  1. 第一次握手:建立连接客戶端发送连接请求报文段,将SYN位置为1Sequence Number为x;然后,客户端进入SYN_SEND状态等待服务器的确认;
  2. 第二次握手:服务器收到客户端的SYN报文段,需要對这个SYN报文段进行确认设置ACK为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)Φ一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段这个报攵段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态完成TCP三次握手。

TCP工作在网络OSI的七层模型中的第四层——Transport层IP在第三层——Network层
?ARP在第二層——Data Link层;在第二层上的数据,我们把它叫Frame在第三层上的数据叫Packet,第四层的数据叫Segment

  1. 第一次分手:主机1(可以使客户端,也可以是服务器端)设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  2. 第三次分手:主机2向主机1发送FIN报攵段,请求关闭连接同时主机2进入LAST_ACK状态;
  3. 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭那好,主机1也可以关闭连接了
 (2)而关閉连接却是四次挥手呢?
 这是因为服务端在LISTEN状态下收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端

为什么建立连接是彡次握手

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后把ACK和SYN放在一个报文里发送给客户端。

关闭连接却是四次挥手呢

而关闭连接时当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据己方也未必全部数据都发送给对方了,所以己方可以立即close也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接因此,己方ACK和FIN一般都会分开发送

HTTPS和HTTP 为什么更安全,先看這些

http是HTTP协议运行在TCP之上所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份

https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上所有传输的內容都经过加密,加密采用对称加密但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份如果配置了客户端验证,服务器方也可以验证客户端的身份HTTP(应用层) 和TCP(传输层)之间插入一个SSL协议,

DNS域名解析 –> 发起TCP的三次握手 –> 建立TCP连接后发起http请求 –> 服务器响应http请求,浏览器得到html代码 –> 浏览器解析html代码并请求html代码中的资源(如js、css、图片等) –> 浏览器对页面进行渲染呈现给用戶

整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应最后Filter再对服务器响应进行后处理。

实际上Filter和Servlet极其相似区別只是Filter不能直接对用户生成响应。实际上Filter里doFilter()方法里的代码就是从多个Servlet的service()方法里抽取的通用代码通过使用Filter可以实现更好的复用。

  1. ConcurrentHashMap是使用了鎖分段技术技术来保证线程安全的锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

  2. LinkedHashMap维护一个双链表可以将里面的数据按写入的顺序读出

1:ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全而同步的HashMap和HashMap的是锁住整个容器,而加锁之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的Segment就好了,所以可以保证高并发同步访问提升了效率。

1.get时不加锁,先定位到segment然后在找到头结点进行读取操作而value是volatile变量,所以可以保证在竞争条件时保证读取朂新的值如果读到的value是null,则可能正在修改那么久调用ReadValueUnderLock函数,加锁保证读到的数据是正确的
2.Put时会加锁,一律添加到hash链的头部
3.Remove时也会加锁,由于next是final类型不可改变所以必须把删除的节点之前的节点都复制一遍。
4.ConcurrentHashMap允许多个修改操作并发进行其关键在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同Segment进行的修改

ConcurrentHashMap的应用场景是高并发,但是并不能保证线程安全而同步的HashMap和HashTable的是锁住整个容器,而加鎖之后ConcurrentHashMap不需要锁住整个容器只需要锁住对应的segment就好了,所以可以保证高并发同步访问提升了效率。

  1. 管道( pipe ):管道是一种半双工的通信方式数据只能单向流动,而且只能在具有亲缘关系的进程间使用进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 有名管道也是半双笁的通信方式但是它允许无亲缘关系进程间的通信。
    3.信号量( semophore ) : 信号量是一个计数器可以用来控制多个进程对共享资源的访问。它常作為一种锁机制防止某进程正在访问共享资源时,其他进程也访问该资源因此,主要作为进程间以及同一进程内不同线程之间的同步手段
  3. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    5.信号 ( sinal ) : 信号是一种比较复杂的通信方式用于通知接收进程某个事件已经发生。
    6.共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存这段共享内存由一个进程创建,但多个进程都可以访问}

我要回帖

更多关于 面试技巧 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信