美文网首页
近期面试java基础部分面试题

近期面试java基础部分面试题

作者: 陈二狗想吃肉 | 来源:发表于2021-05-06 16:15 被阅读0次

    java部分:

    常见的加载器有哪些,如何自定义一个加载器?

    类加载器:启动类加载器(bootstrap classLoader),扩展类加载器(extension),应用类加载器(Application),自定义类加载器(user ClassLoader);

    双亲委派模型:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载.

    使用双亲委派模型原因:java类随着其类加载器一起具备了一种带有优先级的层次关系.例如 java.lang.Object,无论哪一个类加载器要加载该类,最终都是委托给处于顶端的启动类加载器,因此object在程序的各种类加载器环境中都是同一个类.相反如果没有使用双亲委派模型,那么假如用户自定义了一个称为java.lang.Object的类,并放在classPath中,那么系统将会出现多个不同的Object类,则java类型体系中最基础的行为都无法保证.

    为什么需要自定义类加载器:

    1)加密:java代码可以轻易的被反编译,如果你需要对你的代码进行加密以防止反编译,可以先将编译后的代码用加密算法加密,类加密后就不能再使用java自带的类加载器了,这时候就需要自定义类加载器.

    2)从非标准的来源加载代码:字节码是放在数据库,甚至是云端,就可以自定义类加载器,从指定来源加载类.

    自定义类加载器的方法:

    1、如果不想打破双亲委派模型,那么只需要重写findClass方法即可

    2、如果想打破双亲委派模型,那么就重写整个loadClass方法

    ----------------

    ----------------

    java Future 接口

    在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实现,可以来进行异步计算。

    Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。

    Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。

    Future接口是一个泛型接口,严格的格式应该是Future<V>,其中V代表了Future执行的任务返回值的类型。 Future接口的方法介绍如下:

    booleancancel(boolean mayInterruptIfRunning) 取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束

    booleanisCancelled() 任务是否已经取消,任务正常完成前将其取消,则返回true

    booleanisDone() 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true

    Vget() throws InterruptedException, ExecutionException  等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException

    Vget(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException

    Future的实现类有java.util.concurrent.FutureTask<V>即 javax.swing.SwingWorker<T,V>。通常使用FutureTask来处理我们的任务。FutureTask类同时又实现了Runnable接口,所以可以直接提交给Executor执行。使用FutureTask实现超时执行的代码如下:

    1ExecutorService executor = Executors.newSingleThreadExecutor();    2FutureTask future = 3newFutureTask(newCallable() {//使用Callable接口作为构造参数    4public String call() {    5//真正的任务在这里执行,这里的返回值类型为String,可以为任意类型    6      }});    7executor.execute(future);    8//在这里可以做别的任何事情    9try {  10result = future.get(5000, TimeUnit.MILLISECONDS);//取得结果,同时设置超时执行时间为5秒。同样可以用future.get(),不设置执行超时时间取得结果  11}catch (InterruptedException e) {  12futureTask.cancel(true);  13}catch (ExecutionException e) {  14futureTask.cancel(true);  15}catch (TimeoutException e) {  16futureTask.cancel(true);  17}finally {  18    executor.shutdown();  19}

    不直接构造Future对象,也可以使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如下:

    1ExecutorService executor = Executors.newSingleThreadExecutor();  2FutureTask future = executor.submit(  3newCallable() {//使用Callable接口作为构造参数  4public String call() {  5//真正的任务在这里执行,这里的返回值类型为String,可以为任意类型  6  }});  7//在这里可以做别的任何事情  8//同上面取得结果的代码

    ExecutorService executor = Executors.newSingleThreadExecutor();

    FutureTask future = executor.submit(

      newCallable() {//使用Callable接口作为构造参数public String call() {

          //真正的任务在这里执行,这里的返回值类型为String,可以为任意类型  }});//在这里可以做别的任何事情//同上面取得结果的代码

    利用Future接口实现程序执行超时大致用法就这么多,改天需要研究下Future接口的内部实现,特别是设定执行超时的实现。

    ---------------

    ----------------

    什么是内存泄漏,为什么会导致内存溢出?

    工作一段时间后,会经常听到内存溢出,那内存溢出到底是哪里的内存溢出,是什么原因导致的,如何解决,今天就来深入了解一下.

    在java中,要了解内存,需要先清楚jvm内存模型,我们常说的java内存实际上就是指Runtime Data Area,分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分.这里不做具体介绍.

    1.常见的内存泄露

    (1)内存分配未成功,却使用了它

    (2)内存分配成功,但尚未初始化就引用它

    (3)内存分配成功且初始化,但操作越过了内存的边界

    (4)忘记释放内存,造成内存泄漏

    (5)释放了内存却继续使用它

    以发生的方式来分类:

    (1)常发性内存泄漏,发生内存泄漏的代码会被多次执行到,每次执行都会导致一块内存泄漏

    (2)偶发性内存泄漏

    (3)一次性内存泄漏,发送泄漏的代码只会被执行一次

    (4)隐式内存泄漏,程序在运行过程中不停地分配内存,但直到结束时才释放内存。

    2.为什么会导致内存溢出

    编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理,当java对象不再被应用时,等到堆内存不够用时,jvm会进行垃圾回收,清除这些对象占用的堆内存空间,如果对象一直被应用,jvm无法对其进行回收,创建新的对象时,无法从Heap中获取足够的内存分配给对象,这时候就会导致内存溢出。而出现内存泄露的地方,一般是不断的往容器中存放对象,而容器没有相应的大小限制或清除机制。容易导致内存溢出。

    3.如何发现内存泄漏

    可以直接使用VisualVM,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈.

    如果要在服务器上使用Java VisualVM, 比如CentOS。那么就出现 WARNING: environment variable DISPLAY is not set,因为一般服务器都不会装X server。我们可以在远程机器上装一个X server,比如windwos上,那么就可以非常方便的查看服务器运行情况。

    如果有大量的FGC就要查询是否有内存泄漏的问题了,图中的FGC数量就比较大,并且执行时间较长,这样就会导致系统的响应时间较长,如果对jvm的内存设置较大,那么执行一次FGC的时间可能会更长。(直接运行linux上的jvisualvm,下载X-Manager,可以将视图展现在本地机器上。)

    从上图可以发现执行FGC的情况,下午3:10分之前是没有FGC的,之后出现大量的FGC。

    上图是jvm堆内存的使用情况,下午3:10分之前的内存回收还是比较合理,但是之后大量内存无法回收,最后导致内存越来越少,导致大量的full gc。

    4.如何定位内存泄漏

    1、查看Visual GC标签,内容如下,这是输出first的截图

    这是输出forth的截图:

    通过2张图对比发现:

    老生代一直在gc,当程序继续运行可以发现老生代gc还在继续:

    增加到了7次,但是老生代的内存并没有减少。说明存在无法被回收的对象,可能是内存泄漏了。

    如何分析是那个对象泄漏了呢?打开抽样器标签:点击后如下图:

    按照程序输出进行堆dump,当输出second时,dump一次,当输出forth时dump一次。

    进入最后dump出来的堆标签,点击类:

    点击右上角:“与另一个堆存储对比”。如图选择第一次导出的dump内容比较:

    比较结果如下:

    可以看出在两次间隔时间内TestMemory对象实例一直在增加并且多了,说明该对象引用的方法可能存在内存泄漏。

    如何查看对象引用关系呢?

    右键选择类TestMemory,选择“在实例视图中显示”,如下所示:

    左侧是创建的实例总数,右侧上部为该实例的结构,下面为引用说明,从图中可以看出在类CyclicDependencies里面被引用了,并且被HashMap引用。

    如此可以确定泄漏的位置,进而根据实际情况进行分析解决。

    ----------------

    -----------------

    生产上有没有与到JVM参数调优的问题?

    原始配置:

    -Xms128m  -Xmx128m  -XX:NewSize=64m  -XX:PermSize=64m -XX:+UseConcMarkSweepGC  -XX:CMSInitiatingOccupancyFraction=78 -XX:ThreadStackSize=128k

    -Xloggc:logs/gc.log  -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.server.exceptionTrace=true

    问题:

    ◆  permsize 设置较小,很容易达到报警范围(0.8)

    ◆ 没有设置MaxPermSize,堆增长会带来额外压力。

    ◆ NewSize较大,old gen 剩余空间64m,一方面可能会带来old区容易增长到报警范围(监控数据显示oldgenused长期在50m左右,接近78%,容易出现full gc),另一方面也存在promontion fail风险。

    Jvm内存调优:

    -Xms128m  -Xmx128m  -Xmn24m-XX:PermSize=80m 

    -XX:MaxPermSize=80m -Xss256k-XX:SurvivorRatio=1-XX:MaxTenuringThreshold=20-XX:+UseParNewGC 

    -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75-XX:+UseCMSCompactAtFullCollection  -XX:+CMSParallelRemarkEnabled  -XX:CMSFullGCsBeforeCompaction=2-XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -XX:+PrintHeapAtGC  -Xloggc:logs/gc.log  -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.server.exceptionTrace=true

    修改点:

    ◆ PermSize与MaxPermSize都设置为80,一方面避免non heap warn(报警阀值0.8 非对内存一般占用到60M以内),一方面避免堆伸缩带来的压力

    ◆ 通过设置Xmn=24M及SurvivorRatio=1 使得Eden区=from space=to space=8M,降低了Eden区大小,降低YGC的时间(降低到3-4ms左右),同时通过设MaxTenuringThreshold=20,使得old gen的增长很缓慢。带来的问题是YGC的次数明显提高了很多。

    ◆ 其他参数优化 修改后带来的好处见另一篇文章对参数的详细介绍

    再次进行内存调优:

    -Xms128m 

    -Xmx128m 

    -Xmn36m 

    -XX:PermSize=80m 

    -XX:MaxPermSize=80m 

    -Xss256k 

    -XX:SurvivorRatio=1

    -XX:MaxTenuringThreshold=20-XX:+UseParNewGC 

    -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=73

    -XX:+UseCMSCompactAtFullCollection 

    -XX:+CMSParallelRemarkEnabled 

    -XX:CMSFullGCsBeforeCompaction=2-XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -XX:+PrintHeapAtGC  -Xloggc:logs/gc.log  -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.server.exceptionTrace=true

    修改点:

    在上面的基础上调整Xmn大小到36M,设置CMSInitiatingOccupancyFraction=73。

    Dden区与Survivor区大小都增加到12M,通过CMSInitiatingOccupancyFraction计算公式,计算得出value为73是,可以避免promotion faild问题,因为 年老去的大小为 (128m - 36m)0*(1-0.73) = 24.86m  完全可以放一下一个Eden区的大小;

    同时满足堆内存监控报警值在80%:内存大小128M*80%=102.4M, 102.4M-36M=66.4M (老生代达到此值也会报警 老生代达到92M*0.73 =67.15M)将发生Full GC,所以在老生代大小达到66.4M时也就是WARN报警时将很有可能出现Full GC。

    增大了Eden和Survivor区的值,会减小YGC的次数,但由于空间变大理论上也会相应的增加YGC的时间,不过由于新生代本身就很小(才36M)这点儿变化可以忽略掉。实际的监控值显示YGC的时间在4-5ms之间。是可以接受范围。

    SurvivorRatio 这个值还得在仔细考虑下,有待优化中。

    网上某个牛人的配置 :每天几百万pv一点问题都没有,网站没有停顿

    $JAVA_ARGS 

    .="-Dresin.home=$SERVER_ROOT 

    -server 

    -Xms6000M 

    -Xmx6000M 

    -Xmn500M 

    -XX:PermSize=500M 

    -XX:MaxPermSize=500M 

    -XX:SurvivorRatio=65536

    -XX:MaxTenuringThreshold=0

    -Xnoclassgc 

    -XX:+DisableExplicitGC 

    -XX:+UseParNewGC 

    -XX:+UseConcMarkSweepGC 

    -XX:+UseCMSCompactAtFullCollection 

    -XX:CMSFullGCsBeforeCompaction=0

    -XX:+CMSClassUnloadingEnabled-XX: 

    -CMSParallelRemarkEnabled 

    -XX:CMSInitiatingOccupancyFraction=90

    -XX:SoftRefLRUPolicyMSPerMB=0

    -XX:+PrintClassHistogram 

    -XX:+PrintGCDetails 

    -XX:+PrintGCTimeStamps 

    -XX:+PrintHeapAtGC 

    -Xloggc:log/gc.log ";

    该调优设置中:

    -XX:SurvivorRatio=65536  -XX:MaxTenuringThreshold=0  目的就是去掉了救助空间 也就是年轻代中只有Eden区 Eden的对象 会直接到年老区中;

    -Xnoclassgc 禁用类垃圾回收,性能会高一点;

    -XX:+DisableExplicitGC禁止System.gc(),免得程序员误调用gc方法影响性能;

    -XX:+UseParNewGC,对年轻代采用多线程并行回收,这样收得快;

    带CMS(并发收集算法)参数的都是和并发回收相关的,不明白的可以上网搜索;

    CMSInitiatingOccupancyFraction,这个参数设置有很大技巧,基本上满足如下公式:

    (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn

    就不会出现promotion failed;

    在我的应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500兆,也就是年老代有5500兆,CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS),这时还剩10%的空间是5500*10%=550兆,所以即使Xmn(也就是年轻代共500兆)里所有对象都搬到年老代里,550兆的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的promotion failed;

    SoftRefLRUPolicyMSPerMB这个参数我认为可能有点用,官方解释是softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap,我觉得没必要等1秒;

    继续进行jvm调优:

    -Xmx4000M  -Xms4000M  -Xmn600M  -XX:PermSize=500M  -XX:MaxPermSize=500M  -Xss256K  -XX:+DisableExplicitGC  -XX:SurvivorRatio=1-XX:+UseConcMarkSweepGC  -XX:+UseParNewGC  -XX:+CMSParallelRemarkEnabled  -XX:+UseCMSCompactAtFullCollection  -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled  -XX:LargePageSizeInBytes=128M  -XX:+UseFastAccessorMethods  -XX:+UseCMSInitiatingOccupancyOnly  -XX:CMSInitiatingOccupancyFraction=80-XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -XX:+PrintHeapAtGC  -Xloggc:log/gc.log

    改进说明:

    第一次的调优方法不太好,因为没有用到救助空间,所以年老代容易满,CMS执行会比较频繁(年老代进行一次GC 则年轻代也要及进行一次GC 想当于一次Full GC)。我改善了一下,还是用救助空间,但是把救助空间加大,这样也不会有promotion failed。

    具体操作上,32位Linux和64位Linux好像不一样,64位系统似乎只要配置MaxTenuringThreshold参数,CMS还是有暂停。为了解决暂停问题和promotion failed问题,最后我设置-XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,年老代和永久代上升非常慢(因为好多对象到不了年老代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次,这样,服务器都不用重启了。

    某网友的调优方案:

    $JAVA_ARGS 

    .="  -Dresin.home=$SERVER_ROOT  -server  -Xmx3000M  -Xms3000M  -Xmn600M  -XX:PermSize=500M  -XX:MaxPermSize=500M  -Xss256K  -XX:+DisableExplicitGC  -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC  -XX:+UseParNewGC  -XX:+CMSParallelRemarkEnabled  -XX:+UseCMSCompactAtFullCollection  -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled  -XX:LargePageSizeInBytes=128M  -XX:+UseFastAccessorMethods  -XX:+UseCMSInitiatingOccupancyOnly  -XX:CMSInitiatingOccupancyFraction=70 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps  -XX:+PrintHeapAtGC -Xloggc:log/gc.log";

    64位jdk参考设置,年老代涨得很慢,CMS执行频率变小,CMS没有停滞,也不会有promotion failed问题,内存回收得很干净。

    -------------------

    -------------------

    相关文章

      网友评论

          本文标题:近期面试java基础部分面试题

          本文链接:https://www.haomeiwen.com/subject/kdrddltx.html