Java面试大全(更新中)

一:基础篇

1.1:java基础

  1:包装类,装箱,拆箱,Integer的值的缓存范围?

        包装类即使把基本类型变成对象类型;自动根据数值创建对应的Integer对象,这就是装箱;自动将包装器类型转换为基本数据类型,这就是拆箱;Integer的值的缓存范围为-128~127。

     对于Integer、Short、Byte、Character、Long类型的valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象(Integer=100,这样的赋值会调用valueOf方法)

Integer a=128,Integer b=128 boolean res=(a==b)         res为false, 当   a,b都为 -128~127之间的某数时,res为true

 

2.:jdk动态代理和cglib的效率比较:

教科书说法:

1.CGLib所创立的动态代理商对象在实际运行时候的性能要比JDK动态代理商高不少,有研究表明,大概要高10倍;

2、但是CGLib在创立对象的时候所花费的时间却比JDK动态代理商要多很多,有研究表明,大概有8倍的差距;

3、因而,对于singleton的代理商对象或者者具备实例池的代理商,由于无需频繁的创立代理商对象,所以比较适合采使用CGLib动态代理商,反正,则比较适使用JDK动态代理商。

  实际效果:

在1.6和1.7的时候,JDK动态代理的速度要比CGLib动态代理的速度要慢,但是并没有教科书上的10倍差距,在JDK1.8的时候,JDK动态代理的速度已经比CGLib动态代理的速度快很多了。

 

4.java内存结构中方法区存的什么:

类信息,常量,静态变量。

 

5.full gc次数太多,如何优化:

a:扩大老年代大小;b:禁止使用System.gc()方法(建议jvm执行fullgc,并不一定会执行),

c:使用标记压缩算法(标记整理)保证老年代连续可用空间最大:(标记清除算法会产生大量的内存碎片)

原因:执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小。
如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。
如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC。
如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC。
如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC
内存分配担保机制:就是当在新生代无法分配内存的时候,把新生代的对象转移到年老代,然后把新对象放入腾空的新生代。

d:排查代码中的无用大对象(内存泄漏)

 

 

6.java直接内存如何管理:

直接内存不属于 Java 堆,所以它不受堆大小限制,但是它受物理内存大小的限制。

可以通过 -XX:MaxDirectMemorySize 参数来设置最大可用直接内存,如果启动时未设置则默认为最大堆内存大小,即与 -Xmx 相同。即假如最大堆内存为1G,则默认直接内存也为1G,那么 JVM 最大需要的内存大小为2G多一些。当直接内存达到最大限制时就会触发GC,如果回收失败则会引起OutOfMemoryError。

一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

 

7.线程池中使用有界队列和无界队列的区别:

   使用有界队列:队列大小有限,可以保证内存不会发生溢出, 但是存在任务入队失败的情况(队列已满并且当前线程数为最大线程数),此时需要执行饱和策略(abort(直接抛异常),discard(直接丢弃任务),或者自定义)。

 使用无界队列:队列大小无限,所有任务可以保证进入队列不会丢失,但是随着队列的扩大可能造成内存溢出,此时线程池的最大线程数设置已经没有意义(所有任务都会进入队列等待线程池分配核心线程执行)。

 

 

8.垃圾回收为什么发生stop the world

所谓的Stop the World机制,简称STW,即在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。

当jvm运行对象存活判定算法的时候(根搜索算法),如果当前环境下,对象之间的引用还在发生变化,那么这个算法几乎没法执行,所以常常需要STW来维持秩序。

 

9.一个线程连续调用两次start会发生什么?

Java 的线程是不允许启动两次的,第二次调用必然会抛出 IllegalThreadStateException,这是一种运行时异常,多次调用 start 被认为是编程错误。

 

10.rt.jar被什么类加载器加载,什么时间加载:
    rt.jar(Java基础类库,也就是Java doc里面看到的所有的类的class文件),被bootstrapClassLoader(启动)加载器加载,应该是在jvm启动的时候加载。

 

11.自己写的类被什么加载,什么时候加载:
  自己的写的类应该是被AppClassLoader(系统)加载器加载。

 什么时候被加载:1.实例通过使用new()关键字创建或者使用class.forName()反射

2.类的静态方法被调用            3.初始化这个类的子类的时候       4 .类的静态域被赋值

 

12. 在java里,数组一旦被声明,数组元素就自动初始化了,值为0,不会有null,实际长度为10

    .length 是数组的属性,获取的是声明的大小; length() 是对象的方法列如 String.length()    ,size()是集合的方法,获取的实际元素个数。

 

13.Class.forName和ClassLoader的区别:

在java中Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。

Class.forName(String className);这个方法的源码是

    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

     Class.forName加载类是将类进了初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。

14.已知两个数a,b要求不使用其他变量和函数来交换这两个数的值:

异或运算:两个操作数的位中,相同则结果为0,不同则结果为1。 

a=a^b;   b=a^b;      a=a^b;

 

1.2:多线程

  1:为什么wait与notify设计在Object类中?

         1.在java的内置锁机制中,每个对象都可以成为锁,也就是说每个对象都可以去调用wait,notify方法,而Object类是所有类的一个父类,把这些方法放在Object中,则java中的所有对象都可以去调用这些方法了。

         2.一个线程可以拥有多个对象锁,wait,notify,notifyAll跟对象锁之间是有一个绑定关系的,比如你用对象锁aObject调用的wait()方法,那么你只能通过aObject.notify()或者aObject.notifyAll()来唤醒这个线程,这样jvm很容易就知道应该从哪个对象锁的等待池中去唤醒线程,假如用Thread.wait(),Thread.notify(),Thread.notifyAll()来调用,虚拟机根本就不知道需要操作的对象锁是哪一个。

 

2:wait,notify为什么必须在synchronized里进行:

事情得从一个多线程编程里面臭名昭著的问题"Lost wake-up problem"说起。

这个问题并不是说只在Java语言中会出现,而是会在所有的多线程环境下出现。

假如有两个线程,一个消费者线程,一个生产者线程。生产者线程的任务可以简化成将count加一,而后唤醒消费者;消费者则是将count减一,而后在减到0的时候陷入睡眠:

生产者是两个步骤:

  1. count+1;

  2. notify();

消费者也是两个步骤:

  1. 检查count值;

  2. 睡眠或者减一;

万一这些步骤混杂在一起呢?比如说,初始的时候count等于0,这个时候消费者检查count的值,发现count小于等于0的条件成立;就在这个时候,发生了上下文切换,生产者进来了,噼噼啪啪一顿操作,把两个步骤都执行完了,也就是发出了通知,准备唤醒一个线程。这个时候消费者刚决定睡觉,还没睡呢,所以这个通知就会被丢掉。紧接着,消费者就睡过去了……。

 

3. 程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

 

4. 虚拟机栈和本地方法栈为什么是私有的?

  • 虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
  • 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

 

5. 什么是上下文切换?

概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换

 

6.如何避免线程死锁?

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

破坏互斥条件

这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。

破坏请求与保持条件

一次性申请所有的资源。

破坏不剥夺条件

占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。

破坏循环等待条件

靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

 

 

7.volatile的作用:

1.保证线程之间的可见性(不能保证原子性)          2.禁止重排序

volatile怎么实现线程之间的可见性:
volatile可以让对变量的操作都是直接读写主内存,一般来说变量更新后都会选存储在cpu缓存中,然后才会同步到主内存,这段时间如果有其他线程操作该变量,是看不到该变量已经发生变化的。

.

 

8.线程调用start方法启动流程

start ->调用start0 ->调用run方法

既然 start() 方法会调用 run() 方法,为什么我们调用 start() 方法,而不直接调用 run() 方法?

当你调用 start() 方法时,它会新建一个线程然后执行 run() 方法中的代码。如果直接调用 run() 方法,并不会创建新线程,方法中的代码会在当前调用者的线程中执行

 

9:synchronized的局限性和Lock的优点:

1.在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待.Lock可以通过tryLock(long time, TimeUnit unit))实现线程等待一段时间后就释放当前占有的锁。

2.多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作,这样效率极低。Lock中读写锁ReentrantReadWriteLock可以让多个线程同时进行读操作允许读读。

3.我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。

1.3:web模块

1.Https和Http的区别:

1、https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。(原来网易官网是http,而网易邮箱是https。)

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息)。

 

2.浏览器输入url发生了什么:

1.DNS域名解析          2.建立Tcp连接       3.发起Http请求   

4.接受响应结果    5.浏览器解析html    6.浏览器布局渲染

 

 

3.TCP和UDP

TCP和UDP的区别:

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议,他们之间的区别包括:

    是否连接:TCP是面向连接的,UDP是无连接的,TCP的发送发要确认接收方是否收到数据段(3次握手协议)
    传输可靠性:TCP是可靠的,UDP是不可靠的;
    TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多的通信模式
    TCP是面向字节流的,UDP是面向报文的(流模式和数据报模式)
    TCP拥有拥塞控制机制,UDP没有拥塞控制,适合媒体通信
    TCP要求系统资源较多,UDP较少
    TCP首部开销(20个字节)比UDP的首部开销(8个字节)要大,TCP传送数据段的时候要给段标号,UDP协议不用

TCP如何保证可靠性:

    数据包校验:目的是检测数据在传输过程中的任何变化,若校验包出错,则丢弃报文不给出响应,这时TCP发送数据端超时后会重发数据。
    超时重发:当TCP发出一个段之后,启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
    对失序数据包重排序:TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层。
    丢弃重复数据:将丢弃重复数据。
    应答机制:当TCP收到来自另一端的数据,它将发送一个确认。
    流量控制:TCP连接的每一方都有固定大小的缓冲空间。TCP接收端只允许另一端发送接收端缓冲区所能接纳的数据,这可以防止较快主机致使较慢主机的缓冲区溢出,这就是流量控制。TCP使用的流量控制协议是可变大小的滑动窗口协议。

 

4.跨域问题的产生和常见解决方式:

要搞懂什么是跨域要明白同源策略,同源策略满足条件有三个:1.协议相同   2.端口相同   3.域名相同,如果不满足同源策略就会产生跨域问题。

常见解决方式:

1.jsonp(前端),缺点:只支持get请求。

2.配置CORS:服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。

3.nginx反向代理实现跨域

使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过 nginx 配置一个代理服务器(域名与 页面域名 相同,端口不同)做跳板机,反向代理访问 后端 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。

 

 

5.redis怎么存放对象:

存String类型,将对象转化为json格式(json格式和对象可以互相转换)

 

6.redis宕机之后,redis值会失效吗?
不会,redis有持久化机制,默认开启rdp机制,rdb方式是在一定时间内,key和value的修改次数达到一定值后才会去做持久化,所以rdb可能会造成数据的丢失。rdb方式当redis断开连接后会自动做备份,kill 9和断电除外。

如果想保证数据尽量不丢失,最好采用aof方式,aof是实时进行保存(aof性能没有rdb高,rdb使用单独的子进程进行持久化)

 

 

 

 

1.4:Mysql相关

1.mysql中having和where的区别:

where 后面要跟的是数据表里的字段,而having只是根据前面查询出来的是什么就可以后面接什么或者直接跟聚合函数。

select –>where –> group by–> having–>order by

 

2.sql语句查询问题:

现有student(s_id,s_name,s_score,c_id)    class(c_id,c_name),学生和班级表,通c_id关联。

a:查询没有学生的班级(不能使用子查询)

            select  class.c_id  from  class left  join  student  on  class.c_id=student.c_id  where student.c_id is null

 

 

 

 

3.mybatis中#和$的区别:

#{} 和 ${} 在预编译中的处理是不一样的。#{} 在预处理时,会把参数部分用一个占位符 ? 代替,而 ${} 则只是简单的字符串替换,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中,${} 会导致 sql 注入的问题。

#{ } 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符,变量替换完成在DBMS中。

${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将会进行变量替换,DBMS拿到的即为执行的sql。

 

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值