Q:面向对象的特征:继承、封装和多态
封装:
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
继承:
指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
多态:
指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
Q:final, finally, finalize 的区别
final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
Q:Exception、Error、运行时异常与一般异常有 何异同
unchecked exception(非检查异常):包括运行时异常(RuntimeException)和派生于Error类的异常。对于运行时异常,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
checked exception(检查异常,编译异常,必须要处理的异常) :也称非运行时异常(运行时异常以外的异常就是非运行时异常),java编译器强制程序员必须进行捕获处理,比如常见的IOExeption和SQLException。对于非运行时异常如果不进行捕获或者抛出声明处理,编译都不会通过。
Q:请写出5种常见到的runtime exception
IndexOutOfBoundsException(下标越界异常)
NullPointerException(空指针异常)
NumberFormatException (String转换为指定的数字类型异常)
ArithmeticException -(算术运 算异常 如除数为0)
ArrayStoreException - (向数组中存放与声明类型不兼容对象异常)
SecurityException -(安全异常)
Q:int 和 Integer 有什么区别,Integer的值缓存范围
(1)Integer是int的包装类;int是基本数据类型;
(2)Integer变量必须实例化后才能使用;int变量不需要;
(3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
(4)Integer的默认值是null;int的默认值是0。Integer的值缓存范围默认是1bit, -128~127
Q:包装类,装箱和拆箱
一切皆对象,八种基本数据类型不是对象。在此基础上进行包装封装为类,提供一些普通操作
把Int包装成一个类,这样的一个类就可以以对象的形式操作基本数据类型。
1)将基本数据类型变成包装类称为装箱。
2)将包装类的类型变为基本数据类型称为拆箱。
Q:String、StringBuilder、StringBuffer
String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
这方面运行速度快慢为:StringBuilder > StringBuffer > String
Q:重载和重写的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
方法重写(overriding):
1、也叫子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。
2、子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常)
3、子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
方法重载(overloading):重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数,可以参考API帮助文档看看类的构造方法
Q:抽象类和接口有什么区别
(1)抽象类可以有构造方法,接口中不能有构造方法。
(2)抽象类中可以有普通成员变量,接口中没有普通成员变量
(3)抽象类中可以包含静态方法,接口中不能包含静态方法
(4) 一个类可以实现多个接口,但只能继承一个抽象类。
(5)接口可以被多重实现,抽象类只能被单一继承
(6)如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
Q:说说反射的用途及实现
指程序在运行状态中,可以获取自身的信息,并且可以操作任意一个类或对象的内部属性和方法。
获取一个对象对应的反射类,在 Java 中有下列方法可以获取一个对象的反射类
1. 通过
getClass()
方法2. 通过
Class.forName()
方法3. 使用
类.class
通过类加载器实现,
getClassLoader()
Q:说说自定义注解的场景及实现
跟踪代码的依赖性,实现代替配置文件的功能。比较常见的是Spring等框架中的基于注解配置。
还可以生成文档常见的
@See、@param、@return
等。如@override
放在方法签名,如果这个方法 并不是覆盖了超类方法,则编译时就能检查出。使用@interface自定义注解时,自动继承了
java.lang.annotation.Annotation
接口,由编译程序自动完成其他细节,在定义注解时,不能继承其他注解或接口。
Q:HTTP请求的GET与POST方式的区别
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
- GET - 从指定的资源请求数据。
- POST - 向指定的资源提交要被处理的数据
GET方法
请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:
/test/demo_form.asp?name1=value1&name2=value2
- 请求可被缓存
- 请求保留在浏览器历史记录中
- 请求可被收藏为书签
- 请求不应在处理敏感数据时使用
- 请求有长度限制
- 请求只应当用于取回数据
POST方法
请注意,查询字符串(名称/值对)是在 POST 请求的 HTTP 消息主体中发送的:
POST /test/demo_form.asp HTTP/1.1 Host: w3schools.com name1=value1&name2=value2
比较 GET 与 POST
方法 GET POST 缓存 能被缓存 不能缓存 编码类型 application/x-www-form-urlencoded application/x-www-form-urlencoded 或 multipart/form-data。为二进制数据使用多重编码。 对数据长度的限制 是的。当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符) 无限制。 对数据类型的限制 只允许 ASCII 字符 没有限制。也允许二进制数据。 安全性 与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时绝不要使用 GET POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中。 可见性 数据在 URL 中对所有人都是可见的。 数据不会显示在 URL 中。 其他 HTTP 请求方法
- HEAD 与 GET 相同,但只返回 HTTP 报头,不返回文档主体。
- PUT 上传指定的 URI 表示。
- DELETE 删除指定资源。
- OPTIONS 返回服务器支持的 HTTP 方法
- CONNECT 把请求连接转换到透明的 TCP/IP 通道。
Q:Session与Cookie区别
- cookie数据存放在客户的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用COOKIE。
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
- 所以个人建议:
将登陆信息等重要信息存放为SESSION
其他信息如果需要保留,可以放在COOKIE中
Q:session 分布式处理
第一种:粘性session
粘性Session是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,如果负载均衡器设置了粘性Session的话,那么用户以后的每次请求都会转发到A服务器上,相当于把用户和A服务器粘到了一块,这就是粘性Session机制
第二种:服务器session复制
原理:任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步。
第三种:session共享机制
使用分布式缓存方案比如memcached、Redis,但是要求Memcached或Redis必须是集群。
原理:不同的 tomcat指定访问不同的主memcached。多个Memcached之间信息是同步的,能主从备份和高可用。用户访问时首先在tomcat中创建session,然后将session复制一份放到它对应的memcahed上
第四种:session持久化到数据库
原理:就不用多说了吧,拿出一个数据库,专门用来存储session信息。保证session的持久化。 优点:服务器出现问题,session不会丢失 缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。
第五种terracotta实现session复制
原理:就不用多说了吧,拿出一个数据库,专门用来存储session信息。保证session的持久化。 优点:服务器出现问题,session不会丢失 缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库
Q:列出自己常用的JDK包
java.lang: 这个是系统的基础类,比如String等都是这里面的,这个package是唯一一个可以不用import就可以使用的Package
java.io: 这里面是所有输入输出有关的类,比如文件操作等
java.net: 这里面是与网络有关的类,比如URL,URLConnection等。
java.util : 这个是系统辅助类,特别是集合类Collection,List,Map等。
java.sql: 这个是数据库操作的类,Connection, Statememt,ResultSet等
Q:MVC设计思想
每当用户在Web浏览器中点击链接或提交表单的时候,请求就开始工作了。请求是一个十分繁忙的家伙,从离开浏览器开始到获取响应返回,它会经历很多站,在每站都会留下一些信息,同时也会带上一些信息。
Spring工作流程描述原文在这里
1、 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
2、
DispatcherServlet
对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping
获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain
对象的形式返回;3、DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(...)方法)
4、提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
- 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
- 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
- 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
5、Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
6、根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中ViewResolver)返回给DispatcherServlet ;
7、ViewResolver 结合Model和View,来渲染视图
8、将渲染结果返回给客户端。
Spring工作流程描述
- 为什么Spring只使用一个Servlet(DispatcherServlet)来处理所有请求?
- 详细见J2EE设计模式-前端控制模式
- Spring为什么要结合使用
HandlerMapping
以及HandlerAdapter
来处理Handler?- 符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如
HandlerAdapter
可能会被用于处理多种Handler。1、请求旅程的第一站是Spring的
DispatcherServlet
。与大多数基于Java的Web框架一样,Spring MVC所有的请求都会通过一个前端控制器(front contrller)Servlet.前端控制器是常用Web应用程序模式。在这里一个单实例的Servlet将请求委托给应用的其他组件来执行实际的处理。在Spring MVC中,DisPatcherServlet就是前端控制器。2、DisPactcher的任务是将请求发送Spring MVC控制器(controller).控制器是一个用于处理请求的Spring组件。在典型的应用中可能会有多个控制器,
DispatcherServlet
需要知道应该将请求发送给那个哪个控制器。所以Dispactcher以会查询一个或 多个处理器映射(Handler mapping),来确定请求的下一站在哪里。处理映射器根据请求携带的 URL信息来进行决策。3、一旦选择了合适的控制器,
DispatcherServlet
会将请求发送给选中的控制器。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器 本身只是处理很少,甚至不处理工作,而是将业务逻辑委托给一个或多个服务器对象进行处理)4、控制器在完成处理逻辑后,通常会产生一些信息。这些 信息需要返回给 用户,并在浏览器上显示。这些信息被称为模型(Model),不过仅仅给用户返回原始的信息是不够的----这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送一个视图(View),通常会是JSP。
5、 控制器做的最后一件事就是将模型打包,并且表示出用于渲染输出的视图名。它接下来会将请求连同模型和视图发送回DispatcherServlet。
6、这样,控制器就不会与特定的视图相耦合传递给控制器的视图名并不直接表示某个特定的jsp。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(View resolver),来将逻辑视图名称匹配为一个特定的视图实现,他可能也可能不是JSP
7、虽然DispatcherServlet已经知道了哪个驶入渲染结果、那请求的任务基本上也就完成了,它的最后一站是试图的实现。在这里它交付给模型数据。请求的任务就结束了。视图将使用模型数据渲染输出。这个输出通过响应对象传递给客户端(不会像听上去那样硬编码)
可以看到,请求要经过很多步骤,最终才能形成返回给客户端的响应,大多数的 步骤都是在Spirng框架内部完成的。
Q:equals与==的区别
- 1、使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。
- 2、==返回true如果两个引用指向相同的对象,equals()的返回结果依赖于具体业务实现
- 3、字符串的对比使用equals()代替==操作符
其主要的不同是一个是操作符一个是方法,==用于对比原生类型而equals()方法比较对象的相等性。
Q:hashCode和equals方法的区别与联系
hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等;
(1)equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的;
(2)hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
对于需要大量并且快速的对比的话如果都用equal()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equal(),如果equal()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
然而hashCode()和equal()一样都是基本类Object里的方法,而和equal()一样,Object里hashCode()里面只是返回当前对象的地址,如果是这样的话,那么我们相同的一个类,new两个对象,由于他们在内存里的地址不同,则他们的hashCode()不同,所以这显然不是我们想要的,所以我们必须重写我们类的hashCode()方法.
Q:什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化(将对象转换成二进制)。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间,序列化是为了解决在对对象流进行读写操作时所引发的问题。把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的反序列化。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
serialVersionUID
适用于java序列化机制。简单来说,JAVA序列化的机制是通过判断类的serialVersionUID
来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID于本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException。具体序列化的过程是这样的:序列化操作时会把系统当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明序列化文件的版本与当前类的版本是一样的,可以反序列化成功,否则就失败;
Q:Object类中常见的方法,为什么wait notify会放在Object里边?
首先,虽然这些方法都跟线程的状态变化有关,但wait(),notify(),notifyAll()这三个方法在用法上就跟sleep()方法不太一样,wait(),notify(),notifyAll()必须在sychronized同步代码块中使用,且要用当前线程持有的锁来调用,比如锁.wait(),锁.notify(),而sleep()方法则是Thread类的一个静态方法,直接通过Thread类调用即可。
复习一下这几个方法的作用,wait方法的作用是让获得了锁的线程释放锁,并且把该线程放在这个对象锁的等待池中,请注意,是当前这个对象锁,而不是该线程拥有的其他对象锁(一个线程可以拥有多个对象锁),此时该线程状态会变为waiting;而notify是把该对象锁的等待池中其中一个线程(随机一个)放到锁池中,notifyAll则是把该对象锁的等待池中的线程全部放到锁池中;调用seelp方法会把线程的状态变成timed_waiting,但并不会释放锁。
知道上面这些后,其实就很容易理解为什么jdk把wait,notify,notifyAll放在Object类里面,而把sleep放在Thread类里面了。我觉得有两个原因:
1.在java的内置锁机制中,每个对象都可以成为锁,也就是说每个对象都可以去调用wait,notify方法,而Object类是所有类的一个父类,把这些方法放在Object中,则java中的所有对象都可以去调用这些方法了。
2.一个线程可以拥有多个对象锁,wait,notify,notifyAll跟对象锁之间是有一个绑定关系的,比如你用对象锁aObject调用的wait()方法,那么你只能通过aObject.notify()或者aObject.notifyAll()来唤醒这个线程,这样jvm很容易就知道应该从哪个对象锁的等待池中去唤醒线程,假如用Thread.wait(),Thread.notify(),Thread.notifyAll()来调用,虚拟机根本就不知道需要操作的对象锁是哪一个。
Q:Java的平台无关性如何体现出来的
平台无关性就是一种语言在计算机上的运行不受平台的约束,一次编译,到处执行(Write Once ,Run Anywhere)。
Q:JDK和JRE的区别
JRE是
Java Runtime Environment
的缩写,顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的,还有所有的Java类库的class文件,都在lib目录下,并且都打包成了jar。Jdk是
Java Development Kit
的缩写,顾名思义是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。JRE是Java Runtime Environment的缩写,顾名思义是java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的,还有所有的Java类库的class文件,都在lib目录下,并且都打包成了jar。
Q:Java 8有哪些新特性
(1)Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用Lambda 表达式可以使代码变的更加简洁紧凑。
(2) 方法引用通过方法的名字来指向一个方法。方法引用可以使语言的构造更紧凑简洁,减少冗余代码。方法引用使用一对冒号 :: 。
(3) 函数式接口(FunctionalInterface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为lambda表达式。函数式接口可以现有的函数友好地支持 lambda。
(4) Java 8 新增了接口的默认方法。简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个default关键字即可实现默认方法。
(5) Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
(6) Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。
(7) jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
(8) Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
(9) 在Java8中,Base64编码已经成为Java类库的标准。Java 8 内置了 Base64 编码的编码器和解码器。
Q:Object的hashcode()是怎么计算的?
JDK8 的默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia's xorshift scheme随机数算法得到的一个随机数。对xorshift算法有兴趣可以参考原论文:https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf 。
Q:若hashcode方法永远返回1或者一个常量会产生什么结果?
每次使用java对象“.”hashCode()时,返回的都是相同的数值, 其次,说下hashCode()的值并不一定是对象在内存地址或物理地址
第三,判断java对象的值是否相同的是equals方法,判断对象基本类型是否相同是用的==,而hashCode()这个方法也是比较对象是否相同的一个依据,当hashCode()返回常量时,所有对象都出现hash冲突,而hashCode()本身的性能也会降级。
做hash的key的时候效率会极度变低。
变量比较也会变慢
Q:引用计数法与GC Root可达性分析法区别;
引用计数法师垃圾收集的早期策略,在这中方法中,堆中每个对象都有一个引用计数,每当有一个地方引用他时,引用计数值就+1,当引用失效时,引用计数值就-1,任何时刻引用计数值为0的对象就是可以被回收,当一个对象被垃圾收集时,被它引用 的对象引用计数值就-1,所以在这种方法中一个对象被垃圾收集会导致后续其他对象的垃圾收集行动。
优点:判定效率高;
缺点:不完全准确,当两个对象相互引用的时候就无法回收,导致内存泄漏。
可达性分析算法的基本思路就是通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
在Java语言里,可作为GC Roots对象的包括如下几种:
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象
Q:浅拷贝和深拷贝的区别;
如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。
浅拷贝
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。深拷贝
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
序列化
clone 方法 对其内的引用类型的变量,再进行一次 clone()。
Q:String s="abc"和String s=new String("abc")区别;
String s = new String("abc")的创建过程
- 系统先在字符串常量池里面寻找是否有一个"abc"的字符串,
- 如果有的话,则在堆中复制一个该字符串,并且将堆中的引用指向s,这个时候系统只创建了一个对象,即堆中的对象;
- 如果没有的话,则会先在字符串常量池中先创建一个字符串为"abc"的常量,然后再复制到堆里面,最后将堆所在的地址指向s,这个时候创建了两个对象;
String s = "abc"的创建过程
- 系统先在字符串中寻找是否存在"abc"的常量
- 如果存在,则直接将该"abc"在常量池中的地址指向s,这个时候,系统没有创建新对象。
- 如果不存在,则在常量池中新建一个"abc"并放入常量池里面,然后再返回该地址,这个时候,系统创建了一个对象。
Q:Java中的回调机制;
同步调用是最基本并且最简单的一种调用方式,类A的方法a()调用类B的方法b(),一直等待b()方法执行完毕,a()方法继续往下走。这种调用方式适用于方法b()执行时间不长的情况,因为b()方法执行时间一长或者直接阻塞的话,a()方法的余下代码是无法执行下去的,这样会造成整个流程的阻塞。
异步调用是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。类A的方法方法a()通过新起线程的方式调用类B的方法b(),代码接着直接往下执行,这样无论方法b()执行时间多久,都不会阻塞住方法a()的执行。但是这种方式,由于方法a()不等待方法b()的执行完成,在方法a()需要方法b()执行结果的情况下(视具体业务而定,有些业务比如启异步线程发个微信通知、刷新一个缓存这种就没必要),必须通过一定的方式对方法b()的执行结果进行监听。在Java中,可以使用Future+Callable的方式做到这一点,具体做法可以参见我的这篇文章Java多线程21:多线程下其他组件之CyclicBarrier、Callable、Future和FutureTask。
Q:模板方法模式;
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
何时使用:有一些通用的方法。
如何解决:将这些通用算法抽象出来。
关键代码:在抽象类实现,其他步骤在子类实现。
Q:发布/订阅使用场景;
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
如何解决:使用面向对象技术,可以将这种依赖关系弱化。
关键代码:在抽象类里有一个 ArrayList 存放观察者们。
Q:KMP算法(一种改进的字符串匹配算法);
KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字(接下来称它为P),如果它在一个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常用手段).
Q:JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思
原子性:java中提供两个高级的字节码指令monitorenter、monitorexit,使用对应的关键字synchronized来保证代码块内的操作是原子性的 可见性:java中使用volatile来保证多线程操作变量的可见性。volatile修饰的变量在修改后会被立刻同步到主内存,该变量每次使用之前都会从主内存中刷新 有序性:java中使用synchronized和volatile来实现有序性,只是显示方式有所区别。volatile禁止指令重排,synchronized保证同一时刻只允许一个线程操作
先从CPU层面来了解一下什么是内存屏障?
CPU的乱序执行,本质还是由于在多CPU的机器上,每个CPU都存在cache,当某个数据第一次被一个 CPU获取时,由于在该CPU缓存中不存在这个数据,就会从内存中去获取,从而被加载到CPU高速缓存中后就能从缓存中快速访 问。当某个CPU进行写操作时,必须确保其他的CPU已经将这个数据从他们各自的缓存中移除,这样才能让其他CPU 在各自的缓存中安全的修改数据。显然,存在多个cache时,我们必须通过缓存一致性协议来避免数据不一致的问题,而这 个通讯的过程就可能导致乱序访问,也就是运行时的内存乱序访问。
内存屏障分为:写屏障(store barrier)、读屏障(load barrier)和全屏障(Full Barrier),主要的作用有两点:防止指令之间的重排序和保证数据的可见性 。
Q:写出一个必然会产生死锁的伪代码;
死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。
例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。
死锁
Q:toString()方法什么情况下需要重写;
主要是根据需求来,当默认的toString()不能满足你对”文本方式表示此对象“时,重写toString(),例如bean类需要在重写的toString 方法中组织自己想要显示的当前对象的信息。
官方文档如下:
public String toString()
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
Q:判断对象相等时,什么情况下只需要重写 equals(),什么情况下需要重写 equals(),hashcode()?
加入到HashSet中的自定义类对象,为确保他们不重复,需要对他们的类重写equals() 和 hashcode()的方法。
如果不重写equals() 方法,相同的内容不同引用的对象会被当做不同的对象被加入到hashset中
注意:1、重写equals必须重写hashCode方法 因为是先比较两个对象的hash值是否相等在判断值
2、equals() 和 hashCode() 的定义必须兼容 如果 x.equals(y) 则x , y 的hashCode必须相等,
3、x , y 的 hashCode相等,x , y 不一定相等
Q:如何保证分布式缓存的一致性(分布式缓存一致性hash算法?)?分布式session实现?
Q:Java 8流式迭代的好处?
流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要,以后有时间再来详细分析一下并行流式数据处理的具体实现和最佳实践。
Q:项目中用到的JDK的哪些特性?
针对项目使用的JDK版本围绕性解释用到一些特性。
Q:序列化和反序列化底层如何实现的(ObjectOutputStream 、ObjectInputStream、 readObject writeObject);
Q:Java内存模型,方法区存什么;
方法区(又叫静态区)特点如下:
1. JVM只有一个方法区,被所有线程共享 2. 方法区实际也是堆,只是用于存储类、常量相关的信息 3. 用来存放程序中永远是不变或唯一的内容(类信息、Class对象、静态变量、字符串常量等。
Q:CMS垃圾回收过程;
CMS的GC过程有6个阶段(4个并发,2个暂停其它应用程序):
1. 初次标记(STW initial mark):标记老年代中所有的GC Roots引用的对象;标记老年代中被年轻代中活着的对象引用的对象(初始标记也会扫描新生代);会导致stw。
2. 并发标记(Concurrent marking):从初次标记收集到的‘根’对象引用开始,遍历所有能被引用的对象。
3. 并发可中断预清理(Concurrent precleaning):改变当运行第二阶段时,由应用程序线程产生的对象引用,以更新第二阶段的结果。标记在并发标记阶段引用发生变化的对象,如果发现对象的引用发生变化,则JVM会标记堆的这个区域为Dirty Card。那些能够从Dirty Card到达的对象也被标记(标记为存活),当标记做完后,这个Dirty Card区域就会消失。
4. 最终重新标记(STW remark):由于并发预处理是并发的,对象引用可能发生进一步变化。因此,应用程序线程会再一次被暂停(stw)以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。
5. 并发清理(Concurrent sweeping):清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
6. 并发重置(Concurrent reset):CMS清除内部状态,为下次回收做准备。
Q:Full GC次数太多了,如何优化;
Q:直接内存如何管理的;
Q:如果你的项目出现了内存泄露,怎么监控这个问题呢;
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
Java中内存泄漏的发生场景。具体主要有如下几大类:
1、静态集合类引起内存泄漏
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
3、监听器
4、各种连接
5、内部类和外部模块的引用
6、单例模式
Q:标记清除和标记整理的区别和优缺点,为何标记整理会发生stop the world;
标记清除:会将需要回收的内存标记下来,然后进行清除。会造成内存碎片,大量的内存碎片会导致另一次GC
标记整理:标记过程与标记清除类似,整理过程则是将所有存活的对象向一侧移动,然后直接清除边界以外的内存
Q:线程池,如何根据CPU的核数来设计线程大小,如果是计算机密集型的呢,如果是IO密集型的呢?
计算密集型:在N CPU的处理器上工作,N+1个线程最优
IO密集型:线程数=((线程等待时间+线程CPU时间)/线程CPU时间)*CPU数目
Q:让你设计一个cache如何设计;
主要是想知道如何设计cache的算法
cache的算法设计常见的有:FIFO(先进先出)、LRU(least recently used最近最少使用)、LFU(least Frequently used最不经常使用)
Q:JDK中哪些实现了单例模式?
java.lang.Runtime,可以控制JVM的状态和行为
java.lang.reflect.Proxy感觉不像单例模式
Q:String中hashcode是怎么实现的
hashCode的计算应用在集合中的时候,我们通过hashCode来比较两个对象是否相等,如果hashCode不相等则两个对象一定不等。如果hashCode相等的话再调用对象的equals方法来比较。
由于hashCode的比较比equals的比较快的多,所以我们在设计hashCode的时候要尽可能的让hashCode不重复。
String.hashCode()源码如下:
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
更多可参考:https://blog.csdn.net/myspacedemen/article/details/53353480
Q:char和double的字节,以及在内存的分布是怎样;
char:2byte;double:8byte。
浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。
Q:对象内存布局,然后讲下对象的死亡过程?
Q:对象头,详细讲下;
Q:float f = 1.4f;double d = 1.4d; 与 float f = 1.5f;double d = 1.5d; 是否为true,内存是怎样的;
Q:把所有认识熟用的JUC( java.util.concurrent(简称JUC)包)下的类写出来,讲下使用,然后讲下原生的线程操作;
Q:逃逸分析是什么,作用是什么,用途是什么;
当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
Q:怎么认为一个类是线程安全?线程安全的定义是什么?Java有多少个关键字进行同步?为什么这样设计?
线程安全就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问
Q:抽象方法和类方法的区别,static的抽象方法可以吗?
(1)抽象类只能作为其他类的基类,它不能直接实例化,对抽象类不能使用new 操作符。
(2)抽象类中可以包含抽象成员,但非抽象类中不可以。
(3)如果一个非抽象类从抽象类中派生,则其必须通过覆盖来实现所有继承而来的抽象成员。