Java总结
基于对象的监视器(ObjectMonitor),我们在字节码文件里面可以看到,在同步方法执行前后,有两个指令,方法前monitorenter,方法后monitorexit;
ThreadLocal中文名叫线程变量,它底层维护了一个map,key就是当前的ThreadLocal对象(可以理解为当前执行该段代码的线程),value就是你set的值,这个map保证了各个线程的数据互不干扰;
从事态严重性来讲:穿透 > 雪崩 > 击穿
缓存穿透:请求数据库中根本就不存在的数据,既然数据库中都没有,缓存中更没有,导致每次请求直接怼到数据库;
缓存雪崩:缓存大面积失效;
缓存击穿:请求了很多缓存中没有但是数据库中真实存在的数据,一般是缓存过期导致,也导致请求直接怼到数据库;
解决办法:
缓存穿透:最简单的就是利用布隆过滤器过滤非法key,我写了个 demo来分析具体原理,请移步布隆过滤器原理
缓存雪崩:设置key过期时间的时候加上一个随机数,关键点就在于让key错开时间失效;
缓存击穿:延长热点数据过期时间,或者直接设置永远不过期;
BeanFactory和ApplicationContext的区别
- BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
- ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。
- BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化。ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化
可能存储的是整行数据,也有可能是主键的值。B+树的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引
停止-复制 标记-清除 标记-整理 分代收集算法
CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。
SQL的执行顺序:from---where--group by---having---select---order by
AQS理论的数据结构
AQS内部有3个对象,一个是state(用于计数器,类似gc的回收计数器),一个是线程标记(当前线程是谁加锁的),一个是阻塞队列。
CAS操作ABA问题:
如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。
53、什么是回表查询?如何避免?
对于mysql数据库的InnoDB引擎,它的非主键索引是非聚簇索引,索引文件和数据文件分开存储,索引文件B+树的叶节点只保存主键,在进行查询时,需要先去索引文件找到id,再根据id去数据文件查找具体数据,这种现象就叫回表查询
如何避免:索引覆盖
将查询sql中的字段添加到联合索引里面,只要保证查询语句里面的字段都在索引文件中,就无需进行回表查询;
Synchronized和Lock的区别
- 首先synchronized是java内置关键字在jvm层面,Lock是个java类。
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁,并且可以主动尝试去获取锁。
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁。
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了。
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
JVM锁优化和膨胀过程
- 自旋锁:自旋锁其实就是在拿锁时发现已经有线程拿了锁,自己如果去拿会阻塞自己,这个时候会选择进行一次忙循环尝试。也就是不停循环看是否能等到上个线程自己释放锁。自适应自旋锁指的是例如第一次设置最多自旋10次,结果在自旋的过程中成功获得了锁,那么下一次就可以设置成最多自旋20次。
- 锁粗化:虚拟机通过适当扩大加锁的范围以避免频繁的拿锁释放锁的过程。
- 锁消除:通过逃逸分析发现其实根本就没有别的线程产生竞争的可能(别的线程没有临界量的引用),或者同步块内进行的是原子操作,而“自作多情”地给自己加上了锁。有可能虚拟机会直接去掉这个锁。
- 偏向锁:在大多数的情况下,锁不仅不存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作。
- 轻量级锁:当存在超过一个线程在竞争同一个同步代码块时,会发生偏向锁的撤销。当前线程会尝试使用CAS来获取锁,当自旋超过指定次数(可以自定义)时仍然无法获得锁,此时锁会膨胀升级为重量级锁。
- 重量级锁:重量级锁依赖对象内部的monitor锁来实现,而monitor又依赖操作系统的MutexLock(互斥锁)。当系统检查到是重量级锁之后,会把等待想要获取锁的线程阻塞,被阻塞的线程不会消耗CPU,但是阻塞或者唤醒一个线程,都需要通过操作系统来实现。
Minor GC和Full GC触发条件
- Minor GC触发条件:当Eden区满时,触发Minor GC。
- Full GC触发条件:
- 调用System.gc时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 方法区空间不足
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
六、String s = new String("xyz");究竟产生了几个对象,从JVM角度谈谈?
答:一个或者两个;声明:s不是对象,不是对象,不是对象,s是指针引用
判断 :
if("xyz"在常量池中存在){
只会在堆中创建一个new String("xyz") ;一个对象
} else {
会现在常量池中创建一个“xyz”,然后在堆中创建一个new String("xyz");两个对象
}
查询在什么时候不走(预期中的)索引
- 模糊查询 %like
- 索引列参与计算,使用了函数
- 非最左前缀顺序
- where对null判断
- where不等于
- or操作有至少一个字段没有索引
- 需要回表的查询结果集过大(超过配置的范围)
缓存与数据库双写一致
采用延时双删策略
1. Spring框架优点?
答:方便解耦,简化开发,方便程序的测试,声明事物的支持,AOP编程的支持,方便集成各种优秀框架,降低Java EE API的使用难度,独立于各种应用服务器, 基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承,Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦, Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用,Spring的ORM和DAO提供了与第三方持久层框架的良好整合, 并简化了底层的数据库访问,Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部。
2. 谈谈你对spring IOC和DI的理解,它们有什么区别?
答:IOC控制反转,DI依赖注入。
IOC是一种编程思想,是一种概念,它可以控制不同的类对象去工作。
依赖注入就是打补丁一样,在你在做这个项目的时候需要用到别的技术,就可以用依赖注入来解决这个问题。
3. spring配置bean实例化有哪些方式?
https://www.zhihu.com/question/38597960
答:setter 方法,构造函数,静态工厂,实例工厂
1. 实例化Bean
2. 设置对象属性(依赖注入)
3. 注入Aware接口
4. BeanPostProcessor
5. InitializingBean与init-method
6. DisposableBean和destroy-method
bean 作用域 单例 初始化 实例化 销毁
5. Bean注入属性有哪几种方式?
答:@Controller:表明这是一个后台控制类,@Service,表明业务层。
@Autowired写在属性上,Ra....Maping写在方法上,@P...tory,规定范围,@Resourcesetter注入
构造方法注入
@Autowired和@Resource之间的区别
答:@Autowired注解属于Spring 默认按类型装配,默认情况下必须要求依赖对象必须存在,如果想允许null值 @Autowired(required=false),如果我们想使用名称进行装配
@Autowired()@Qualifier("baseDao")
@Resource 注解属于J2EE,默认按照名称进行装配,当找不到与名称匹配的bean时会按照类型进行装配
我们也可以指定name属性,如果指定了name属性则只会按照名称进行装配
2、在 Java 程序中怎么保证多线程的运行安全?
出现线程安全问题的原因一般都是三个原因:
1、 线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。
2、 缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题
3、 编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题
3、volatile 修饰符的有过什么实践?
一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。
4、Java中各种数据默认值
1、 Byte,short,int,long默认是都是0
2、 Boolean默认值是false
3、 Char类型的默认值是’’
4、 Float与double类型的默认是0.0
5、 对象类型的默认值是null
5、说说Java 垃圾回收机制
在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
6、有没有可能两个不相等的对象有有相同的 hashcode?
有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。
7、synchronized 和 Lock 有什么区别?
1、 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
2、 synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
3、 synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
4、 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
8、什么是Vector
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,访问它比访问ArrayList慢很多
ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。ArrayList的缺点是每个元素之间不能有间隔。
1.对于==,比较的是值是否相等
如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等:即两个变量的值是否相等
如果作用于引用类型的变量,则比较的是所指向的对象的地址:即对于引用型变量表示的是两个变量在栈中存储的地址是否相同,即栈中的内容是否相同
1.==是判断两个变量或实例是不是指向同一个内存空间
equals是判断两个变量或实例所指向的内存空间的值是不是相同
2.==是指对内存地址进行比较
equals()是对字符串的内容进行比较
3.==指引用是否相同
equals()指的是值是否相同
1、什么是ThreadPoolExecutor?
ThreadPoolExecutor就是线程池
ThreadPoolExecutor其实也是JAVA的一个类,我们一般通过Executors工厂类的方法,通过传入不同的参数,就可以构造出适用于不同应用场景下的ThreadPoolExecutor(线程池)
corePoolSize 核心线程数量
maximumPoolSize 最大线程数量
keepAliveTime 线程保持时间,N个时间单位
unit 时间单位(比如秒,分)
workQueue 阻塞队列
threadFactory 线程工厂
handler 线程池拒绝策略
2、invokedynamic 指令是干什么的?
Java 7 开始,新引入的字节码指令,可以实现一些动态类型语言的功能。Java 8 的 Lambda 表达式就是通过 invokedynamic 指令实现,使用方法句柄实现。
3、synchronized、volatile、CAS 比较
1、 synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
2、 volatile 提供多线程共享变量可见性和禁止指令重排序优化。
3、 CAS 是基于冲突检测的乐观锁(非阻塞)
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
5、被引用的对象就一定能存活吗?
不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。
6、列出一些你常见的运行时异常?
1、 ArithmeticException(算术异常)
2、 ClassCastException (类转换异常)
3、 IllegalArgumentException (非法参数异常)
4、 IndexOutOfBoundsException (下标越界异常)
5、 NullPointerException (空指针异常)
6、 SecurityException (安全异常)
7、Servlet生命周期内调用的方法过程?
1、 Init()
2、 Service()
3、 doGet或者doPost
4、 destroy
8、阐述静态变量和实例变量的区别。
静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
9、类加载器双亲委派模型机制?
基本定义:
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器没有找到所需的类时,子加载器才会尝试去加载该类。
双亲委派机制:
1、 当 AppClassLoader 加载一个 class 时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。
2、 当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成。
3、 如果 BootStrapClassLoader 加载失败,会使用 ExtClassLoader 来尝试加载;
4、 若 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException。
双亲委派作用:
1、 通过带有优先级的层级关可以避免类的重复加载;
2、 保证 Java 程序安全稳定运行,Java 核心 API 定义类型不会被随意替换。
2、 可达性算法(引用链法)
该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。
在 Java 中可以作为 GC Roots 的对象有以下几种:
1、 虚拟机栈中引用的对象
2、 方法区类静态属性引用的对象
3、 方法区常量池引用的对象
4、 本地方法栈JNI引用的对象
4、怎么唤醒一个阻塞的线程
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
8、如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
1、 不会,在下一个垃圾回调周期中,这个对象将是被可回收的。
2、 也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。
5、 当统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间,则会触发full gc(这就可以从多个角度上看了)
6、 是不是频繁创建了大对象(也有可能eden区设置过小)(大对象直接分配在老年代中,导致老年代空间不足--->从而频繁gc)
7、 是不是老年代的空间设置过小了(Minor GC几个对象就大于老年代的剩余空间了)
10、Java 中如何将字符串转换为整数?
String s="123";
int i;
第一种方法:i=Integer.parseInt(s);
第二种方法:i=Integer.valueOf(s).intValue();
内存泄漏的原因很简单:
1、 对象是可达的(一直被引用)
2、 但是对象不会被使用
1、 内存泄露导致堆栈内存不断增大,从而引发内存溢出。
2、 大量的jar,class文件加载,装载类的空间不够,溢出
3、 操作大量的对象导致堆内存空间已经用满了,溢出
4、 nio直接操作内存,内存过大导致溢出
解决:
1、 查看程序是否存在内存泄漏的问题
2、 设置参数加大空间
3、 代码中是否存在死循环或循环产生过多重复的对象实体、
4、 查看是否使用了nio直接操作内存。
3、线程与进程的区别?
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程。
公共静态不可变(public static final )变量也就是我们所说的编译期常量
9、运行时栈帧包含哪些结构?
1、 局部变量表
2、 操作数栈
3、 动态连接
4、 返回地址
5、 附加信息
3、什么是TreeMap
1、 TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
2、 TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
3、 TreeMap是线程非同步的。
2、Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)?
SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。SessionFactory一般只会在启动的时候构建。对于应用程序,最好将SessionFactory通过单例模式进行封装以便于访问。Session是一个轻量级非线程安全的对象(线程间不能共享session),它表示与数据库进行交互的一个工作单元。Session是由SessionFactory创建的,在任务完成之后它会被关闭。Session是持久层服务对外提供的主要接口。Session会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的session,可以使用ThreadLocal将session和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。
做一个形象的比喻:
1、 并发 = 俩个人用一台电脑。
2、 并行 = 俩个人分配了俩台电脑。
3、 串行 = 俩个人排队使用一台电脑。
3、Java会存在内存泄漏吗?请简单描述。
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除
但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
4、生产环境服务器变慢,如何诊断处理?
1、 使用 top 指令,服务器中 CPU 和 内存的使用情况,-H 可以按 CPU 使用率降序,-M 内存使用率降序。排除其他进程占用过高的硬件资源,对 Java 服务造成影响。
2、 如果发现 CPU 使用过高,可以使用 top 指令查出 JVM 中占用 CPU 过高的线程,通过 jstack 找到对应的线程代码调用,排查出问题代码。
3、 如果发现内存使用率比较高,可以 dump 出 JVM 堆内存,然后借助 MAT 进行分析,查出大对象或者占用最多的对象来自哪里,为什么会长时间占用这么多;如果 dump 出的堆内存文件正常,此时可以考虑堆外内存被大量使用导致出现问题,需要借助操作系统指令 pmap 查出进程的内存分配情况、gdb dump 出具体内存信息、perf 查看本地函数调用等。
4、 如果 CPU 和 内存使用率都很正常,那就需要进一步开启 GC 日志,分析用户线程暂停的时间、各部分内存区域 GC 次数和时间等指标,可以借助 jstat 或可视化工具 GCeasy 等,如果问题出在 GC 上面的话,考虑是否是内存不够、根据垃圾对象的特点进行参数调优、使用更适合的垃圾收集器;分析 jstack 出来的各个线程状态。如果问题实在比较隐蔽,考虑是否可以开启 jmx,使用 visualmv 等可视化工具远程监控与分析。
从图中可以看出,CMS 收集器的工作过程可以分为 4 个阶段:
1、 初始标记(CMS initial mark)阶段
2、 并发标记(CMS concurrent mark)阶段
3、 重新标记(CMS remark)阶段
4、 并发清除((CMS concurrent sweep)阶段
使用算法:复制+标记清除
G1 垃圾收集器
特点: 主要步骤:初始标记,并发标记,重新标记,复制清除。
使用算法:复制 + 标记整理
7、Log4j日志有几个级别?
由低到高:debug、info、wran、error
10、Java 中怎么获取一份线程 dump 文件?
在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。在 Windows 下,你可以按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。如果你使用Tomcat。
2、能否使用任何类作为 Map 的 key?
可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:
1、 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。
2、 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。
3、 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。
4、 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。
6、Java中如何实现序列化,有什么意义?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。
要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(可以参考第29题)。
7、单例模式使用注意事项:
1、 使用时不能用反射模式创建单例,否则会实例化一个新的对象
2、 使用懒单例模式时注意线程安全问题
3、 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
9、创建线程的有哪些方式?
1、 继承Thread类创建线程类
2、 通过Runnable接口创建线程类
3、 通过Callable和Future创建线程
4、 通过线程池创建
6、JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代?
当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。
2、ArrayList 和 LinkedList 的区别是什么?
1、 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
2、 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3、 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
4、 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
5、 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
6、 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
7、 LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点
8、接口和抽象类有什么区别?
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。 构造函数:抽象类可以有构造函数;接口不能有。 main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
10、ConcurrentHashMap 和 Hashtable 的区别?
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
底层数据结构:
JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式:
1、 在JDK1.7的时候,ConcurrentHashMap(分段锁对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
2、 Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
10、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
1、 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
2、 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
3、 minor gc后,survivor仍然放不下,则放到老年代
4、 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代
8、你所知道网络协议有那些?
1、 HTTP:超文本传输协议
2、 FTP:文件传输协议
3、 SMPT:简单邮件协议
4、 TELNET:远程终端协议
5、 POP3:邮件读取协议
GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
72.什么是 XSS 攻击,如何避免?
XSS 攻击:即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其他网站等。预防 XSS 的核心是必须对输入的数据做过滤处理。
73.什么是 CSRF 攻击,如何避免?
CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。防御手段: