Java面试题(网上资料整理)

参考:web缓存的了解
参考:Java检查异常、非检查异常、运行时异常、非运行时异常的区别
参考:finally代码块中的代码一定会执行吗?

1.反射的性能问题和优化

1.反射原理

首先我们需要了解java程序运行的过程,该过程包含两个阶段编译期和运行期。首先java代码会通过jdk编译成.class字节码文件,程序运行的时候,jvm会去调用业务逻辑对应需要的的字节码文件,生成对应的Class对象,并调用其中的属性方法完成业务逻辑。
在这里插入图片描述

而java反射则是在运行期时,主动让jvm去加载某个.class文件生成Class对象,并调用其中的方法属性。

在这里插入图片描述

2.反射为什么慢

Class.forName这个方法比较耗时,它实际上调用了一个本地方法,通过这个方法来要求JVM查找并加载指定的类。

3.通过什么方式优化

  • 利用缓存
    可以把Class.forName返回的Class对象缓存起来,下一次使用的时候直接从缓存里面获取,这样就极大的提高了获取Class的效率。同理,在我们获取Constructor、Method等对象的时候也可以缓存起来使用,避免每次使用时再来耗费时间创建。

  • 通过字节码生成的方式来实现的反射机制
    高性能反射工具包ReflectASM:reflectasm

2.Java事件机制

Java事件机制包括三个部分:事件、事件监听器、事件源,使用的设计模式是监听者模式
这边 写了一个开门的事件监听,可以帮助理解这个东西

1.事件

事件,一般是继承了EventObject
事件就是作为事件监听器触发的依据

/**
 * 事件,一般是继承了EventObject
 * 事件就是作为事件监听器触发的依据
 */
class DoorEvent extends EventObject {

    /**
     * Constructs a prototypical Event.
     *
     * @param    source    The object on which the Event initially occurred.
     * @exception IllegalArgumentException  if source is null.
     */
    public DoorEvent(Object source,String control) {
        super(source);
        this.control = control;
    }

    private String control;

    public String getControl() {
        return control;
    }

    public void setControl(String control) {
        this.control = control;
    }

    public void open() {
        System.out.println("open the door");
    }

    public void close() {
        System.out.println("close the door");
    }
}

2.事件监听器

获取到对应的事件,进行事件监听,根据不同的事件作出不同的反馈
一般一类事件监听器对应一类事件

interface DoorEventListener extends EventListener {
    void doorControl(DoorEvent doorEvent);
}


class DoorListenerImpl implements DoorEventListener {

    @Override
    public void doorControl(DoorEvent doorEvent) {
        if ("open".equals(doorEvent.getControl())) {
            doorEvent.open();
        } else if ("close".equals(doorEvent.getControl())) {
            doorEvent.close();
        }
    }
}

3.事件源

事件源,可以理解为事件发生的地方
有一组时间监听器,当发生一个对应的事情的时候,通知对应的事件监听器去处理对应的事件

class DoorEventSource {
    private List<DoorEventListener> doorEventListener;

    public List<DoorEventListener> getDoorEventListener() {
        return doorEventListener;
    }

    public void setDoorEventListener(List<DoorEventListener> doorEventListener) {
        this.doorEventListener = doorEventListener;
    }

    public void openDoor(){
        System.out.println("我要进门了");
        doorEventListener.forEach(item->item.doorControl(new DoorEvent(this,"open")));
    }



    public void closeDoor(){
        System.out.println("我要出门了");
        doorEventListener.forEach(item->item.doorControl(new DoorEvent(this,"close")));
    }
}

进行事件的触发

    public static void main(String[] args) {
        DoorEventSource doorEventSource = new DoorEventSource();
        doorEventSource.setDoorEventListener(Lists.newArrayList(new DoorListenerImpl()));
        doorEventSource.openDoor();
        doorEventSource.closeDoor();
    }

3.从事件机制理解Spring的事件发布

1.Spring事件

Spring的ApplicationEvent也是去继承了EventObject,再往下也有很多的具体事件实现类去区分不同的事件。

public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened */
	private final long timestamp;


	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}


	/**
	 * Return the system time in milliseconds when the event happened.
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}

2.Spring事件监听器

Spring的事件监听器继承Eventlistener,同样对于不同的事件,有不同的事件监听的实现。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

3.Spring的事件源(发布者)

当Spring进行onRefresh调用的时候会去触发事件的发布。

1.首先是注册监听器,往事件发布者(事件源)中注册对应的监听器
在这里插入图片描述
在这里插入图片描述

2.注册完之后,调用事件发布者去执行这些监听事件
在这里插入图片描述

3.执行即调用事件监听器的处理方法
在这里插入图片描述
在这里插入图片描述

4.Java常见的队列使用场景

1.newSingleThreadExecutor

注释提取
创建一个执行器,该执行器使用单个工作线程在无边界队列上操作。

只创建一个执行器,不设置超时.也就是说永远只有一个执行器在执行。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

2.newFixedThreadPool

注释提取
在任何时候,最多n个线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待线程可用。

不设置超时时间,corePoolSize和MaxPoolSize一样,也就是说任何时间都只有一定量的线程在执行。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

3.newCachedThreadPool

注释提取
这些池通常会提高执行许多短期异步任务的程序的性能。执行调用将重用先前构造的线程(如果可用)。如果没有现有线程可用,将创建一个新线程并将其添加到池中。已60秒未使用的线程将被终止并从缓存中移除。

使用无界限队列SynchronousQueue,设置60S超时时间,corePoolSize容量为0。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
    }

4.newScheduledThreadPool

定时任务线程池

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

5.newWorkStealingPool

参考:介绍 ForkJoinPool 的适用场景,实现原理

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

5.Web缓存

1.概述

Web缓存就在服务器-客户端之间搞监控,监控请求,并且把请求输出的内容(例如html页面、 图片和文件)(统称为副本)另存一份;然后,如果下一个请求是相同的URL,则直接请求保存的副本,而不是再次麻烦源服务器。

在这里插入图片描述

在这里插入图片描述

2.使用缓存的原因

  • 降低延迟
    缓存离客户端更近,因此,从缓存请求内容比从源服务器所用时间更少,呈现速度更快,网站就显得更灵敏。

  • 降低网络传输
    副本被重复使用,大大降低了用户的带宽使用,其实也是一种变相的省钱(如果流量要付费的话),同时保证了带宽请求在一个低水平上,更容易维护了。

3.Web缓存的类型

1. 浏览器缓存

浏览器会在你的硬盘上专门开辟一个空间专门为你存储资源副本。浏览器缓存的工作规则很简单:检查以确保副本是最新的,通常只要一次会话(就是当前浏览器调用的这次N)。

浏览器缓存在用户触发“后退”操作或点击一个之前看过的链接的时候很管用。同样,如果你在网站上访问同一张图片,该图片可以从浏览器缓存中调出并几乎立即显现出来。

2.代理服务器缓存
用户设定浏览器通过缓存进行Web访问
浏览器向缓存/代理服务器发送所有的HTTP请求
如果所请求的对象在缓存中,缓存返回对象
否则,缓存服务器向原始的服务器发送HTTP请求,
获取对象,然后返回给客户端并保存该对象。
在这里插入图片描述
3. 网关缓存

也被称为“反向代理缓存”或“替代缓存”。网关缓存同样是起中介作用的,不过不是(素不相识、不曾谋面的Add)网络管理员部署的,而多半是网站管理员(公司专门的运维工程师、或UED或程序组某人Add)他们自己部署,这样更容易扩展与维护。cdn 和负载均衡就是这个应用。

4.如何保证缓存服务器与远端服务器的数据一致

当需要改动远端服务器的数据的时候,更新变化的那部分的数据,并且要设置一个新的Last-Modified值
而客户端从缓存服务器获取数据的时候,先会拿着Last-Modified 的值和远端服务器做对比,如果发生改变则获取最新的资源进行缓存,如果没有改变,则直接返回缓存服务器当前的缓存即可。

6.一致性哈希算法

参考:白话解析:一致性哈希算法
防丢失:截图保存

7.Java异常

1.异常层次

在这里插入图片描述

2.检查异常

就是编译器要求你必须处理的异常。比如我们在编程某个文件的读于写时,编译器要求你必须要对某段代码try…catch… 或者 throws exception,这就是检查异常,简单的来说,你代码还没有运行,编码器就会检查你的代码,对可能出现的异常必须做出相对的处理。(比如当文件不存在时…)

在这里插入图片描述

在这里插入图片描述

3.非检查异常

编译器不要求强制处置的异常,虽然有可能出现错误,但是我不会在编译的时候检查。
在这里插入图片描述

8.finally一定会执行吗

答案:不一定,我们来看一下几种场景

1.未执行try(){}finally{}之前就返回

	public static boolean getTrue(boolean flag) {
	        if (flag) {
	            return flag;
	        }
	        try {
	            flag = true;
	            return flag;
	        } finally {
	            System.out.println("我是一定会执行的代码?");
	        }
	    }

2.未执行try(){}finally{}之前就报错

	public static boolean getTrue(boolean flag) {
        int i = 1/0;
        try {
            flag = true;
            return flag;
        } finally {
            System.out.println("我是一定会执行的代码?");
        }
    }

3.finally之前执行了System.exit()

	public static boolean getTrue(boolean flag) {
        try {
            flag = true;
            System.exit(1);
            return flag;
        } finally {
            System.out.println("我是一定会执行的代码?");
        }
    }

4.主线程终止,守护线程的finally不执行

	public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5);
                } catch (Exception e) {
                }finally{
                    System.out.println("我是一定会执行的代码?");
                }
            }
        });
        
        t1.setDaemon(true);//设置t1为后台线程
        t1.start();
        System.out.println("我是主线程中的代码,主线程是非后台线程。");
    }

5.在finally中报错

如果finally中执行报错,报错之后的内容不会再执行。如果在finally中返回,则不会返回

   public static void main(String[] args) {
       int i = get();
        System.out.println(i);
    }

    private static int get() {
        try{
            System.out.println("try");
        }finally {
            int i = 1/0;
            System.out.println("catch");
            return 1;
        }
    }
发布了82 篇原创文章 · 获赞 15 · 访问量 3126

猜你喜欢

转载自blog.csdn.net/qq_34326321/article/details/103560920