面试试答0309
HashMap的源码,实现原理,JDK8 中对HashMap做了怎样的优化
my:HashMap 使用了数组加链表的方式来存储数据,JDK1.8后,链表在超过一定长度后会转换为红黑树。
answer:除了上面说的之外, JDK1.7之前,插入数据使用头插法,在并发情况下会导致链表成环。JDK1.8后,修改头插法为尾插法。 优化了高位运算的 hash 算法,将 hashcode 无符号右移16位,让高16位和低16位进行异或。
HashMap扩容是怎样的,为什么都是2的N次幂大小?
my:HashMap 有一个负载因子的参数,默认是0.75。当 HashMap 中存储的数据量达到阈值的时候,就会触发扩容。扩容时需要重新计算哈希值值,比较影响性能。HashMap 将容量设计成2的N次幂,在扩容的时候就可以使用右移操作来代替哈希值的重新计算。
answer:不是右移,是与运算
HashMap,HashTable,ConcurrentHashMap的区别
my:HashMap 是线程不安全的,HashTable 和 ConcurrentHashMap 线程安全。HashTable 用 synchronized 来实现线程安全,性能较差,不推荐使用。ConcurrnetHashMap 之前使用分段锁,1.7还是1.8之后使用 synchronized 和 CAS。synchronized 就用来锁链表或红黑树的头节点。
answer:差不多
Java 中四种修饰符的限制范围
my:四种修饰符是默认,private,protected 和 public。 默认是包权限,private 是类权限,protected 加了个子类权限,public 权限最大。
answer:protected 是同包权限+子类权限
Object 类中的方法有哪些
my:应该有个hashcode(),别的忘了,可能有个 equals()。
answer:
- getClass():获取对象的运行时 class 对象
- hashCode():获取对象的散列值,默认返回对象的堆内存地址
- equals()
- clone():实现对象的浅拷贝
- toString()
- notify()
- notifyAll()
- wait() 有几种参数
- finalize:保护方法,主要用于在GC的时候再次被调用
接口和抽象类的区别
my:只能继承一个抽象类,可以实现多个接口。接口中只能写抽象方法,1.8里有了默认方法。抽象类即可以先抽象方法,也可以写普通方法。
answer:对
动态代理的两种方式,以及区别
my:不知道
answer:动态代理指 JVM 在运行期间动态创建 class 字节码并加载的过程。有两种方式,JDK 代理和 cglib 代理。JDK 代理是基于接口的动态代理技术,但要求目标对象必须有接口;cglib 代理是基于父类的动态代理技术。 #TODO
Java 序列化的方法
my:只知道一个@Serializable注解。
answer:有两种方法,一个是实现 Serializable
接口;另一个是实现 Externalizable
接口,必须要实现 writeExternal
,readExternal
两个方法,并且必须提供无参构造器。
Externalizable 可以由程序员决定存储哪些信息,并且性能略好,但因为复杂程度高,所以一般还是用 Serializable。
值传递和引用传递的区别,Java中是怎样的,有没有值传递
my:当其中一个变量的值变了的时候,值传递的变量的值不会变,引用传递的变量的值会变。Java中值传递和引用传递都有吧,基本类型值传递,对象引用传递。
answer:这个问题感觉不用纠结
值传递:
在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容
引用传递:
”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响的真实内容。
Java中不存在引用传递,基本数据类型很好理解,下面就对象进行说明:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void PersonCrossTest(Person person){
System.out.println("传入的person的name:"+person.getName());
person.setName("我是张小龙");
System.out.println("方法内重新赋值后的name:"+person.getName());
}
看起来原始的Person
对象也被修改了,但这并不意味着是引用传递。PersonCrossTest
方法内的形参与外部的person
实参在两个不同的虚拟机栈中,内存地址不同,它们只是指向了堆中的同一个对象。
如果修改方法:
public static void PersonCrossTest(Person person){
System.out.println("传入的person的name:"+person.getName());
person = new Person();
System.out.println("方法内重新赋值后的name:"+person.getName());
}
外部的实参 person 并不会被初始化,印证了这一点。
一个 ArrayList 在循环过程中删除,会不会出现问题,为什么
my:应该会出现问题,是因为java会对代码执行顺序进行优化,可能会报空指针?
answer:正向循环,在删除连续元素的时候,会漏删。比如一个list 是1、2、2、3、4,删除元素2,第2个2会被漏删。反向循环删除不会有问题。 https://www.cnblogs.com/Joe-Go/p/10419573.html
另一种解决方案是使用Iterator
的remove()
方法,因为Iterator
已经定位到了当前对象,不需要再用序号去搜索:
Iterator iter = subjects.iterator();
while (iter.hasNext()) {
String subject = iter.next();
if (subject.startsWith("6.")) {
iter.remove();
}
}
@transactional 注解在什么情况下会失效,为什么
my:修饰符不是 public 的情况下失效,或者调用入口没有加这个注解的情况下也会失效
answer:@Transactional
为 Spring 提供了控制事务管理的快捷手段。
失效场景:
- 修饰符不是
public
,在AOP 进行代理时,TransactionInterceptor
事务拦截器会检查修饰符,不是 public 的话就不会获取@Transactional
的配置信息。 - 注解属性
propagation
设置错误,设置成PROPAGATION_SUPPORTS
、PROPAGATION_NOT_SUPPORTED
、PROPAGATION_NEVER
中的一种。 - 注解属性
rollbackFor
设置错误 - 比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。因为只有当事务方法被当前类以外的代码调用时,才会由
Spring
生成的代理对象来管理。 - 默认遇到运行期例外( RuntimeException)会回滚,而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到非运行时异常时,需指定方式来让事务回滚。要想所有异常都回滚,要加上
@Transactional( rollbackFor={Exception.class,其它异常})
,如果让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)
。
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2); /** * A 插入字段为 2的数据 */
insert = cityInfoDictMapper.insert(cityInfoDict); /** * B 插入字段为 3的数据 */
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,事务不能正常回滚。
因为当ServiceB
中抛出了一个异常以后,ServiceB
标识当前事务需要rollback
。但是ServiceA
中由于你手动的捕获这个异常并进行处理,ServiceA
认为当前事务应该正常commit
。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException
异常。
spring
的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit
or rollback
,事务是否执行取决于是否抛出runtime异常
。如果抛出runtime exception
并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException()
,或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class)
,否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。
CMS 和 G1 的垃圾回收过程
my:CMS分为4个步骤,初次标记,并发标记,再次标记,和回收。其中初次标记和再次标记会 stop the world。G1就是将堆内存分成一个一个小块,好像可以单独回收老年代。
answer:CMS差不多,补充一下用的是标记-清除算法。 G1 是可以同时回收新生代和老年代,回收流程:
- 初始标记
- 并发标记
- 最终标记: 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
- 筛选回收: 首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
标记清除算法和标记整理算法的实现,优缺点
my:标记清除算法就是标记了要回收的对象以后,清理掉。标记整理算法会把所有活的对象移动到头部,然后再清理。标记清除算法容易产生内存碎片,标记整理算法性能比较差。
answer:对吧
双亲委派的过程以及优势
my:Java 有好几个创建类的容器,在创建类的时候,会不断把这个类往上抛,来决定用哪个来创建这个类。优势是不会出现自定义创建器创建了 java 原生类这种现象,保证了安全性。
answer:双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。优点和上面差不多。JDBC 会违反双亲委派机制。
线程池创建一个线程的过程
my:我记得是初始化的时候就创建完了。之后如果线程没达到最大线程数,又有新的任务进来,就会创建一个新的线程来执行这个任务吧
answer:
在 MyThreadPool 的内部,我们维护了一个阻塞队列 workQueue 和一组工作线程,工作线程的个数由构造函数中的 poolSize 来指定。用户通过调用 execute() 方法来提交 Runnable 任务,execute() 方法的内部实现仅仅是将任务加入到 workQueue 中。
连接池适合长连接还是短连接
my:长连接,如果在线程释放之前网络已经断开了,那这个线程就废了。如果没有检查机制的话,会导致线程池中的线程全部不可用。
answer:长连接是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接,而短连接则每次数据传输都需要客户端和服务器端建立一次连接。长连接的优势在于省去了每次数据传输连接建立的时间开销,能够大幅度提高数据传输的速度。