Java面试指南

 

 

    

    

 

    

 

    

    

    

    

 

 

 

 

前言

 

    2015年可以说是Java程序员的“大年”,随着2014年11月乌镇互联网的闭幕,互联网行业是真的迎来了春天。2015年大量的创业型公司的兴起,对Java中高级职位的需求几乎可以用抢人的程度,跳槽人员的薪资普通在30%-50%的涨幅(一方面是因为市场需求、另一方面企业也在努力打破好多年员工待遇倒挂现象,即新入职员工的工资总比老员工高),更高的也不罕见,这是近几年来不曾出现过的。

    同时2015年也是Java 20岁的生日,Java一路走来,打破了多次“将死”的预言,现在依旧如日中天,在未来的很长一段时间内,将依然领跑于市场,

对于开发者的需求也依然居于其它语言之首。

     本文档是笔者和朋友曾经的面试经历,出于对市场和对技术发展方向的了解,近几年来,每年笔者都会参加一些公司的面试,2015年笔者以及朋友换工作时,更是前前后后面试20家工公司有余(多数是公司反馈信息太慢,有的是待遇不合理)斩获了不少OFFER。笔者在动笔之前曾经把浙江图书馆的Java面试相关的书都借阅了一遍,汲取精华。

本文档的内容主要定位Java技术相关,这块也是Java面试中最重要的环节,所涉猎不足部分也会在以后慢慢的加入。文档的内容主要适合0-4年工作经验的求职者,对于五年以上工作经验的朋友,基础已经扎实,甚至很多公司面试都不涉及技术点了,只是简单聊聊工作中的一个些项目开发经验就下OFFER了;所以本文档的帮助也不是很大了。

    下面对于本文档作一下简单说明:

    1、本文档旨在帮助求职者快速复习Java面试的知识点,而不是学习参考书,所以涉及的内容宽泛但不深入。

    2、本文档适合读者:一是突然间决定换工作,没有太多时间准备系统复习;二是准备换工作想了解一下市场技术方向;三是毕业不久的求职者对技术面试环节不太了解;四是感觉网上复习题信息量太大,没有层次,针对性不强。

    3、笔者一直想写一本Java相关的书,本文档的知识会穿插在未来的书中,所以文档中的内容不会全部免费公开(书写好后,文档会开源),但此文档中部分知识点是引用网上优秀的博客、论坛中内容,对于此部分内容是公开的。

4、本文档重点针对Java技术后端相关的面试部分,对于UI前端技术、数据库、数据结构、智力考察等部分在以后的版本中会陆续加入。

5、有些参考答案可能写的很长很多,主要是出于求职者能够很好了解的角度考虑,实际面试时可以根据自己的理解回答。

    6、对于文档中如果存在错误、不当之处欢迎指出,如果有好的建议也可以联系我们dyc_gary@126.com,我们将努力改正,不断完善,谢谢支持。

 

 

收获 offer,挑战高薪,全本请访问:http://shop113074087.taobao.com

 

 

 

目录

第一部分 应聘求职... 7

第01章 简历篇... 7

第02章 应聘流程... 8

第03章 福利如何谈... 9

第二部分 CoreJava基础... 10

第04章 解读API 10

4.1 Object常用的方法... 10

4.2 String与StringBuffer区别... 10

4.3  Vector,ArrayList, LinkedList的区别... 10

4.4  HashTable, HashMap, ConcurrentHashMap区别... 11

4.5  equals()和hashCode()作用... 12

4.6  sleep()和wait()区别... 13

4.7  IO与NIO的区别... 13

4.8  Synchronized和Lock区别和用法... 14

4.9  Final、Finally、Finalize的区别... 15

4.10  OverLoad与Override的区别... 15

4.11  Collection与Collections的区别... 16

第05章 Java基础知识... 16

5.1 “==”与“equals”区别... 16

5.2 接口和抽象类的区别... 17

5.3 运行时异常和一般异常的区别... 18

5.4 序列化和反序列化... 18

5.5  Java实现浅克隆与深克隆... 19

5.6 枚举可以序列化吗... 20

5.7 Java创建对象的方式... 20

5.8 能否自己定义一个java.lang.String类... 21

5.9 Java线程池的了解... 21

5.10  Return和finally语句块的顺序... 22

5.11  Java静态变量与实例变量的区别... 22

5.12  同步与异步区别及使用场景... 23

5.13  Java堆和栈的区别... 23

5.14  Java多线程快速问答... 23

第06章 JVM相关... 25

6.1 JVM的工作原理... 25

6.2 类的加载机制... 26

6.3 GC的工作原理... 27

6.4 Java的反射机制... 28

6.5 JDK性能分析工具及应用... 28

第三部分 J2EE框架相关... 错误!未定义书签。

第07章 Servlet&JSP. 错误!未定义书签。

7.1 Servlet的生命周期... 错误!未定义书签。

7.2 Servlet3.0有哪些新特性... 错误!未定义书签。

7.3 Servlet监听器的作用... 错误!未定义书签。

7.4 forward与redirect区别... 错误!未定义书签。

7.5 Session与Cookie的区别... 错误!未定义书签。

7.6  如何实现一个自己的session. 错误!未定义书签。

7.7 Http请求中Get和Post区别... 错误!未定义书签。

7.8  JSP中动态INCLUDE与静态INCLUDE的区别... 错误!未定义书签。

7.9  Servlet是否线程安全并解释原因... 错误!未定义书签。

第08章 Struts1.x框架... 错误!未定义书签。

8.1 Struts1的MVC实现... 错误!未定义书签。

8.2 Struts1的工作原理... 错误!未定义书签。

8.3 为什么要用Struts1. 错误!未定义书签。

8.4 Struts1的优缺点... 错误!未定义书签。

8.5 Struts1常用的标签库... 错误!未定义书签。

第09章 Struts2.x框架... 错误!未定义书签。

9.1  Struts2的工作原理... 错误!未定义书签。

9.2  Struts2有哪些优点... 错误!未定义书签。

9.3  为什么要用Struts2. 错误!未定义书签。

9.4  struts2 如何获取request(session) 错误!未定义书签。

9.5  Struts2中拦截器与过滤器的区别... 错误!未定义书签。

9.6  什么是ValueStack和OGNL. 错误!未定义书签。

第10章 Hibernate框架... 错误!未定义书签。

10.1  Hibernate工作原理... 错误!未定义书签。

10.2  简述Hibernate中对象的状态... 错误!未定义书签。

10.3  Load()与Get()的区别... 错误!未定义书签。

10.4  Hibernate缓存机制... 错误!未定义书签。

10.5  Hibernate悲观锁与乐观锁的区别... 错误!未定义书签。

10.6  update()和saveOrUpdate()的区别... 错误!未定义书签。

10.7  Hibernate的优缺点... 错误!未定义书签。

第11章 Mybatis框架... 错误!未定义书签。

11.1  resultType和resultMap的区别... 错误!未定义书签。

11.2  Mybatis中“#”与“$”区别... 错误!未定义书签。

第12章 Spring框架... 错误!未定义书签。

12.1  为什么要用Spring. 错误!未定义书签。

12.2  简述一下Spring结构... 错误!未定义书签。

12.3  什么是IOC(DI)... 错误!未定义书签。

12.4  AOP的实现原理... 错误!未定义书签。

12.5  Spring的生命周期... 错误!未定义书签。

12.6  BeanFactory和ApplicationContext原理及作用... 错误!未定义书签。

12.7  Spring实例化Bean的方式... 错误!未定义书签。

12.8  Spring各种事务实现及区别... 错误!未定义书签。

12.9  Spring事务属性... 错误!未定义书签。

12.10  Spring编程事务与声明事务的区别... 错误!未定义书签。

12.11  SpringMVC的工作原理... 错误!未定义书签。

第13章 WebService技术... 错误!未定义书签。

13.1  什么是WebService. 错误!未定义书签。

13.2  WebService常用实现引擎并说明... 错误!未定义书签。

13.3  WebService工作原理... 错误!未定义书签。

13.4  可以谈一下什么是SOA吗... 错误!未定义书签。

第14章 综合部分... 错误!未定义书签。

14.1  常用哪些设计模式及使用场景... 错误!未定义书签。

14.2  XML常用的解析方式... 错误!未定义书签。

14.3  Apache、Tomcat、JBoss、WebLogic的区别... 错误!未定义书签。

14.4  动态代理的原理及实现... 错误!未定义书签。

14.5  Struts与Spring的区别... 错误!未定义书签。

14.6  Mybatis与Hibernate的区别... 错误!未定义书签。

14.7  Struts1与Struts2的区别... 错误!未定义书签。

14.8  对NoSQL数据库有什么理解... 错误!未定义书签。

14.9  Memcached的工作原理... 错误!未定义书签。

14.10  数据库连接池的机制... 错误!未定义书签。

14.11  Maven的工作原理... 错误!未定义书签。

14.12  谈一下对web.xml的理解... 错误!未定义书签。

14.13  Encache,Memcached的区别... 错误!未定义书签。

14.14  能谈一下linux常用的命令吗... 错误!未定义书签。

14.15  如何设计高并发下的秒杀功能... 错误!未定义书签。

14.16  谈一下高并发框架的设计... 错误!未定义书签。

第四部分 数据库技术... 错误!未定义书签。

第15章 JDBC操作部分... 错误!未定义书签。

15.1  Statement与PreparedStatement的区别... 错误!未定义书签。

15.2  JDBC连接数据库的步骤... 错误!未定义书签。

第16章 MYSQL部分... 错误!未定义书签。

16.1  Mysql的常用连接方式... 错误!未定义书签。

16.2  Mysql乱码如何解决... 错误!未定义书签。

16.3  Mysql如何获取系统时间... 错误!未定义书签。

第17章 ORACLE部分... 错误!未定义书签。

17.1  oracle中 rownum与rowid的理解... 错误!未定义书签。

17.2  union和union all有什么不同... 错误!未定义书签。

17.3  truncate和delete的区别... 错误!未定义书签。

17.4  谈谈你对SQL优化的看法... 错误!未定义书签。

17.5  存储过程与函数的区别... 错误!未定义书签。

第五部分 笔试与上机... 错误!未定义书签。

第18章 程序阅读... 错误!未定义书签。

18.1          读代码写执行结果... 错误!未定义书签。

18.2          String经典系列... 错误!未定义书签。

18.3          分析程序是否异常... 错误!未定义书签。

第19章  上机编程... 错误!未定义书签。

19.1          写出一个单例模式... 错误!未定义书签。

19.2          按字节截取的字符串... 错误!未定义书签。

19.3          多线程编程题... 错误!未定义书签。

19.4          写一个冒泡算法... 错误!未定义书签。

19.5          向int数组随机插入100个数... 错误!未定义书签。

第六部分 离职流程... 错误!未定义书签。

20.1          离职信... 错误!未定义书签。

20.2          离职流程... 错误!未定义书签。

第七部分 附录:简历模板... 错误!未定义书签。

第八部分 附录:求职信... 错误!未定义书签。

第九部分 附录:加薪申请... 错误!未定义书签。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第一部分 应聘求职

    什么时候适合找工作?在网上搜索的话,会有很多人回复说九月是毕业生进入社会的高峰期,金九银十也是换工作的的黄金时期。貌似现在什么东西都能和金九银十扯上关系,房产、旅游、找工作等等;我感觉毕业生如果等到金九银十再去找工作,估计很难找到合适的了。现在的大学生貌似六七月毕业,而很多学校最后半学期就是让学生做毕业设计和找工作的时间,春节到校招生的企业也是最多的。并不能否定九月十月不适合找工作,但建议找工作要趁早,如果你准备好了,什么时候都可以找工作,而且越早越好。

对于社会有工作经验的朋友来说,好多人可能认为年初最适合找工作,因为拿了年终,而且好多企业会在年初提供很多职位;但我感觉年初不适合找工作,而适合入职,最好在年底把新的工作搞定。

去年年末对于51JOB,智联,猎聘等专业的招聘网站提供的职位进行了分析,年底的职位可能没有年初多,但从待遇福利和公司规模来看,都很不错。而且从周围的朋友来看,过一个春节,回家放松得很彻底,回来两三周才找到感觉,这个时候去面试,成功率低很多。当然上面多是个人建议,工作不爽了,随时可以找;尤其是IT行业,在一家公司呆的越久,与新技术脱离的越远,找一个满意的工作对自己和对公司来说,都是有利的。

第01章 简历篇

       如果你是一个刚毕业的大学生,建议可以到51job或其它网络上了解一下简历的注意事项,上面讲的很细致,这里就懒得把那么多细节拉下来占用篇幅,请大家一定要慎重对待简历,好的简历就是一次面试机会,如果你的简历没有写好,那么接下来的内容看了也无用武之地了,这里简单介绍一下简历的注意事项,在附录中,我会放上一个简历模板供大家参考使用。

1、基本信息简单明确

       像姓名、性别、学校专业、学历、电话、电子邮箱、应聘职位这些是必须的,绝对不能被简洁的,至于出生年月,政治面貌之类的可以选择;薪资和地址之类的个人建议没有必要写,如果写上薪资要求的话,一定要加上左右两字或者给出一个范围,给自己留一些谈判空间。

 

2、信息要真实

  诚信是最基本的沟通前提,无论对于用人企业还是以后与同事相处,如果简历中没有太多引人注意的地方,可以通过自荐信之类的弥补(后面附录中会提到);不要想通过制造虚假的简历信息来获得职位,IT这个行业求职时要提供毕业证,学位证,有经验的还要提供离职证明,很多公司会对你的简历信息进行验证(阿里就有求职者的档案库,对于不良求职者可能会进入黑名单,永远没有录用的机会),所以信息尽可能的真实,如果刚毕业的学生感觉自己的简历太空,就把实习工作和毕业设计都写进去充实一下。如果简历中的一些硬件信息达不到用人单位的要求,可以变通一下,把这些信息从简历中删除,毕竟IT行业更重实力,通过OFFER后再和HR解释清楚就行了。

 

3、谦虚适度、切忌张扬

       刚毕业的求职者,简历上往往会看到很多精通CoreJava,精通SSH……总之简历上很多精通,精通这个词就是属于很张扬的词,可以说实际面试中十个难有一个求职者达到技术要求的精通级别。自信固然不可少,但切忌太张扬,容易让面试人员产生不好的感觉,感觉你有点华而不实,所以除了精通外还可以用一些熟练,这是一个很狡猾的词。虽然这高调的张扬不提昌,但过分谦虚也不行的,比如一个简历上写满了“会一点”、“了解”、“学过”之类的,企业单位虽然喜欢诚实,但付给你钱是让你干活的,这样的简历连HR一看就知道干不了事,可能都到不了面试官手里。

  另外简历上的东西要和你的实际经验符合,别为了显示你的知识面广,经验丰富,乱七八糟的东西全写了,要知道你简历上的东西都是面试官可能会问到你的问题,写简历前要清楚写的每一项技术能不能禁得起面试官的提问,好多求职者都是简历投出后,上面的内容自己都不太清楚了,这点是很忌讳的。

 

4、项目经验不易过长

       简历项目经验不易太多,一般四到六个为宜,写上你认为最有代表性的,然后描述详细一点就OK了;不要把那些参与三两行代码的项目都写上,面试官如果较真起来,可能认为你对项目了解不深入,留下负面印象;如果项目经验实在太多了,也可以针对职位来修改简历中的项目经验,这样命中率更高。其实优秀的员工一般都有自己钟情的公司,希望大家以后也都有这样的公司。

 

5、求职信

       这个主要针对刚毕业的求职者,实际工作经验可能还没有,简历写出来有时候会很空白,在海量的简历中很容易被埋没;写一封求职信将有助于你获取更大的面试机会。求职信的目的是让对方了解自己、相信自己、录用自己,它是一种私人对公并有求于公的信函。但格式有一定的要求,内容要求简练、明确,切忌模糊、笼统、面面俱到。

 

第02章 应聘流程

1、应聘流程图

 

2、面试前的工作

    I、写简历,除了要有一份电子档的简历外,还要在各大主要的招聘网上写一下简历,如51job,智联,猎聘,100%OFFER,CSDN招聘专栏等。对于刚毕业的学生,主要推荐前两个,职位多;对于有一定工作经验,重点推荐后三种,含金量高,待遇一般都不错。

   II、 投简历约面试,写简历的目的就是投给用人单位以获得面试机会;切忌海投,盲目投,甚至乱投,可能会被HR打入黑名单;投简历一定要有针对性,一定要看清公司招聘的职位信息。约面试时,要问清时间,如果是当天要求面试的,最好协商一下是否可以晚一天,以便有时间了解一些公司信息和确认一些职位信息,这个很有必要;面试时出于礼貌最好早到十来分钟,如果因为意外可能迟到,最好给HR电话说明一下。

   III、面试前一天根据招聘的职位信息,看一下此"Java面试指南",如果没有包含的网上查一下,做到有备无患,知已知彼。

第03章 福利如何谈

1、薪资多少合适

    首先薪水福利是不能比的,同一年同一班出来的同学,工作几年后薪资差别是相当大的,不同的城市和地区也差别较大。对于有工作经验的朋友跳槽的话,可以参考自己的同学,和自己工作经验工作强度相似的朋友(不要盲目比较,毕竟技术能力,行业背景不同,而且工作强度、工作压力也不同),一般跳槽薪资比上家会浮动30%左右;如果实在没法参考,可以参考网络上传的一个比较牛的计算公式:3k+工作年限*2。对于刚毕业的求职者来说,经验比薪资更重要,但作为IT的同行建议坚决不能干那种白出力的活,一般实习本科大概4k+,专科一般少0.5K。

 

2、福利如何谈

    面试成功后,和HR谈福利待遇时,千万不要只谈工资不谈福利,尤其是刚毕业的求职者没什么经验。谈薪资福利可以从以下几方面入手:第一,谈薪资的时候要问清是税前还是税后,是试用期工资还是转正后的工资,试用期是付全额还是付全额的80%.第二,公司有没有年终奖,发多少,什么时候发?第三,公司福利怎么缴,是五险一金还是三险一金,缴费基数是多少?记住千万别忘记问一金(公积金,对以后买房可能很有帮助),然后再问清试用期给不给缴。第四,公司加班制度,有没有加班补助,好多人受不了加班太多,所以入职时最好问清楚。第五,像其它的旅游了,体检了,餐补了,高温补贴,过节礼品等其它的一些东西可以都问清楚。

 

 

 

 

 

 

 

 

第二部分 CoreJava基础

第04章 解读API

4.1 Object常用的方法

       解析:面试刚开始都很简单,但也容易给面试官留下印象,就像这个题,如果你有三年以上经验,回答的少于六个,那么估计你下面将很危险了。Object中的这些方法都很精典,务必记住。

 

  参考答案:object常用的方法有clone(),equals(),hashCode(),notify(),notifyAll(),

toString(),wait(),finalize()。

                                               

4.2 String与StringBuffer区别

    解析:这是一个很基础的题,String字符串的问题几乎很多面试官都喜欢问,因为在实际项目开发中用到的最多的变量就是String,所以读者要重点对待,熟练掌握。

 

   参考答案:String与StringBuffer在java中都是用来进行字符串操作的。

String是一个很别的类,它被final修饰,意味着String类不能被继承使用,一旦声明是一个不可变的类。

StringBuffer是一个长度可变的,通过追加的方式扩充,并且是线程安全;尤其在迭代循环中对字符串的操作,性能一般优于String。但线程安全意味着开销加大,性能下降,所以在线程安全的情况下一般用StringBuilder来代替StringBuffer使用,StringBuilder的速度要优于StringBuffer。                                                        

拓展:javaAPI中哪些类是final的?String的"+"号操作性能是不是一定慢?

4.3  Vector,ArrayList, LinkedList的区别

       解析:这里主要考察集合类的熟练程度,考察深度可深可浅;浅的主要是考察使用,深点可能考察底层的数据结构,算法实现等,感兴趣的朋友可以深究一下。

 

   参考答案:

  1、Vector、ArrayList都是以类似数组的形式存储在内存中(Vector不能存null,但ArrayList可以),LinkedList则以链表的形式进行存储。

  2、Vector线程同步,ArrayList、LinkedList线程不同步;后两者一般用在线程安全的地方,也可以通过Collections.synchronizedList(……);实现线程同步。

  3、LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。

  4、ArrayList在元素填满容器时会自动扩充容器大小的50%,而Vector则是100%,因此ArrayList更节省空间。

  5、LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等,多与一些线程池一起使用.

  补充:以上都实现了序列化接口,这点在对象远程传输时很重要;而Vector,ArrayList实现了AccessRandom接口,这是一个标记接口,此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。

  注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

拓展:线性列表和双链表的数据结构实现?

4.4  HashTable, HashMap, ConcurrentHashMap区别

       解析:这里还有一个TreeMap没有列出,TreeMap能够把它保存的记录根据Key键排序,所以Key值不能为空,默认是按升序排序,也可以写自己的比较器(TreeMap排序是根据红黑树实现的,有兴趣的可以研究一下它的算法实现)。而ConcurrentHashMap是从jdk1.5新增的,是HashMap的一种线程安全的实现类,经常在多线程的环境中使用,由于底层的实现使用的是局部锁技术,在线程安全的前提下比HashTable的性能要好,推荐使用(ConcurrentHashMap局部锁技术实际上就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中,而这里的每个segment都相当于一个小的HashTable,有兴趣的朋友可以读一下源代码),此题经常在中高级职位面试中问到,所以请慎重对待。

  参考答案:

  1、HashTable线程同步,key,value值均不允许为空;HashMap非线程同步,key,value均可为空,HashTable因为线程安全的额外开销会造成性能下降,而HashMap由于线程不安全,在多线程的情况下,一般要加锁,或者使用Collections.synchronizedMap()来创建线程安全的对象。

   2、HashTable继承的父类是Dictionary,而HashMap继承的父类是AbstractMap

,而且又都实现了Map接口,所以Map接口中提供的方法,他们都可以使用;HashTable的遍历是通过Enumeration,而HashMap通常使用Iterator实现。

  3、HashTable有一个contains(Object value)的方法,其功能和HashMap的containsValue(Object value)一样,而HashMap提供的更加细致还有一个取Key值的方法为containsKey(Object key)。

  4、哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的:         

  int hash = key.hashCode();

  int index = (hash & 0x7FFFFFFF) % tab.length;

而HashMap重新计算hash值,而且用与代替求模:

 int hash = hash(k);   int i = indexFor(hash, table.length);

static int hash(Object x) {  int h = x.hashCode();

  h += ~(h << 9); 

  h ^= (h >>> 14);  

  h += (h << 4);

  h ^= (h >>> 10);  

  return h;}

static int indexFor(int h, int length) {return h & (length-1);} //网络提供,面试时不追问,可以不说出具体的实现代码。

  5、ConcurrentHashMap是HashMap线程安全的实现,并且逐渐取代Hashtable的使用,因为Hashtable锁的机制是对整个对象加锁,而ConcurrentHashMap使用的是局部锁技术,实际上就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中,而这里的每个segment都相当于一个小的Hashtable,性能将高于HashTable。

 

4.5  equals()和hashCode()作用

       解析:这个问题每个面试求职者或许都被问到过,属于常见题型,但也是比较难回答的面试题之一;包括很多面试官本身也有时候并不能解释得很清楚,如果要回答好这个小问题,首先要了解这两个方法的实现原理(在第I版中不会太深入讨论)。在《Effective java》中有这样一句话:在每个覆盖了equals方法的类中,也必须覆盖hashCode方法,如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常动作,这样的集合包括HashMap,Hashtable,HashSet。这里列出一下equals重写的规则:

1 自反性:对任意引用值X,x.equals(x)的返回值一定为true.

2 对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;

3 传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true

4 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变

5 非空性:任何非空的引用值X,x.equals(null)的返回值一定为false。

 

   参考答案:这两个方法都是Object类的方法,equals通过用来判断两个对象是否相等。HashCode就是一个散列码,用来在散列存储结构中确定对象的存储地址。HashMap,HashSet,他们在将对象存储时,需要确定它们的地址,而HashCode就是做这个用的。在默认情况下,由Object类定义的hashCode方法会针对不同的对象返回不同的整数,这一般是通过将该对象的内部地址转换成一个整数来实现的。

在java中equals和hashCode之间有一种契约关系:

   1. 如果两个对象相等的话,它们的hashcode必须相等。

   2. 但如果两个对象的hashcode相等的话,这两个对象不一定相等。

    由于java.lang.Object的规范,如果两个对象根据equals()方法是相等的,那么这两个对象中的每一个对象调用hashCode方法都必须生成相同的整数结果。(下面作为解释内容可以不必回答,理解使用)

举例说,在HashSet中,通过被存入对象的hashCode()来确定对象在HashSet中的存储地址,通过equals()来确定存入的对象是否重复,hashCode()和equals()都需要重新定义,因为hashCode()默认是由对象在内存中的存储地址计算返回一个整数得到,而equals()默认是比较对象的引用,如果不同时重写他们的话,那么同一个类产生的两个完全相同的对象就都可以存入Set,因为他们是通过equals()来确定的,这就是HashSet失去了意义。面试时简单的说,HashSet的存储是先比较HashCode,如果HashCode相同再比较equals,这样做的目的是提交储存效率。所以换句方式问,如果用对象来做为Key值的话,要满足什么条件呢?答要重写equals和HashCode方法。

 

4.6  sleep()和wait()区别

        解析:多线程可以说是java面试中的重点难点之一,也是最能筛选面试者的分水领,这个只是属于最基本的考点,尽可能的回答周全,面试的朋友可以对多线程这块多花点时间。

   参考答案:

  1、wait()是Object类的方法,在每个类中都可以被调用;而sleep()是线程类

Thread中的一个静态方法,无论New成多少对象,它都属于调用的类的。

  2、sleep方法在同步对象中调用时,会持有对象锁,其它线程必须等待其执行结束,如果时间不到只能调用interrupt()强行打断;在sleep时间结束后重新参与cpu时间抢夺,不一定会立刻被执行。

   3、wait()方法在同步中调用时,会让出对象锁。通常与notify,notifyAll一起使用。

 

4.7  IO与NIO的区别

    解析:这是现在面试中常被问到的一个问题,尤其做后台开发的职位;NIO是从jdk1.4就引入了,但是如果没有实际开发中使用过,不是很容易说的很清楚,这里从概念的层面介绍一下,希望有兴趣的朋友能更深层次的研究一下。

   参考答案:IO是早期的输入输出流,而NIO全名是New IO在IO的基础上新增了许多新的特性。提供的新特性包括:非阻塞I/O,字符转换,缓冲以及通道。

  1、IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。

  2、Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。虽然是非阻塞的,但也会遇到一个问题就是服务器对最大连接的支持,但在线用户连接数大于系统支持数时,NIO的默认实现是并不管是否还有足够的可用连接数,而是直接打开连接。在netty框架中经常会看到一个open too many files异常就是由此引起的,所以要灵活使用,合适配置。

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

 扩展:Netty是NIO实现的一个经典框架,好多职位中都要求熟悉netty,大家有时间可以研究一下。

4.8  Synchronized和Lock区别和用法

       解析:这里相对于上面的wait与sleep的区别来说,难度明显增加,也是常见的面试题。

   参考答案:

   首先他们都是用来实现线程的同步操作的,synchronized是jdk1.2以来一直存在,而且Lock是jdk1.5的新增;具体区别如下:

  1、Lock是JDK1.5才出现的,而在jdk1.5及之前的synchronized存在很大的性能问题,尤其在资源竞争激烈的条件下,性能下降十多倍,而此时的Lock还基本能维持常态。(synchronized在jdk1.5后的版本,作了很大的性能优化)

 2、使用synchronized时对象一定会被阻塞,其它线程必须等待锁释放后才能执行,在高并发的情况下,很容易降低性能,而且控制不当还容易造成死锁,所以要合适的利用Synchronized,并且尽可能的精确到最小的业务逻辑块。而Lock提供了更细致的操作,其常用的实现类有读写锁,这里以ReentrantLock 为例,它拥有Synchronized相同的并发性和内存语义,此外还多了锁投票,定时锁等候和中断锁等候等很多详细的方法操作。 ReentrantLock获取锁定与三种方式:a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。

 3、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。

 补充:

A.无论synchronized关键字加在方法上还是对象上,他取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 B.每个对象只有一个锁(lock)和之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 synchronized可以加在方法上,也可以加在对象上,通常理解为,只有持有了锁才可以进行对应代码块的执行。

java.util.concurrent.locks包下面提供了一些锁的实现,有读写锁,公平锁等。

将synchronized替换成lock的实现可以提升性能:

1. 大部分应用场景是读写互斥,写和写互斥,读和读不互斥。而synchronized则是都互斥。

可以利用读写锁来优化性能,读锁锁住读的代码块,写锁锁住写的代码块。

2. 要确保你在理解原来利用到synchronized的代码逻辑,避免一概而论地把synchronized替换成锁。

PS:被synchronized修饰的方法所在的对象,其它线程可以访问该对象的非synchronized修饰的方法,不能访问synchronized的任何方法。

  说明:里面有一部分看网上总结的比较好,就直接拿过来使用了,当然面试时也不是说必须要把上面的点回答完,最重要要通过自己的理解表达出来。

 

4.9  Final、Finally、Finalize的区别

       解析:这是一个很老很老就存在的面试题,但还是经常被问到,越简单的题在面式中越不允许答错。

  参考答案:

  Final是Java中一个特别的关键字(修饰符),final修饰的类不能被继承,final修饰的变量不能被修改(是常量),final修饰的方法不能被重写,所以使用final要考虑清楚使用场景。

  Finally是Java异常处理try{}catch(..){}finally{}中的一部分,finally语句块中的语句一定会被执行到(Down机除外),通常用于进行资源的关闭操作,如数据连接关闭等。

  Finalize是Object类中的一个方法,finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的,设计的初终是可以进行一些资源的关闭,但这个方法很少被重写,因为垃圾回收器执行时并不一定会调用,比如抛异常时,而且垃圾回收本身就是随意的,不可控制的。

 

4.10  OverLoad与Override的区别

       解析:这里其实是考的是Java面向对象的三(四,如果算上抽象就是四大特征)大特征,封装、继承、多态、(抽象)中的多态,属于基础类型常考题之一。

 参考答案:overload和override是多态的两种表现形式;

 overload重载规则是同名不同参,方法名相同,参数类型、个数、顺序至少有一个不相同(与返回值、参数名、抛出的异常都没有关系);

 overload可以发生在父类、同类、子类中。override重写的规则是方法名、参数、返回值相同,发生在父子类之间,而且子类不能抛出比父类更多的异常,被重写的方法一定不能定义成final。

  拓展:static方法能不能被overload,override?

 

4.11  Collection与Collections的区别

  参考答案:Collection是一个集合的超级接口,它定义了一些集合常用的操作方法,如add,size,remove,clear等,我们经常用到的List(可重复集合),Set(不可重复集合)接口就是它的子接口。

 Collections此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容。 如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException。常用到的方法如copy,sort,reverse,replaceall等。

 

 

第05章 Java基础知识

5.1 “==”与“equals”区别

       解析:这是一个很简单的题,很多人都能答上来“==”通常用来比较地址,而且equals则是比较内容;如果仅仅这样回答,通常面试只能得到60分,可以通过下面的两个例子来看一下。

String s1 = "s";

String s2 ="s" ;

String s3 = new String("s");

String s4 = new String("s");

StringBuffer sb1 = new StringBuffer("s");

StringBuffer sb2 = new StringBuffer("s");

System.out.println(s1==s2) ;  

System.out.println(s3==s4) ;

System.out.println(sb1==sb2) ;

System.out.println(s1.equals(s2)) ;

System.out.println(s3.equals(s4))

System.out.println(sb1.equals(sb2)) ;

       读者可以自己判断一下结果;对于s1,s2会指向同一个地址,因为这里的s1会保存在常量池中,s2会先读取常量池,发现里面已经存在”s”了,不会再进行重新开辟空间。对于s3,s4是在堆中分配两块空间,它们的地址是存放在栈中。sb1与sb2的内在分配和s3,s4一样,但注意StringBuffer没有重写equal方法,而调用的是Object中的equal方法,看一下源码可以知道Object中的equal的实现return (this == obj)其实就是在比较对象的地址。

 

  参考答案:“==”对于基本数据类型,通常用来比较值(基本数据类型是在栈中进行分配的)相等,对于对象通常用来比较对象的地址。equals是Object中的方法,可以根据业务需要进行重写,比如String中重写了equals的方法,常用来比较两个字符串内容是否相等。

  说明:重写equal的风险也很大的,上面也提到过重写的规则,所以根据需要进行重写。

 

5.2 接口和抽象类的区别

   解析:这是一个很简单的题,面试时一定要想清楚再回答,这样的题答错,估计机会也就失去了。

   参考答案:

   含有abstract修饰符的class 即为抽象类,abstract类不能创建实例对象,含有abstract的方法的类必须定义为abstract class ,abstract class 里的方法不必是抽象的,抽象类中定义抽象方法必须放在具体子类中实现,所以,不能有抽象的构造方法或抽象的静态方法,如果子类没有实现抽象父类中的所有方法,那么,子类也必须定义为抽象类。

   接口(interface)可以说成是抽象类的特例。接口中的所有方法都必须是抽象的,接口中的方法定义默认为public abstract 。接口中的变量是全局常量,即public static final修饰的。概括如下:

 1、抽象类里可以有构造方法,而接口内不能有构造方法。

 2、抽象类中可以有普通成员变量,而接口中不能有普通成员变量。

 3、抽象类中可以包含非抽象的普通方法,而接口中所有的方法必须是抽象的,不能有非抽象的普通方法。

 4、抽象类中的抽象方法的访问类型可以是public ,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

 5、抽象类中可以包含静态方法,接口内不能包含静态方法。

 6、抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static类型,并且默认为public static类型。

 7、一个类可以实现多个接口,但只能继承一个抽象类。

 以上答案网上都是可以找的到的,也很有条理,如果能回答出五条左右就可以满分了。除此外,下面就是加分项了,在设计上接口的作用侧重于模块划分,比如:用户管理接口,信息管理接口,里面都相关用户的增删改查操作。而抽象类则是侧重于功能的划分,比如事先 知道需要实现某个功能,但设计时却还没有想到如何实现则先抽象出一个方法,或者把子类中重复实现的功能放到抽象中去实现等。

 

5.3 运行时异常和一般异常的区别

   解析:异常的优点是可以提高程序的健壮性,可读性,可维护性,但异常的设计初衷是用于不正常的情形,一般的JVM是不会试图对它进行优化,所以异常使用不当也会给系统带来负面影响。在谈异常之前,往往还会谈一下Error,它们都是基于Throwable实现;但Error的出现通常是JVM层次无法自动恢复的严重例外,比如JVM内存泄露,一般都会造成系统荡机,所以对于Error进行继承设计的没有意义的。而Exception是可以处理的,合理的使用可以使用系统更健壮。

   参考答案:运行异常(RuntimeException)和一般异常(CheckedException)都是从Exception继续来的,都是可以捕捉到,可以恢复的例外。

一般异常(CheckedException):是JVM强制让我们处理的异常,在现在通常的开发IDE会提示我们必须用try{}catch(..){}捕捉,常见的文件读写的IO导常,Socket通信时的网络异常,数据操作的JDBC异常等。

运行异常(RuntimeException):JVM不强制我们去捕捉,异常一旦发生,会有JVM接管,一直往上抛,直到遇到异常处理的代码;如果没有处理块,到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。

   异常处理的目的就是使程序从异常中恢复,不会中止线程退出系统,所以对于常的Exception要做一些合理的处理,但由于异常机制比较复杂而且往往得不到JVM的优化,会造成性能下降下,所以异常的使用最好对可能出现的异常位置进行捕捉,最好同时进行异常日志记录,便于系统维护。

 

5.4 序列化和反序列化

       解析:对于经常接口开发的的来说,序列化这个词一定不会默生,对象本身是不能直接在网络中传输的,传递的都是一些字节流,而将对象转换成字节流的过程就叫做对象的序列化。这个考点的命中率属于一般,一般多出现于后台接口开发相关的职位面试中,但序列化与反序列化的使用却在开发中无处不在,需要深入了解。

 参考答案:

 1、序列化与反序列化的定义:把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。

 2、对象序列化的作用,比如远程接口调用,对象网络传输;深度克隆时,复杂对象的的复制;缓存对象的硬盘存储;Session的硬盘序列化等。至于反序列化的作用,就是把序列化的对象重新加载到内存中使用。

 3、序列化的实现,首先必须实现Serializable或者Externalizable,而Serializable是超级接口,Externalizable是其实现,Serializable接口中什么也没有定义,它只是对对象生成一个唯一的ID标识,告诉JVM该对象是可以序列化的,如果不实现序列化接口,在序列化的过程中会抛出异常。

 4、JavaAPI提供的序列化方式,java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。对于实现了Externalnalizable接口的类,且类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,按照以下方式进行序列化与反序列化。ObjectOutputStream调用该类生成对象的writeExternal(ObjectOutput out))的方法进行序列化。ObjectInputStream会调用对象的readExternal(ObjectInput in)的方法进行反序列化。

  扩展:Java实现序列化带来了很多好处,但实际开发中要根据项目类型和业务场景慎重实现,因为实现序列化也会付出一定的“代价”,首先会降低类的灵活性,其实会增加出现BUG和漏洞的可能。

      

5.5  Java实现浅克隆与深克隆

  解析:这个面试命中的概率不算太高,此题目是笔者在某家公司的面试的原题,而笔者之前的工作中研究过克隆的使用,在这里总结一下供大家参考(里面用到序列化的一些知识,参考上面)。

 

  参考答案:

  所谓的浅克隆,顾名思义就是很表面的很表层的克隆,如果我们要克隆Administrator对象,只需要克隆他自身以及他包含的所有对象的引用地址。而深克隆,就是非浅克隆。克隆除自身以外所有的对象,包括自身所包含的所有对象实例。至于深克隆的层次,由具体的需求决定,也有“N层克隆”一说。但是,所有的基本(primitive)类型数据,无论是浅克隆还是深克隆,都会进行原值克隆。毕竟他们都不是对象,不是存储在堆中。(Java中所有的对象都是保存在堆中,而堆是供全局共享的。也就是说,如果同一个Java程序的不同方法,只要能拿到某个对象的引用,引用者就可以随意的修改对象的内部数据。)

    潜克隆实现:1. 让该类实现java.lang.Cloneable接口;2. 重写(override)Object类的clone()方法。(并且在方法内部调用父对象的clone()方法)。

       对于复杂对象,比如数组、集合通过浅克隆是不能修改对象的引用的,这里要彩用深度克隆,一般是通过序列和反序列的方式来实现浓度克隆:

  如上面的代码,对序列化的对象写到内存或者研盘上,然后在需要的地方再从内存或者硬盘上反序列化成对象。

 

 

5.6 枚举可以序列化吗

       解析:这个是笔者参加某一个著名的互联网公司的面试题,冷不丁上来这个小问题,如果你平时没太关注,很容易认为是陷阱,大江大河都淌过的人很容易死在小水塘里;而且在两家的互联网开发设计规范中都提到多使用Enum,所以像这样的小知识点平时还是要多留意。

 

  参考答案:因为Enum实现了序列化接口Serializable,所以枚举是可以被序列化的。

 

5.7 Java创建对象的方式

       解析:这个没什么深意,面试官多是想了解一下你的知识面,一般人都应该至少能回答出来两种。

 

  参考答案:

 (1) 用new语句创建对象,这是最常见的创建对象的方法。

 (2) 运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。

 (3) 调用对象的clone()方法。

 (4) 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

 

5.8 能否自己定义一个java.lang.String类

  解析:这个是考察面试人员对类加载机制的了解,问题不难,但需要真正了解JVM对类的加载原理,这个在下面JVM相关部分还会详细提及,这里就简答一下。

 

  参考答案:

  一般情况下是不可以的,类的APP加载器会根据双亲代理机制委托父类加载器去加载此类,而在父亲加载器中已经加载过此类(因为java.lang.String类是JVM定义的类),会报该类已经存。处理方法,自定义加载器加载,指定一个其它路径。

 

5.9 Java线程池的了解

    解析:这里还是多线程考察的延续,在互联网职位、高并发分布式相关的职位中几乎必然会面试多线程题目,而且有资格面试这块的面试官一般都是设计、架构级别的资深人士,难度中偏上,是面试有效筛选题目类型;线程池在jdk1.5之前的版本并不是特别成熟,但在Jdk1.5的版本中作了很大的优化,在jdk1.6的版本中更是引入了java.util.concurrent包,有兴趣的读者可以详细研究一下这个包;而回答此问题可以从线程池的作用、为什么要使用线程池及线程池的优越性、线程池的实现原理和使用等几方面回答。

  参考答案:

 1、线程池的作用:在没用使用线程池的情况下,开发人员通常自己来开发管理线程,很容易将线程开启过多或者过少;过多将造成系统拥堵,过少又浪费系统资源。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

 2、由于手动维护线程成本很高(大家都知道多线程的开发难度大,对程序员要求高,而且代码不易调试维护),所以我们应尽可能用JDK提供的比较成熟的线程池技术,它减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。可以根据系统的承受能力,调整线程池中工作线线程的数目,以达到系统性能最优。

 3、线程池的父接口是Executor,它只定义了一个Execute方法,而通过使用的是它的扩展接口ExecutorService,它有几个常用的实现类AbstractExecutorService, ScheduledThreadPoolExecutor, ThreadPoolExecutor (详见API)。而在实际开发中,通过使用的是一个java.util.concurrent中的一个工具类Executors,里面提供了一些静态工厂,生成常用的线程池(下面这些作为了解,说出一两个就可以了)。

i. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

ii.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

iii. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

iv.newScheduledThreadPool

   创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

5.10  Return和finally语句块的顺序

       解析:这个面试题就是考查基本功的,而且出现频率较高,面试官大部分也不会深问,但希望读者关注一下拓展中的问题,在面试笔试中都有很高的命中率。

  参考答案:Finally是在return语句之前被执行的, 但是finally与return是相互独立的;return语句位置会影响到返回值的结果,finally中的计算将影响到其后return结果,不会影响到try里面return的结果。

  扩展:Finally做为try{}catch(..){}finally{}一部分,如果finally删掉可以吗?catch()删掉呢?只留try{}可以吗?try{}finally{}连用呢?请诸位试一下,这里也是一个考点,这里就不详细说明了。

 

5.11  Java静态变量与实例变量的区别

    解析:这个属于java面向对象中语法级别的考查,是一种基本的面试题,也是不容许失分的题目。

  参考答案:Java成员变量中静态变量用static修饰,而没有static修饰的变量称为实例变量。

 Java静态变量也叫类变量,在类加载时被分配空间(从java内存分配上可以知道,它是存放在静态域中),由于它是属于类的,为所有实例共享,它的特性是一改全改(任何一个实例中修改了这个属性,其它实例都能看到修改的结果),调用方式通过类名.变量名。

 Java实例变量属于对象的,在对象被实例化的时候(new的时候)分配空间,每个实例化的对象都有一套自己的实例变量,调用方法通常通过生成的set/get方法,或者通过this.实例变量名。

 

5.12  同步与异步区别及使用场景

  解析:同步一定是发生在多线程条件下,对共同资源不安全的访问进行控制一种手段;异步是要求减少请求方时间的等待。

  参考答案:如果数据将在线程间共享.例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。

    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效。

 使用场景:同步,最简单最常见的就是火车票销售,不可能多个窗口下允许出现卖两张完全一样的票。异步,在注册页面,当我们写其它信息时,我希望后台已经完成了对我姓名是否重复的验证,而不是我完全写完提交后再去验证。

 

5.13  Java堆和栈的区别

       解析:要想详细了解堆和栈的区别,就要了解JavaVM的内存分配机制,一般会涉及以下几个区域寄存器、堆、栈、静态域、常量池、非RAM存储(这些概念性的东西,理解就好),静态域主要是存放一些static静态成员、常量池存放常量,下面主要说一下堆、栈。

   参考答案:栈与堆都是Java用来在Ram中存放数据的地方,与C和C++不同Java自动管理栈和堆,程序员不能直接地设置栈或堆。

  栈中存放局部变量(基本类型的变量)和对象的引用地址。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性;栈是跟随线程的,有线程就有栈

  堆中存放对象,包括对象变量以及对象方法(大学时老师可能都讲过一个类似的笑话,一New一堆,说的就是堆是用来存放对象的)。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。堆是跟随JVM的,有JVM就有堆内存

http://www.csdn123.com/html/exception/723/723513_723516_723514.htm

 

5.14  Java多线程快速问答

   解析:多线程中有很多知识点,面试经常问到,而且会被连续问到,这里简单介绍一下。

 

 1、Java实现多线程的方式有哪些?

  答:Java实现多线程的方式通常有实现Runnable接口,或者继承Thread类,或者从线程ExecutorService中获取。

 2、你通常用哪种方式来实现线程,为什么?

   答:通常使用实现Runnable接口或者从线程池中来获得经程;实现接口是因为接口是支持多实现,我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此Runnable具有更好的扩展性,而且Thread也是实现了Runnable接口,所以推荐通过接口实现来实现线程。另一种方式就是从线程池中获取线程,优点是将线程交给线程池去管理,可以减轻大量的精力去维持线程状态的管理,而且手动干预的越多,后期的维护成本越高。

  3、run()和start()哪个是线程的启动方法?

  答:run()方法,在实现一个线程类时必须重写的方法,但它本身只是一个普通的方法,如果通过对象.run()去调用,它只是一个属于调用线程中的普通方法,可以调用多次,而且是按顺序执行。而start()方法是线程的启动方法,当他把调用时,线程的状态就变成了就绪,一旦获取了时间片,就会立刻执行(执行run()方法,这里的run是线程体,执行完成表示线程执行结束)。

 4、多线程状态有哪些?

    答:Java线程有五种状态创建、就绪、运行、阻塞和死亡。创建:可以理解我们new了一线程对象;就绪:new的线程对象调用了start()方法,但并没有立即抢到CPU时间片;运行:线程启动后,线程体run方法在执行;阻塞:阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态;死亡:当线程执行完run()方法中的代码或者调用了stop()方法,又或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。

 5、多线程的同步方法有哪些?

    答:wait():使一个线程处于等待状态,并且释放所持有的对象的lock。sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。notifyAll():唤醒所有处入等待状态的线程。

实现线程同步一个是synchronized关键字,一个是通过对象Lock。

 6、你了解死锁吗(如果是笔试可能让你写一段死锁代码)?

  答:死锁可以这样认为:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放(例如有a,b两个线程,a等待b让出了某个资源才可以执行;而b同样等待a让出了某个资源才可以继续,ab都在等待对方就是线程同时阻塞,结局就是死锁)。

  7、当一个线程进入到一个对象的synchronized方法,那么其他线程是否可以进入该对象的其它方法?

    答:当一个线程进入到一个对象的synchronized方法,那么其他线程是可以进入这个对象的非synchronized方法,但不可能进入synchronized方法

  扩展:多线程面试是Java面试中不可缺少的一部分,也是属于面试中的难点,绝对是面试中的淘汰型问题,需要多花点时间准备。

 

第06章 JVM相关

6.1 JVM的工作原理

       解析:很多大的互联网公司笔试时,会让你画一下JVM的结构图,然后让你解释一下各部分的意义,所以这个面试命中率还是很高的,作为基础扎实的求职者是应该要会的。纠错:搜索执行--->执行引擎

  参考答案:下面是我画的一个JVM的结构图

1.类装载器子系统:

   负责查找并装载Class 文件到内存,最终形成可以被虚拟机直接使用的Java类型。

2.方法区:

   在类装载器加载class文件到内存的过程中,虚拟机会提取其中的类型信息,并将这些信息存储到方法区。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。由于所有线程都共享方法区,因此它们对方法区数据的访问必须被设计为是线程安全的。

3.堆:

   存储Java程序创建的类实例。所有线程共享,因此设计程序时也要考虑到多线程访问对象(堆数据)的同步问题。

4.Java栈:

   Java栈是线程私有的。每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈或出栈。当线程调用java方法时,虚拟机压入一个新的栈帧到该线程的java栈中。当方法返回时,这个栈帧被从java栈中弹出并抛弃。一个栈帧包含一个java方法的调用状态,它存储有局部变量表、操作栈、动态链接、方法出口等信息。

5.程序计数器:

   一个运行中的Java程序,每当启动一个新线程时,都会为这个新线程创建一个自己的PC(程序计数器)寄存器。程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。

6.本地方法栈:

    本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态链接并直接调用指定的本地方法。如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。

7.执行引擎:

   负责执行字节码。方法的字节码是由Java虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。执行引擎执行字节码时,首先取得一个操作码,如果操作码有操作数,取得它的操作数。它执行操作码和跟随的操作数规定的动作,然后再取得下一个操作码。这个执行字节码的过程在线程完成前将一直持续。

6.2 类的加载机制

       解析:这个题目看上去不太容易,但实际上在学习Java第一节课的时候,这些知识老师都已经告诉我们了,第一节课就是安装JDK配置环境变量,PATH,JAVA_HOME大家肯定不默生,但随着IDE的使用,CLASSPATH环境变量被慢慢遗忘了,CLASSPATH就是配置我们自己开发的项目路径,JVM在加载我们写的类时,会根据classpath从左到右各个项目中依次查找是否有这个包,包下是否有这个类,找到类后会验证其中的packeage包的路径是否与参数中的路径一致,是则加载,不是会报NotFoundClassException的异常。(这个刚毕业的同学可能比有工作经验的朋友印象更深刻)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

 参考答案:要了解类的加载机制,首先要了解JVM使用到的类的加载器;这里主要有bootstrap classloader、extension cassLoader、AppClassLoader三个类的加载器,第一个是C++写的,两后两个都是JAVA写的,JVM还给我们开放一个ClassLoader来扩展自己的加载器,当然后两个加载器也是继承了ClassLoader。bootstrap classloader在JVM运行的时候加载java核心API以满足java程序最基本的需要,其中就包括用户定义的ClassLoader, ExtClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类。AppClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,通常在没有指定ClassLoader的情况下,程序员自定义的类就由该AppClassLoader来进行加载。

 JVM为了避免类的重复加载,引入了委托加载机制,比如A中定义B的引用,对于B的加载是通过委托A的加载器去加载;在上面我们也提到了双亲委托加载机制,APP加载器加载一个类时,首先会调ExtClassLoader, ExtClassLoader会调用

bootstrap classloader如果发现类已经加载就不再加载。

 扩展:回答完上面这些应该已经差不多了,求职者可以多关注一下,比如预加载,被动加载等。

 

6.3 GC的工作原理

       解析:Java的垃圾回收机制是java语言优越性的一大特色,也奠基它的高级语言的地位。这是一个初级、中级、高级、专家级职位都喜欢面试的问题,因为只有熟练了解JavaGC工作原理,才能写出质量更高的代码,才能更好的在代码级别做性能优化

  参考答案:现在市场上JVM的实现厂家很多,不同的厂家对垃圾回收的算法实现也不同,目前主流上常的算法有标记清除、分代、复制、引用记数、增量回收等,而目前最常用的JVM是SUN公司(现被Oracle收购)的HotSport,它采用的算法为分代回收。

  HotSport将 jvm中堆空间划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。年轻代和年老代是存储动态产生的对象。永久带主要是存储的是java的类信息,包括解析得到的方法、属性、字段等等。永久带基本 不参与垃圾回收。我们这里讨论的垃圾回收主要是针对年轻代和年老代。具体如下图(网络提供)

 

年轻代又分成3个部分,一个eden区和两个相同的survior区。刚开始创建的对象都是放置在eden区的。分成这样3个部分,主要是为了生命 周期短的对象尽量留在年轻代。当eden区申请不到空间的时候,进行minorGC,把存活的对象拷贝到survior。年老代主要存放生命周期比较长的 对象,比如缓存对象。具体jvm内存回收过程描述如下(可以结合上图):

1、对象在Eden区完成内存分配

2、当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收

3、minorGC时,Eden不能被回收的对象被放入到空的survivor(Eden肯定会被清空),另一个survivor里不能被GC回收的对象也会被放入这个survivor,始终保证一个survivor是空的

4、当做第3步的时候,如果发现survivor满了,则这些对象被copy到old区,或者survivor并没有满,但是有些对象已经足够Old,也被放入Old区 XX:MaxTenuringThreshold

5、当Old区被放满的之后,进行fullGC。

  由于fullGC会造成很大的系统资源开销,所以一般情况下通过代码规范和JVM参数设置来减少fullGC的调用,提高性能。

  上面只是从算法实现上来说明的,目前主流的垃圾收集器主要有三种:串行收集器、并行收集器、并发收集器。 这里有兴趣的朋友可以查一下,绝对是加分点,如果在串行的垃圾回收器进行fullGC会造成应用的短暂中断,这是一个很危险的事情,所以一定要了解清楚各种实现方式的区别。

 

 

6.4 Java的反射机制

       解析:这个概念定义起来比较简单,但是使用却不容易,现在很多主流的开源框架中DI,IOC的实现很多都是用Java反射机制来做的,可以适当深入研究一下。

 参考答案:Java发射机制可以让我们在运行时加载,探知,使用编译期间完全未知的classes。换句话说就是Java程序可以加载一个在运行时才得知名称的class,获悉其完整构造,并生成其对象实体,或对其fields设值,或调用其methods。

 反射的作用:在运行的时判定任意一个对象所属的类;运行时,构造任意一个类的对象;运行时,判定一个类所属的成员变量和方法;在运行时调用任意的一个方法;生成动态代理。

 反射的实现步骤(不问不需要答),1、获取类的常用方式有三种:a) Class.forName("包名.类名"),最常用、推荐;b) 包名.类名.class最简捷;c) 对象.getClass的方式获得。2、对象的实例化,上面已经获取了类,只需要调用类的实例化方法,类.newInstance()便可。3、获取属性和构造等,可以参考JavaApi的调用,类. getDeclaredFields,类. getConstructor(..)等。

 

 

6.5 JDK性能分析工具及应用

       解析:这里主要针对一定工作经验的人,而且工作经验越久可能问的越详细,主要考察对JDK性能的监控,用以性能调优。

 参考答案:JDK性能分析工具很多,像jprofileryourkit性能十分优越,但使用费用也不低,对于开发人员的对性能这块的监控JDK本身就给提供了很多优秀的免费的监控工具,如Visualvm(如果jdk自带版本低,可以单独下载)是一款堪比上面商业性能的监控工具、jconsole(下面是我的一个应用截图)能够监视和管理查看堆内存,线程,类,CPU状况。直接双击就可以启动了,然后选择连接本地local还是远程remote,而且支持控制台管理;jstack 主要用于线程死锁的监控;jmap 主要用于监控内存泄露时候对象占用的字节数;jstat 主要用于监控jvm的gc使用情况;jhat 主要用于分析jmap产生的dump并提供web页面查看分析结果。

 

 

收获 offer,挑战高薪,全本请访问:http://shop113074087.taobao.com

 

说明:CoreJava已经完全开放,J2EE部分是两个朋友所写,暂时不免费开放,请谅解!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值