0,基本数据类型:
byte:Java中最小的数据类型,在内存中占8位(bit),即1个字节,取值范围-128~127,默认值0 short:短整型,在内存中占16位,即2个字节,取值范围-32768~32717,默认值0 int:整型,用于存储整数,在内在中占32位,即4个字节,取值范围-2147483648~2147483647,默认值0 long:长整型,在内存中占64位,即8个字节-2^63~2^63-1,默认值0L float:浮点型,在内存中占32位,即4个字节,用于存储带小数点的数字(与double的区别在于float类型有效小数点只有6~7位),默认值0 double:双精度浮点型,用于存储带有小数点的数字,在内存中占64位,即8个字节,默认值0 char:字符型,用于存储单个字符,占16位,即2个字节,取值范围0~65535,默认值为空 boolean:布尔类型,占1个字节,用于判断真或假(仅有两个值,即true、false),默认值false
1,Java的引用类型: 强引用、弱引用、软引用、虚引用 1,强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 2,如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 3,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 4, “虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中
1,WeakReference如字面意思,弱引用, 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收 2,如果一个对象只具有软引用(SoftReference),则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存
3,ThreadLocal什么时候出现内存泄漏?ThreadLocal里面为啥使用了WeakReference? Thread实例为每个ThreadLocal对象维护了一个副本,这个副本数据存放在ThreadLocalMap里面,因此才做到线程间的数据不共享。 <1>当一个ThreadLocal实例被直接赋值为null(没有调用set,remove),此时会出现内存泄漏,因为thread实例里面的ThreadLocalMap保存了ThreadLocal的引用,假设此时线程没有被销毁,因此在gc的时候并不能回收这部分空间,就是说出现了内存泄漏(ThreadLocal直接赋值为null的方式,无论使用强弱引用都无法解决内存泄漏的问题)。 <2>如果使用弱引用(实际是ThreadLocalMap的Entry类的key才使用弱引用,value没有使用,ThreadLocalMap里面放就是Entry弱引用,其封装了ThreadLocal),在ThreadLocal对象被赋值为null,会导致弱引用在gc的时候,Entry的key被回收并变成null,使用弱引用可以多一层保障:对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除(这些方法的内部对Entry的key为null的value数据进行清除)。
备注:ThreadLocal是一个类,当实例化一个ThreadLocal对象时,会在当前线程Thread创建一个ThreadLocalMap,这个ThreadLocalMap里面存放了Entry,Entry是由ThreadLocal(key)和value(实际的数据)构成。Entry的key是通过弱引用封装,如果ThreadLocal没有外部指向(即被赋值为null)时,那Entry的key在gc的时候就会被回收,当此线程的ThreadLocalMap被再次访问时,会自动删除以前Entry的key为null的value数据。
参考:blog.csdn.net/wudiyong22/…
4,内存溢出和内存泄漏的区别 内存溢出(Out Of Memory,OOM),就是内存不够用了,内存泄漏(Memory Leak),指的是申请的内存空间,自己没有去主动释放,gc也无法释放(如强引用),多次内存泄漏,就会导致内存溢 memory leak会最终会导致out of memory!
2,Arraylist初始容量为10,每次扩容1.5倍,原来元素拷贝过去,hashMap初始化容量是16,负载因子0.75,每次容量达到(0.75*上次容量)开始扩容2倍
3,线程池核心线程大小设置,机器内核数量,qps,相应时间关系 <1>如果是计算密集型的服务,由于cpu处理效率非常高,核心线程一般设置为内核N+1(1为等待cpu的时间片) <2>如果是io耗时较高的服务,一般设置为(qps*99线)/1000,其中99线为毫秒
4,简述线程池的实现:线程池把每个提交到线程池的任务封装成一个worker(并且实现了Runnable接口),当第一批任务到达的时候(corePool还没到最大值),此时new出的线程开始执行任务,执行完,并且去消费队列,如果coreSize满了,此时队列就有值了,这时候就会消费队列里面的任务了,实际上是利用阻塞队列的take方法维持核心线程的存活,如果队列满了,就会创建新线程,直至达到maxSizePool,在消费队列中的任务数据的同时,如果线程在keepAlive时间范围内获取不到队列数据,就会释放最大线程,是通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)控制非核心线程的存活,如果从队列获取不到数据,就从worker集合删除该线程。
5,信号量的使用:把信号量比作资源,允许多线程去使用资源,但是只能允许部分线程使用。semaphore构造方法初始化资源大小,semaphore.acquire()获取资源,semaphore.release()释放资源。 CountDownLatch和Semaphore底层实现都是基于AbstractQueuedSynchronizer,CountDownLatch和Semaphore属于典型的共享锁。CyclicBarrier用来给多个线程之间进行互相等待其他所有线程而设计的(并且实现了可重用机制,每次计数到0,自动重新设置为起始值),而CountDownLatch是给一个起"调度"其它一组线程用的,这个线程关键职责就是等待进行其他工作线程返回。
备注:CountDownLatch.await阻塞主线程,CountDownLatch.countDown计数变量递减,递减到0会唤醒主线程,cyclicBarrier.await()当做栅栏,让一组线程都在栅栏之前完成任务,内部会做计数,只有变成0,才能让所有线程复活。 CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
使用场景:CountDownLatch是一个线程等待一组子线程执行完任务,再往下执行其他逻辑,cyclicBarrier是一组线程都达到一个临界值,再开始做新的任务。
6,在很多情况下,可能有多个线程需要访问数目很少的资源。假想在服务器上运行着若干个回答客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程? 解决方案:比如把资源放到阻塞队列,或者放到信号量里面。
7,ConcurrentHashMap的原理 1.6和1.7的实现:分为了16个segement,每个segement内部再次实现一次hashmap,因此查找一个数据需要两次hash(先找segement,再找segement里面的hash位置),put操作是在segement维度使用了reentrantlock,get是先找到segement,再查找数据,找到就对整个segement加锁。size方法是比较两次结果,如果不相等,就对每个segement加锁,重新计算(为什么要比较两次?在两次比较的过程中,被修改的概率很小,如果要加锁,就会导致整个map锁大量竞争(读多写少的时候),不如一开始不用锁的方式进行比较)。 1.8:不再使用segement,直接使用node数组+链表,当链表长度达到8,会升级为红黑树。put操作是使用了synchronize对当前链表加锁,get是使用Unsafe.getObjectVolatile获取最新的共享内存值(不加锁)。
9,Object的notify 和 notifyAll的区别 notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
notify只是唤醒正在等待的线程,至于什么时候开始竞争,取决于当前线程什么时候释放。(ReentrantLock对应的condition.signal方法也是如此)
10,可重入锁(ReentrantLock)的使用场景:当前线程内部逻辑进行递归调用
11,synchronized(独占锁),多线程使用,结合object的wait、notify(notifyAll)使用时注意的问题,调用wait,是释放当前线程持有某个对象的锁,让给其它线程竞争,并且由它们通知回调。 备注:使用wait、notify(notifyAll)方法前提必须是当前线程持有锁,也就是说必须在synchronized模块内使用 synchronized的锁标记存放在Java对象头的Mark Word中,同步代码块采用monitorenter、monitorexit指令(c++层面)显式的实现。
12,ReentrantLock(独占锁),多线程使用,结合Condition(condition = myLock.newCondition()),condition.await()和signal、signalAll()通知其它线程进行锁的竞争。 备注:1,使用await、signal(signalAll)方法前提必须是当前线程持有锁(也就是说一个线程不能释放别的线程持有的锁) 2,reentrantlock的lock方法如果获取不到锁,会被阻塞,tryLock获取不到,立刻返回false,tryLock(long time, TimeUnit unit)是对获取锁加上时间控制 3,condition.await(),将一个线程的锁交出,当前线程进入挂起状态(cpu时间片交出),当前线程放入等待锁的双向队列(AQS)里面,这个线程同时也被另外一个condition队列维护,condition.signal()调用 时,将双向队列中的线程设置为可抢锁状态,condition队列的头结点删除此线程数据。 4,condition.await(),是由用户的其它线程唤醒,condition.await(time),这是由内核在指定时间后去帮你唤醒的
13,静态代理和动态代理区别 静态不够灵活,需要针对每个被代理类的接口都对应开发一个代理类的接口,代码维护成本比较高。 14,动态代理实现的两种方式和区别 java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)
15,CGlib比JDK代理快?
(1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类 的子类。 (2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
16,Java 序列化做了哪些事情 Java的序列化算法一般会按步骤做如下事情: ◆将对象实例相关的类元数据输出。 ◆递归地输出类的超类描述直到不再有超类。 ◆类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。 ◆从上至下递归输出实例的数据
17,简述公平锁和非公平锁的实现。 Reentrantlock支持公平和非公平模式,实现锁的最基础组件类是:内部类NonfairSync和FairSync,外部AbstractQueuedSynchronizer(抽象队列同步器,AQS),公平锁和非公平锁在获取锁时都尝试性去获取,当获取失败才进入有序等待队列中(先进先出的双向链表),并且这些线程会被挂起(让出cpu时间片),公平锁在获取锁时,会判断当前线程是否是队列头结点线程(hasQueuedPredecessors),如果是头结点才有权拿到锁。非公平锁在获取锁时,是没在队列中的线程和队列的头结点竞争(即获取锁时,不对线程是否是头结点线程做限制)。当一个锁被释放时,它会去唤醒等待队列中的头结点,因此才出现新来线程和头结点竞争。
简单理解:公平是按顺序加锁,非公平是不保证按顺序加锁(实际上是外部线程和队列中线程竞争),处于阻塞状态的线程必须依赖别的线程释放锁,才能被唤醒去获取锁。
参考:cloud.tencent.com/developer/a…
18,AbstractQueuedSynchronizer为什么使用双向队列? aqs为什么使用双向队列(即双向链表)的原因,因为新进入阻塞状态的线程要存入尾部节点,头结点保存了尾部节点指针,这样避免了每次尾插法都要顺序遍历一次,直接根据头结点中的尾指针就可以插入了,提高了入队效率。 在移除头结点时,下一个节点升级为head节点时能快速与尾节点关联起来。
19,读写锁,ReentrantReadWriteLock会使用两把锁来解决问题,一个读锁,一个写锁 ReentrantReadWriteLock 的核心是由一个基于AQS的同步器 Sync 构成,然后由其扩展出 ReadLock (共享锁), WriteLock (排它锁)所组成 线程进入读锁的前提条件: 没有其他线程的写锁, 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
复制代码
参考:blog.csdn.net/yupi1057/ar…
20,读写锁的使用场景:读多写少,使用此类锁同步机制则可以提高并发量(www.jianshu.com/p/9f98299a1…
21,锁降级,指的是写锁降级为读锁,实际是持有一个写锁没释放,再去申请一个读锁,再释放写锁,保留读锁,使用场景:如果当前线程不获取读锁而直接释放写锁,假设此刻另一个线程(T)获取了写锁并修改了数据,那么当前线程是无法感知线程T的数据更新,ReentrantReadWriteLock不支持锁升级
20,为啥覆盖equals 时要重写hashcode?如何重写? 举个例子,如果重写equals方法,让对象相等,但是如果不重写hashcode,会导致使用Map结构存储数据时,会导致相等对象存储多个,也就是分布在多个hash槽 重写参考:相同属性组成相同的hashcode
4,mq的好处:广播式解耦合,异步化处理一下长耗时逻辑,流量削峰(上下游推送的流量很大)。
5,spring mvc一次请求经历了什么(SpringMVC核心处理流程)
DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器
HandlerMapping处理器映射器,根据请求路径找到相应的HandlerAdapter处理器适配器(处理器适配器就是那些拦截器或Controller)
HandlerAdapter处理器适配器,处理一些功能请求,也就是真正的执行业务逻辑,返回一个ModelAndView对象(包括模型数据、逻辑视图名)
ViewResolver视图解析器,先根据ModelAndView中设置的View解析具体视图
然后再将Model模型中的数据渲染到View上
这些过程都是以DispatcherServlet为中轴线进行的。
getHandler(HandlerMapping),获取页面处理器,通俗点就是获取由哪个Controller来执行,包含方法信息以及方法参数等信息。 getHandlerAdapter(HandlerAdapter),获取HandlerAdapter,它包含一个handle方法,负责调用真实的页面处理器进行请求处理并返回一个ModelAndView。HandlerAdpter里面有一些常见的处理,比如消息转移,参数处理等,详见此图:里面的argumentResolvers可以用来处理请求的参数,messageConverts是作消息转换等等
3,Struts和spring mvc区别。 一、拦截机制的不同 Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter吧request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。 SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。 Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。 二、底层框架的不同 Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后销毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。 三、性能方面 Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截(更加轻量),有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。
4,StackOverflowError和OutofMemoryError如何发生,怎么模拟(StackOverflowError栈溢出,如方法的递归调用,OutofMemoryError内存耗尽,比如不断创建线程分配内存) 5,jvm已经发展处三种比较成熟的垃圾收集算法:1.标记-清除算法;2.复制算法;3.标记-整理算法(标记-压缩法);4.分代收集算法,参考:www.cnblogs.com/nantang/p/5… 6,Jvm启动参数 一般用到最多的是 -Xms512m 设置JVM促使内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。 -Xmx512m ,设置JVM最大可用内存为512M。 -Xmn200m:设置年轻代大小为200M。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8(young占30%左右) 7,gc,垃圾回收算法,常用的是分代收集算法(新生代和老年代分开处理),分代收集算法是复制算法和标记清除法的二者整合 8,full gc Full GC 如果某个(些)对象(原来在内存中存活的对象或者新创建的对象)由于以上原因需要被移动到老年代中,而老年代中没有足够空间容纳这个(些)对象,那么会触发一次Full GC,Full GC会对整个Heap进行一次GC,如果Full GC后还有无法给新创建的对象分配内存,或者无法移动那些需要进入老年代中的对象,那么JVM抛出OutOfMemoryError
简单理解gc 对象在Eden Space创建,当Eden Space满了的时候,gc就把所有在Eden Space中的对象扫描一次,把所有有效的对象复制到第一个Survivor Space,同时把无效的对象所占用的空间释放。当Eden Space再次变满了的时候,就启动移动程序把Eden Space中有效的对象复制到第二个Survivor Space,同时,也将第一个Survivor Space中的有效对象复制到第二个Survivor Space。如果填充到第二个Survivor Space中的有效对象被第一个Survivor Space或Eden Space中的对象引用,那么这些对象就是长期存在的,此时这些对象将被复制到Permanent Generation。若垃圾收集器依据这种小幅度的调整收集不能腾出足够的空间,就会运行Full GC,此时JVM GC停止所有在堆中运行的线程并执行清除动作。
绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间,因此在其上分配内存极快; 最初一次,当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的); 下次Eden区满了,再执行一次Minor GC,将消亡的对象清理掉,将存活的对象复制到Survivor1中,然后清空Eden区; 将Survivor0中消亡的对象清理掉,将其中可以晋级的对象晋级到Old区,将存活的对象也复制到Survivor1区,然后清空Survivor0区; 当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制,大于该值进入老年代,但这只是个最大值,并不代表一定是这个值)之后,仍然存活的对象(其实只有一小部分,比如,我们自己定义的对象),将被复制到老年代 参考:www.cnblogs.com/bonelee/p/8…
9,cms: PS MarkSweep:老年代收集器,是一个可以并行标记和清理垃圾的回收器,无整理,使用的是空闲列表的方式,就像一个多线程版本的Serial Old收集器 能做到老年代提前GC的垃圾回收器有CMS收集器,但它的搭配伙伴是ParNew,由ParNew来执行新生代垃圾回收。
CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见后面),当用户线程内存不足时,采用备用方案Serial Old收集
www.cnblogs.com/zhguang/p/3… www.iteye.com/topic/11194…
10,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
11,java中的sleep()和wait()的区别 sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 在调用sleep()方法的过程中,线程不会释放对象锁。 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
12,重排序 编译期重排序的典型就是通过调整指令顺序,在不改变程序语义的前提下,尽可能减少寄存器的读取、存储次数,充分复用寄存器的存储值。 13,happens-before原则规则: 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作(有依赖关系的逻辑执行先后顺序是明确知道的); 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作; volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行; 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始 14,jmm: Java内存模型是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的,www.cnblogs.com/lewis0077/p… 堆,方法区,本地方法区,方法栈,程序计数器。
15,Java中notify和notifyAll的区别 Java object提供了两个方法notify和notifyAll来唤醒在某些条件下等待的线程,你可以使用它们中的任何一个,但是Java中的notify和notifyAll之间存在细微差别,这使得它成为Java中流行的多线程面试问题之一。当你调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定,这就是为什么在循环上调用wait,因为如果多个线程被唤醒,那么线程是将获得锁定将首先执行,它可能会重置等待条件,这将迫使后续线程等待。因此,notify和notifyAll之间的关键区别在于notify()只会唤醒一个线程,而notifyAll方法将唤醒所有线程。
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
备注:condition的signal方法唤醒队列头部的。
16,线程共包括以下5种状态。
- 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
- 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。 (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 参考:www.cnblogs.com/happy-coder…
17,synchronize保证了同步代码块内的共享变量可见性,volatile保证声明的共享变量可见性 当一个变量定义为 volatile 之后,将具备两种特性: 1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。
Volatile底层实现:Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大
blog.csdn.net/u012998254/…
18,synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可), synchronized是java关键字,lock(reentrantlock)是基于cas乐观锁机制实现。 1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现; 2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁; 3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断; 4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。 5)Lock可以提高多个线程进行读操作的效率。 6) Lock可以调用await方法让出锁资源,同时可以调用notify通知其它线程重新获取锁资源,这是synchronized不具备的 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择
19,公平锁指的是线程获取锁的顺序是按照加锁顺序来的,而非公平锁指的是抢锁机制,先lock的线程不一定先获得锁。 NonfairSync和FairSync主要就是在获取锁的方式上不同,公平锁是按顺序去获取,而非公平锁是抢占式的获取,lock的时候先去尝试修改state变量,如果抢占成功,则获取到锁。 reentrantlock的实现基于AQS(AbstractQueuedSynchronizer)实现,内部通过自旋的方式完成锁的调度,锁的实现是基于cas(compareAndSet),参考:www.jianshu.com/p/fadac70b2…
20,ReentrantLock.lockInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException
lock 与 lockInterruptibly比较区别在于: lock 优先考虑获取锁,待获取锁成功后,才响应中断。(此线程在运行中, 不会收到提醒,但是此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并作出处理) lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。
可重入特性是在递归调用场景下,防止被调用过程阻塞 21,自定义锁:blog.csdn.net/u012545728/… 22,java中的基本数据类型一定存储在栈中吗?,这句话肯定是错误的。 基本数据类型是放在栈中还是放在堆中,这取决于基本类型在何处声明,下面对数据类型在内存中的存储问题来解释一下: 一:在方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因 在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。 (1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中 (2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。 二:在类中声明的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。 同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量 (1)当声明的是基本类型的变量其变量名及其值放在堆内存中的 (2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中
1,为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用 这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
2,wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别 wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器
23,java中用到的线程调度算法是什么 抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行
24,java异常
Throwable:有两个重要的子类:Exception(异常)和Error(错误),两者都包含了大量的异常处理类。
1、Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。
这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。
2、Exception(异常):程序本身可以捕获并且可以处理的异常。 Exception这种异常又分为两类:运行时异常和编译异常。
1、运行时异常(不受检异常,uncheck):RuntimeException类极其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
2、编译异常(受检异常,check):Exception中除RuntimeException极其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
25,hashMap:threshold(进行扩容时所需要的判断基础,初始化为16),loadfactor是0.75,每次扩容是按照2倍扩容,扩容后threshold=table.length* loadfactor www.cnblogs.com/chengxiao/p…
26,ConcurrentHashMap则采用了不同的线程安全保证方式——分段锁。它不像Hashtable那样将整个table锁住而是将数组元素分段加锁,如果线程1访问的元素在分段segment1,而线程2访问的元素在分段segment2,则它们互不影响可以同时进行操作。如何合理的进行分段就是其关键问题 a, ConcurrentHashMap在数据查找的时候,为什么要两次hash?第一次hash是确定segement的位置,第二次hash是确定segement中链表的位置。 b,ConcurrentHashMap扩容,只扩容segement中的数组大小。
27,自旋锁即是某一线程去尝试获取某个锁时,如果该锁已经被其他线程占用的话,此线程将不断循环检查该锁是否被释放,而不是让此线程挂起或睡眠。它属于为了保证共享资源而提出的一种锁机制,与互斥锁类似,保证了公共资源在任意时刻最多只能由一条线程获取使用,不同的是互斥锁在获取锁失败后将进入睡眠或阻塞状态
28,Comparable和Comparator区别比较 Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。 Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。 两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。 29,如果设置线程池的大小,目前业务都是io密集型的,耗时在io,因此线程池可以设置大一些,接受更多的网络请求,常见设置是99.9线耗时(秒)*qps,即是每个线程每秒处理的请求
30,减少fullgc次数,原理上把大对象移到堆外,减少对堆空间的占用,堆空间满的时候才会触发fullgc,只有堆空间被写满的次数少了,才能减少fullgc 31,java所有的gc都会stop-the-world,包括young gc和old gc。 32,参考:www.cnblogs.com/yang-hao/p/… Minor GC触发条件 1、eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC 注:新生代分为三个区域,eden space, from space, to space。默认比例是8:1:1。在MinorGC时,会把存活的对象复制到to space区域,如果to space区域不够,则利用担保机制进入老年代区域。 对eden space, from space, to space的理解:每次分配eden space空间,如果不够,则小于 to space大小的对象复制到 to space,然后to space和from space换位置,所以我们看到的to space一直是空的。
Full GC触发条件 老生代空间不够分配新的内存(old区不足以存放从young区复制过来的对象)
33,EHCache(Terrcotta BigMemory)的 off-heap(堆外内存,操作系统层面的堆外内存,不受gc影响)将你的对象从堆中脱离出来序列化,然后存储在一大块内存中,这就像它存储到磁盘上上一样,但它仍然在RAM中 34,Java缓存类型 2.1 堆内缓存 使用Java堆内存来存储对象。可以使用Guava Cache、Ehcache、MapDB实现。 优点:使用堆缓存的好处是没有序列化/反序列化,是最快的缓存; 缺点:很明显,当缓存的数据量很大时, GC暂停时间会变长,存储容量受限于堆空间大小;一般通过软引用/弱引用来存储缓存对象,即当堆内存不足时,可以强制回收这部分内存释放堆内存空间。一般使用堆缓存存储较热的数据。 2.2 堆外缓存 即缓存数据存储在堆外内存。可以使用Ehcache 3.x、MapDB实现。
优点:可以减少GC暂停时间(堆对象转移到堆外,GC扫描和移动的对象变少了),可以支持更大的缓存空间(只受机器内存大小限制,不受堆空间的影响)。 缺点:读取数据时需要序列化/反序列化,会比堆缓存慢很多
34,java的本地缓存类型 1,分为堆内和堆内两种类型的缓存数据,常见的堆内缓存:如hashMap,或者guavacache,它们都收到jvm gc的影响。堆外内存有两种类型:受jvm gc影响的堆外缓存和操作系统层面的堆外缓存,受gc影响的堆外内存可以用过nio的DirectByteBuffer申请内存空间,不受gc影响的堆外内存可以通过ehcache(堆外,堆内,文件模式都支持)管理 2,DirectByteBuffer(调用unsafe的native方法申请分配内存)申请的内存空间是堆外内存,这块内存的地址会被Cleaner持有(ByteBuffer.allocateDirect,分配内存时,将这块的内存地址给Cleaner),在gc的时候,如果这块内存空间出现无引用之后,就会被释放,也就是说这块内存空间是受到gc影响的
Cleaner类继承自PhantomReference< Object>在此处保留Cleaner对象的虚引用。此类中还包含一个静态DirectByteBuffer引用队列用于得知那些虚引用所指向的对象已回收,这是一个很棒的设计因为jvm不知道堆外内存的使用情况,通过DirectByteBuffer对象的回收来间接控制堆外内存的回收。 参考:blog.csdn.net/Big_Blogger…
35,类加载器(www.importnew.com/6581.html,h… 类加载机制,简单理解就是委托、可见性和单一性。
<1>Bootstrap类加载器负责加载rt.jar中的JDK类文件,它是所有类加载器的父加载器
<2>Extension将加载类的请求先委托给它的父加载器,也就是Bootstrap,如果没有成功加载的话,再从jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下加载类。Extension加载器由sun.misc.Launcher$ExtClassLoader实现。这为引入除Java核心类以外的新功能提供了一个标准机制
<3>System类加载器(又叫作Application类加载器),它负责从classpath环境变量中加载某些应用相关的类,Application类加载器是Extension类加载器的子加载器。通过sun.misc.Launcher$AppClassLoader实现 <4>自定义加载器,MyClassLoader extends ClassLoader,一般只需要重写findClass(从别的地方获取类文件),最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
Java类加载器的作用就是在运行时加载类。Java类加载器基于三个机制:委托、可见性和单一性。委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。正确理解类加载器能够帮你解决NoClassDefFoundError和java.lang.ClassNotFoundException,因为它们和类的加载相关。类加载器通常也是比较高级的Java面试中的重要考题,Java类加载器和工作原理以及classpath如何运作的经常被问到。Java面试题中也经常出现“一个类是否能被两个不同类加载器加载”这样的问题。这篇教程中,我们将学到类加载器是什么,它的工作原理以及一些关于类加载器的知识点。
36, (1)阿里的面试官问我,可以不可以自己写个String类
答案:不可以,因为 根据类加载的双亲委派机制,会去加载父类,父类发现冲突了String就不再加载了;
(2)能否在加载类的时候,对类的字节码进行修改
答案:可以,使用Java探针技术,可以参考:Java探针-Java Agent技术-阿里面试题
(3)如何实现热部署:自定义classLoader就可以了,热部署之前,销毁(即gc回收掉)之前部署的classLoader
37,class何时触发初始化
为一个类型创建一个新的对象实例时(比如new、反射、序列化) 调用一个类型的静态方法时(即在字节码中执行invokestatic指令) 调用一个类型或接口的静态字段,或者对这些静态字段执行赋值操作时(即在字节码中,执行getstatic或者putstatic指令),不过用final修饰的静态字段除外,它被初始化为一个编译时常量表达式 调用JavaAPI中的反射方法时(比如调用java.lang.Class中的方法,或者java.lang.reflect包中其他类的方法) 初始化一个类的派生类时(Java虚拟机规范明确要求初始化一个类时,它的超类必须提前完成初始化操作,接口例外) JVM启动包含main方法的启动类时。
38,数据库连接池简单实现,参考:blog.csdn.net/moakun/arti… public class SimplePoolDemo { //创建一个连接池 private static LinkedList pool = new LinkedList();
//初始化10个连接
static{
try {
for (int i = 0; i < 10; i++) {
Connection conn = DBUtils.getConnection();//得到一个连接
pool.add(conn);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("数据库连接失败,请检查配置");
}
}
//从池中获取一个连接
public static Connection getConnectionFromPool(){
return pool.removeFirst();//移除一个连接对象
}
//释放资源
public static void release(Connection conn){
pool.addLast(conn);
}
复制代码
}
C3p0,dbcp,druid的区别: c3p0有自动回收空闲连接功能,dbcp没有自动的去回收空闲连接的功能
C3P0提供最大空闲时间,DBCP提供最大连数。 Druid具备的功能更加丰富,还具备sql注入的语法校验。 参考:mp.weixin.qq.com/s?__biz=MzI…
Dbcp源码解读:www.jianshu.com/p/f430c1d13… Dbcp源码读后总结: dbcp有个定时器(基于定时器实现)去保证连接池维持一个称作minIdle状态(最小闲置状态,如果是无并发场景,minIdle为1就够了),大部分情况下都超过minIdle,因为数据库访问频率都很高的,当访问量增加的时候,会创建连接直至最大值为maxActive,如果连接数超过maxActive,请求会被阻塞(分为永久阻塞和限时阻塞,可配置最长等待时间maxWait)。当qps降下来,连接数不需要那么多的时候,会保持连接数在maxIdle(最大闲置数),多余的会被销毁(如果maxIdle==maxActive,就不会出现销毁了,因此生产环境一般配置maxIdle和maxActive相同)。
欢迎打赏