《32Java GC面试题及答案(1~5题).docx》由会员分享,可在线阅读,更多相关《32Java GC面试题及答案(1~5题).docx(6页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Java GC面试题及答案(15题)L既然有GC机制,为什么还会有内存泄露的情况?理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个圭要原因).然而在实 际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生.例如hibernate的Session (一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的 垃圾对象,如果不及时关闭(close )或清空(flush ) 一级缓存就可能魏内存泄露.下面例子中的代码也会导致内存泄露.jeiva ut.il .Arrays;
2、javei , util , EmptyStackExceptiion;MySlackT elements;size = 0;工NIT CAPACITY -elements - (T) Object1N1T CAPACITY;push(T elcm) push(T elcm) elements sizg+ + J = eleni;(size = 0)(size = 0)EmptyStackException();elements-sizg;onsureCapacily () (elements. len- elements - Arrays.copyOf (elements,2 size !)
3、;上面的代码实现了一个栈(先进后出(FILO )结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试.然 而其中的pop方法却存在内存泄露的问题,当我们用pop方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再 引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete reference )。在支持垃圾回收的语言中,内存泄露是很隐蔽的, 这种内存泄露其实就是无意识的对象保持.如果一个对象引用被无意识的保存起来了,那么垃圾回收器不会处理这个对段,也不会处理 该对象引用的X他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之
4、外,从而对性能造成更大影响, 极端情况下会引发Disk Paging (物理内存与硬盘的虚拟内存交换数据),甚至造成。utOfMemoryError.2、Java中为什么会有GC机制呢?安全性考虑;-for security.减少内存泄露;-erase memory leak in some degree.减少程序员工作IL -Programmers dont worry about memory releasing.3、对于Java的GC哪些内存需要回收?内存运行时JVM会有一个运行时数据区来管理内存.它主要包括5大局部:1 .程序计数器(Program CounterRegister);.
5、虚拟机筏(VM Stack);2 .本地方法栈(Native Method Stack);.方法区(Method Area);5.堆(Heap).而其中程序计数器、虚拟机栈、本地方法栈是每个线程私有的内存空间,随线程而生,随线程而亡.例如栈中每一个栈帧中分配多少内 存基本上在类结构确定是哪个时就了,因此这3个区域的内存分配和回收都是确定的,无需考虑内存回收的问题.但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创立哪些对象,这局部内 存的分配和回收都是动态的,GC主要关注的是这局部内存.总而言之,GC主要进行回收的内存是JVM中的方法区和堆.4、
6、Java的GC什么时候回收垃圾?在面试中经常会碰到这样一个问题(事实上笔者也碰到过):如何判断一个对象已经死去?很容易想到的一个答案是:对一个对象添加引用计数器,每当有地方引用它时,计数器值加1 ;当引用失效时,计数器值减1.而当计数 器的值为0时这个对象就不会再被使用,判断为已死.是不是简单又直观。然而,很遗憾。这种做法是错误的!为什么是错的呢?事实 上,用引用计数法照实在大局部情况下是一个不错的解决方案,而在实际的应用中也有不少案例,但它却无法解决对象之间的循环引用 问题。比方对象A中有一个字段指向了对象B ,而对象B中也有一个字段指向了对象A ,而事实上他们俩都不再使用,但计数器的值永
7、远都不可能为0 ,也就不会被回收,然后就发生了内存泄露. 正确的做法应该是怎样呢?在Java , C#等语言中,比拟主流的判定f 对象已死的方法是:可达性分析(Reachability Analysis).所有生成的对象都是一个称为.GC Roots”的根的子树.从GC Roots开始向下搜索,搜索所经过的路径称为引用鹿(Reference Chain),当一个对象到GC Roots没有任何 引用链可以到达时,就称这个对象是不可达的(不可引用的),也就是可以被GC回收了. 无论是引用计数器还是可达性分析,判定对蕊是否存活都与引用有关!那么,如何定义对象的引用呢?我们希望给出这样一类描述:当内存
8、空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,那么可以抛弃这 些对象.所以根据不同的需求,给出如下四种引用,根据引用类型的不同,GC回收时也会有不同的操作: 强引用(Strong Reference):Object obj=new Object。;只要强引用还存在,GC永远不会回收掉被引用的对象. 软引用(Soft Reference):描述一些还有用但非必需的对象.在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收 (即系统将会发生内存溢出了,才会对他们进行回收)弱引用(Weak Reference):程度比软引用还要弱一些.这些对象只能生存到下次G
9、C之前.当GC工作时,无论内存是否足够都会将其 回收(即只要进行GC ,就会对他们进行回收.) 虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。关于方法区中需要回收的是一些废弃的常 信和无用的类.L废弃的常量的回收。这里看引用计数就可以了。没有对象引用该常量就可以放心的回收了.2.无用的类的回收.什么是无用的类呢?A.该类所有的实例都己2被回收.也就是Java堆中不存在该类的任何实例;B加载该类的ClassLoader已经被回收;C该类对应的对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法.总而言之:对于堆中的对象,主要用 可达性
10、分析判断一个对象是否还存在引用,如果该对嵌没有任何引用就应该被回收.而根据我们实际对引用的不同需求,又分成了 4种 引用,每种引用的回收机制也是不同的.对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。而对于类, 如果可以判定它为无用类,就可以被回收了.5、通过10个例如来初步认识Java8中的lambda表达式用lambda表达式实现Runnable/ Java 8 之前:Thread( Runnable () .startrincln( Bctore Jhh8 too much ccdo for loo little lo do );Thread()-System,o
11、ut.printlnhi Java8. Lambda expression rocks !”),start();输出:too much code , for too little to doLambda expression rocks!这个例子向我们展示了 Java 8 lambda表达式的语法.你可以使用lambda写出如下代码:statementtamsj expression params)例如,如果你的方法不对参数进行修改、至写,只是在控制台打印点东西的话,那么可以这样写:Hello Laabdii Expressions );如果你的方法接收两个参数,那么可以写成如下这样:even
12、, odd) - even + od眼便提一句,通常都会把lambda表达式内部变后的名字起得短一些.这样能使代码更简短,放在同一行.所以,在上述代码中,变最 名选用a、B或者x、y会比even、0dd要好. 使用Java 8 lambda衷达式进行事件处理如果你用过Swing API编程,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但现在可以不这样了。你可 以用lambda表达式写出更好的事件监听代码,如下所示:Button show = JButton( Show ); show.addActionListener (ActionListener) dellonPe
13、riormoc(ActlonEvenl e) System.out .println ( Event handling wiihout lambda expression is boringshow , addActionListenerStem,out .print In (Light, CamerH, Action !|ambda oxprossions Rocks);使用Java 8 lambda表达式进行事件处理使用lambda表达式对列表进行迭代如果你使过几年Java ,你就知道针对集合类,最常见的操作就是进行迭代,并将业务逻辑应用于各个元素,例如处理订单、交易和事件 的列表.由于J
14、ava是命令式语言,Java 8之前的所有循环代码都是I顶序的,即可以对其元素进行并行化处理,如果你想做并行过谑,就 需要自己写代码,这并不是困容易.通过引入lambda表达式和默认方法,将做什么和怎么做的问题分开了 ,这意味着Java集合现在 知道怎样做迭代,并可以在API层面对集合元素进行并行处理.下面的例子里,我将介绍如何在使用lambda或不使用lambda表达式 的情况下迭代列表.你可以看到列表现在有了 forEach。方法,它可以迭代所有对象,并将你的lambda代码应用在其中.|List features = Arrays .dsListLambdas Defaul l Mclh
15、od , SlrcaiaAPI Date and Time API ); (String feature :features)stemout printin(feature);List features = Arrays .msList ( Laabdas , Defaull Molhod Stream API . Ddle and Time API );features,forEach(n - System.。口c.println(口);ealures forEach(System.out:printin)输出:Lambdas Default Method Stream APIDate an
16、d Time API列表循环的最后 f例子展示了如何在Java 8中使用方法引用(method reference ).你可以看到C + +里面的双目号.范围解析操作 符现在在Java 8中用来表示方法引用. 使用lambda表达式和函数式接口 Predicate除了在语言层面支持函数式编程风格Java 8也添加了一个包,叫做java.utiLfunction.它包含了很多类,用来支持Java的函数式编程. 其中一个便是Predicate ,使用函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代 码支持更多的动态行为.下面是Java 8 Predicate的例子,展示了过谑
17、集合数据的多种常用方法.Predicate接口非常适用于做过派.List 二anguages=Auays asList (JavzJ, Scala, C+, Haskell, Lisp);filter(Languages, (str)-str.startsWith( J );rintln( languages uhich ends wiih a );f ilr.ar (st r ( ,i );System, out .print In ( Print all languages :);filter(languages, (str)System.out .printIn ( Pi int no
18、language : );filter(languages.System.out.println ( Prinl liingiuig。 whose lenglh grouter Ihun 4:);eSr (str)-str.length()I);filler(List names. Predicate condition)(condition test(name)(rintln(name+v );rintln(name+v );fi1 tor(List names. Predicate condition) namesstream().f i1tGt(name)一 (condi t ion.tost(name)forEach(name)一)(System.out.printin(name +可以看到,Stream API的过泥方法也接受一Predicate ,这意味假设可以将我们定制的filter。方法替换成写在里面的内联代码,这就是 lambda裳达式的魔力.另外,Predicate接口也允许进行多重条件的测试.