java面试常考

整理文档很辛苦,赏杯茶钱您下走!

免费阅读已结束,点击下载阅读编辑剩下 ...

阅读已结束,您可以下载文档离线阅读编辑

资源描述

一.Java垃圾回收机制:垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc()(GarbageCollection以下简称gc)方法来建议执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。垃圾收集器的主要特点有:工作目标是回收已经无用的对象的内存空间,从而避免内存渗漏体的产生,节省内存资源,避免程序代码的崩溃。(2)垃圾收集器判断一个对象的内存空间是否无用的标准是:如果该对象不能再被程序中任何一个活动的部分所引用,此时我们就说,该对象的内存空间已经无用。所谓活动的部分,是指程序中某部分参与程序的调用,正在执行过程中,尚未执行完毕。当一个方法执行完毕,其中的局部变量就会超出使用范围,此时可以被当作垃圾收集,但以后每当该方法再次被调用时,其中的局部变量便会被重新创建。垃圾收集器线程虽然是作为低优先级的线程运行,但在系统可用内存量过低的时候,它可能会突发地执行来挽救内存资源。当然其执行与否也是不可预知的。垃圾收集器不可以被强制执行,但程序员可以通过调用System.gc方法来建议执行垃圾收集器。不能保证一个无用的对象一定会被垃圾收集器收集,也不能保证垃圾收集器在一段Java语言代码中一定会执行。因此在程序执行过程中被分配出去的内存空间可能会一直保留到该程序执行完毕,除非该空间被重新分配或被其他方法回收。同样没有办法预知在一组均符合垃圾收集器收集标准的对象中,哪一个会被首先收集。循环引用对象不会影响其被垃圾收集器收集。可以通过将对象的引用变量(referencevariables,即句柄handles)初始化为null值,来暗示垃圾收集器来收集该对象。但此时,如果该对象连接有事件监听器(典型的AWT组件),那它还是不可以被收集。所以在设一个引用变量为null值之前,应注意该引用变量指向的对象是否被监听,若有,要首先除去监听器,然后才可以赋空值。每一个对象都有一个finalize()方法,这个方法是从Object类继承来的。每个对象只能调用finalize()方法一次。如果在finalize()方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。finalize()方法可以明确地被调用,但它却不能进行垃圾收集。finalize()方法可以被重载(overload),但只有具备初始的finalize()方法特点的方法才可以被垃圾收集器调用。当finalize()方法尚未被调用时,System.runFinalization()方法(运行处于挂起终止状态的所有对象的终止方法。调用该方法说明Java虚拟机做了一些努力,运行已被丢弃对象的finalize方法,但是这些对象的finalize方法至今尚未运行。当控制权从方法调用中返回时,Java虚拟机已经尽最大努力去完成所有未执行的终止方法。)可以用来调用finalize()方法,并实现相同的效果,对无用对象进行垃圾收集。当一个方法执行完毕,其中的局部变量就会超出使用范围,此时可以被当作垃圾收集,但以后每当该方法再次被调用时,其中的局部变量便会被重新创建。Java语言使用了一种标记交换区的垃圾收集算法。该算法会遍历程序中每一个对象的句柄,为被引用的对象做标记,然后回收尚未做标记的对象。所谓遍历可以简单地理解为检查每一个。总之,在Java语言中,判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个:1.给对象赋予了空值null,以下再没有调用过。2.给对象赋予了新值,既重新分配了内存空间。Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。2.触发主GC(GarbageCollector)的条件JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。②Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则JVM将报“outofmemory”的错误,Java应用将停止。由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。3.减少GC开销的措施根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:(1)不要显式调用System.gc()此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。(2)尽量减少临时对象的使用临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。(3)对象不用时最好显式置为Null一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。(4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章Java中String与StringBuffer)由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。(5)能用基本类型如Int,Long,就不用Integer,Long对象基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。(6)尽量少用静态对象变量静态变量属于全局变量,不会被GC回收,它们会一直占用内存。(7)分散对象创建或删除的时间集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。二Java中几组常见的区别:1.equals和==的区别:(1)基本数据类型:int、float、double等,“==”比较的是基本数据类型的变量的值是否相等;(2)引用类型(数组、String、Integer类对象)变量:“==”比较的是是否为同一对象;‘equals()’比较的是对象的内容是否相同。注意:String的常量为同一个对象,用new生成的为不同的对象。2.String和StringBuffer,StringBuilder的区别:Java中字符串分直接量和变量两种,字符串直接量是使用双引号括起来的一串字符,是一个String对象,可以使用String类中的各种方法。程序中所有字符串直接量将用同一个对象表示。对于字符串变量,它是一个字符串对象的引用,在使用之前同样要进行申明。String:用于处理不变字符串(1)、String是final类,不能被继承。是不可变对象,一旦创建,就不能修改它的值。(2)、对于已经存在的String对象,修改它的值,就是重新创建一个对象,然后将新值赋予这个对象,让同一个引用指向另一个对象。StringBuffer:用于处理可变字符串(1)一个类似于String的字符串缓冲区,对它的修改的不会像String那样重创建对象。(2)使用append()方法修改Stringbuffer的值,使用toString()方法转换为字符串。Stringbuild是jdk1.5后用来替换stringBuffer的一个类,大多数时候可以替换StringBuffer。和StringBuffer的区别在于Stringbuild是一个单线程使用的类,不值执行线程同步所以比StringBuffer的速度快,效率高。是线程非安全的。使用举例Strings1=“hello”;s1=“world”;这个操作其实是:其实是创建了两个String对象。Strings2=hellos2+=world;这操作是:先创建一个String对象,在接下来进行字符串连接的时候,有创建了一个StringBuild(jdk1.5前是StringBuffer),然后调用append()方法,最后调用toString()方法。有此可以看出String对字符的操作比直接使用Stringbuffer(或者StringBuild)要多出附加的操作,而且String是不可变对象,使用String对字符串操作会产生大量的、多余java对象。所以结果是:影响性能,占用空间。3、Wait()和Sleep()的区别:通常,多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如:synchronized(obj){while(!condition){obj.wait();}obj.doSomething();}当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:synchronized(obj){condition=true;obj.notify();}需要注意的概念是:#调用obj的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){}代码段内。#调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){}代码段内唤醒A。#当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。#如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。#obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj

1 / 10
下载文档,编辑使用

©2015-2020 m.777doc.com 三七文档.

备案号:鲁ICP备2024069028号-1 客服联系 QQ:2149211541

×
保存成功