网站广告如何做,html5做网页网站,企业网站首页布局设计,网站开发现状及研究意义1.你是怎样理解面向对象的
面向对象是利于语言对现实事物进行抽象。面向对象具有以下四大特征#xff1a;
#xff08;1#xff09;继承#xff1a;继承是从已有类得到继承信息创建新类的过程
#xff08;2#xff09;封装#xff1a;通常认为封装是把数据和操作数据…1.你是怎样理解面向对象的面向对象是利于语言对现实事物进行抽象。面向对象具有以下四大特征1继承继承是从已有类得到继承信息创建新类的过程2封装通常认为封装是把数据和操作数据的方法绑定起来对数据的访问只能通过已定义的接口。3多态性多态性是指允许不同子类型的对象对同一消息作出不同的响应。4抽象抽象是将一类对象的共同特征总结出来构造类的过程包括数据抽象和行为抽象两方面。2. int和Integer有什么区别以及以下程序结果1Integer是int的包装类int则是java的一种基本数据类型2Integer变量必须实例化后才能使用而int变量不需要3Integer实际是对象的引用当new一个Integer时实际上是生成一个指针指向此对象而int则是直接存储数据值4Integer的默认值是nullint的默认值是05java在编译Integer i 100 ;时会翻译成为Integer i Integer.valueOf(100)。而java API中对Integer类型的valueOf的定义如下对于-128到127之间的数会进行缓存Integer i 127时会将127这个Integer对象进行缓存下次再写Integer j 127时就会直接从缓存中取就不会new了package com.atguigu.interview.chapter03;/*** author atguigu* since 2019/7/28*/publicclassTest01 {publicstaticvoidmain(String[] args){Integer a 127;Integer b 127;Integer c 128;Integer d 128;System.out.println(ab); //trueSystem.out.println(cd); //false}}3.和Equals区别1 如果比较的是基本数据类型那么比较的是变量的值如果比较的是引用数据类型那么比较的是地址值两个对象是否指向同一块内存equals如果没重写equals方法比较的是两个对象的地址值如果重写了equals方法后我们往往比较的是对象中的属性的内容equals方法是从Object类中继承的默认的实现就是使用3.4谈谈你对反射的理解1反射机制:所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。Java的反射机制的实现要借助于4个类classConstructorFieldMethod;其中class代表的时类对 象Constructor类的构造器对象Field类的属性对象Method类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。2Java反射的作用在Java运行时环境中对于任意一个类可以知道这个类有哪些属性和方法。对于任意一个对象可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射Reflection机制。3Java 反射机制提供功能在运行时判断任意一个对象所属的类。在运行时构造任意一个类的对象。在运行时判断任意一个类所具有的成员变量和方法。在运行时调用任意一个对象的方法篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho3.5ArrarList和LinkedList区别1ArrayList是实现了基于动态数组的数据结构LinkedList基于链表的数据结构。2对于随机访问get和setArrayList觉得优于LinkedList因为LinkedList要移动指针。3对于新增和删除操作add和removeLinkedList比较占优势因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据要移动插入点及之后的所有数据。3.6HashMap底层源码数据结构HashMap的底层结构在jdk1.7中由数组链表实现在jdk1.8中由数组链表红黑树实现以数组链表的结构为例。JDK1.8之前Put方法JDK1.8之后Put方法3.7 HashMap和HashTable区别1线程安全性不同HashMap是线程不安全的HashTable是线程安全的其中的方法是Synchronize的在多线程并发的情况下可以直接使用HashTabl但是使用HashMap时必须自己增加同步处理。2是否提供contains方法HashMap只有containsValue和containsKey方法HashTable有contains、containsKey和containsValue三个方法其中contains和containsValue方法功能相同。3key和value是否允许null值Hashtable中key和value都不允许出现null值。HashMap中null可以作为键这样的键只有一个可以有一个或多个键所对应的值为null。4数组初始化和扩容机制HashTable在不指定容量的情况下的默认容量为11而HashMap为16Hashtable不要求底层数组的容量一定要为2的整数次幂而HashMap则要求一定为2的整数次幂。Hashtable扩容时将容量变为原来的2倍加1而HashMap扩容时将容量变为原来的2倍。3.8TreeSet和HashSet区别HashSet是采用hash表来实现的。其中的元素没有按顺序排列add()、remove()以及contains()等方法都是复杂度为O(1)的方法。TreeSet是采用树结构实现(红黑树算法)。元素是按顺序进行排列但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法。它还提供了一些方法来处理排序的set如first(), last(), headSet(), tailSet()等等。3.9String buffer和String builder区别1StringBuffer 与 StringBuilder 中的方法和功能完全是等价的2只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰因此是线程安全的而 StringBuilder 没有这个修饰可以被认为是线程不安全的。3在单线程程序下StringBuilder效率更快因为它不需要加锁不具备多线程安全而StringBuffer则每次都需要判断锁效率相对更低3.11什么是Java 序列化如何实现Java 序列化序列化就是一种用来处理对象流的机制所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序 列 化 的 实 现 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 该 接 口 没 有 需 要 实 现 的 方 法 implements Serializable 只是为了标注该对象是可被序列化的然后使用一个输出流(如FileOutputStream)来构造一个ObjectOutputStream(对象流)对象接着使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态)要恢复的话则用输入流。3.12Object中有哪些方法1protected Object clone()---创建并返回此对象的一个副本。2boolean equals(Object obj)---指示某个其他对象是否与此对象“相等”。3protected void finalize()---当垃圾回收器确定不存在对该对象的更多引用时由对象的垃圾回收器调用此方法。4Class? extendsObject getClass()---返回一个对象的运行时类。5int hashCode()---返回该对象的哈希码值。6void notify()---唤醒在此对象监视器上等待的单个线程。7void notifyAll()---唤醒在此对象监视器上等待的所有线程。8String toString()---返回该对象的字符串表示。9void wait()---导致当前的线程等待直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。void wait(long timeout)---导致当前的线程等待直到其他线程调用此对象的 notify() 方法或 notifyAll()方法或者超过指定的时间量。void wait(long timeout, int nanos)---导致当前的线程等待直到其他线程调用此对象的 notify()3.13线程有几种状态产生的条件是什么新建状态(New) 线程对象被创建后就进入了新建状态。例如Thread thread new Thread()。2就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后其它线程调用了该对象的start()方法从而来启动该线程。例如thread.start()。处于就绪状态的线程随时可能被CPU调度执行。3运行状态(Running)线程获取CPU权限进行执行。需要注意的是线程只能从就绪状态进入到运行状态。4阻塞状态(Blocked)阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态。阻塞的情况分三种等待阻塞 -- 通过调用线程的wait()方法让线程等待某工作的完成。同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用)它会进入同步阻塞状态。其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时线程会进入到阻塞状态。当sleep()状态超时、join(等待线程终止或者超时、或者I/O处理完毕时线程重新转入就绪状态。5死亡状态(Dead)线程执行完了或者因异常退出了run()方法该线程结束生命周期。3.14产生死锁的基本条件产生死锁的原因1 因为系统资源不足。2 进程运行推进的顺序不合适。3 资源分配不当等。如果系统资源充足进程的资源请求都能够得到满足死锁出现的可能性就很低否则就会因争夺有限的资源而陷入死锁。其次进程运行推进顺序与速度不同也可能产生死锁。产生死锁的四个必要条件1 互斥条件一个资源每次只能被一个进程使用。2 请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放。3 不剥夺条件:进程已获得的资源在末使用完之前不能强行剥夺。4 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。这四个条件是死锁的必要条件只要系统发生死锁这些条件必然成立而只要上述条件之一不满足就不会发生死锁。死锁的解除与预防理解了死锁的原因尤其是产生死锁的四个必要条件就可以最大可能地避免、预防和解除死锁。所以在系统设计、进程调度等方面注意如何不让这四个必要条件成立如何确定资源的合理分配算法避免进程永久占据系统资源。此外也要防止进程在处于等待状态的情况下占用资源。因此对资源的分配要给予合理的规划。3.15什么是线程池如何使用线程池就是事先将多个线程对象放到一个容器中当使用的时候就不用 new 线程而是直接去池中拿线程即可节省了开辟子线程的时间提高的代码执行效率。在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。ExecutorService newCachedThreadPool Executors.newCachedThreadPool();ExecutorService newFixedThreadPool Executors.newFixedThreadPool(4);ScheduledExecutorService newScheduledThreadPool Executors.newScheduledThreadPool(4);ExecutorService newSingleThreadExecutor Executors.newSingleThreadExecutor();然后调用他们的 execute 方法即可。优点第一降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。第三提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。3.16Java自带有哪几种线程池1newCachedThreadPool创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。这种类型的线程池特点是工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务即如果工作线程空闲了指定的时间(默认为1分钟)则该工作线程将自动终止。终止后如果你又提交了新的任务则线程池重新创建一个工作线程。在使用CachedThreadPool时一定要注意控制任务的数量否则由于大量线程同时运行很有会造成系统瘫痪。2newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程如果工作线程数量达到线程池初始的最大数则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时即线程池中没有可运行任务时它不会释放工作线程还会占用一定的系统资源。3newSingleThreadExecutor创建一个单线程化的Executor即只创建唯一的工作者线程来执行任务它只会用唯一的工作线程来执行任务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束会有另一个取代它保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务并且在任意给定的时间不会有多个线程是活动的。4newScheduleThreadPool创建一个定长的线程池而且支持定时的以及周期性的任务执行。例如延迟3秒执行。3.17Java 中有几种类型的流3.18字节流如何转为字符流字节输入流转字符输入流通过 InputStreamReader 实现该类的构造函数可以传入 InputStream 对象。字节输出流转字符输出流通过OutputStreamWriter 实现该类的构造函数可以传入 OutputStream 对象。3.19请写出你最常见的5个RuntimeException1java.lang.NullPointerException 空指针异常出现原因调用了未经初始化的对象或者是不存在的对象。2java.lang.ClassNotFoundException 指定的类找不到出现原因类的名称和路径加载错误通常都是程序试图通过字符串来加载某个类时可能引发异常。3java.lang.NumberFormatException 字符串转换为数字异常出现原因字符型数据中包含非数字型字符。4java.lang.IndexOutOfBoundsException 数组角标越界异常常见于操作数组对象时发生。5java.lang.IllegalArgumentException 方法传递参数错误。6java.lang.ClassCastException 数据类型转换异常。3.20JDK和JRE有什么区别1JRE是Java运行时环境就是Java程序必须运行在JRE上面它就是执行Java程序的虚拟机。2JDK是包含JRE的它比JRE多了一些工具这些多出来的工具就是让我们程序员来开发Java程序的。3JDK包含JRE编译器和其他的工具(比如JavaDocJava调试器)可以让开发者开发、编译、执行Java应用程序4JDK面向开发者JRE面向程序使用者3.21两个对象的 hashCode()相同则 equals()也一定为 true对吗1两个对象equals相等则它们的hashcode必须相等反之则不一定。2两个对象相等则其hashcode一定相等反之不一定成立3.22final 在 java 中有什么作用final作为Java中的关键字可以用于三个地方。用于修饰类、类属性和类方法。特征凡是引用final关键字的地方皆不可修改(1)修饰类表示该类不能被继承(2)修饰方法表示方法不能被重写(3)修饰变量表示变量只能一次赋值以后值不能被修改常量。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho3.23Java 中的 Math.round(-1.5) 等于多少Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整3.24String 属于基础的数据类型吗在Java中数据类型分为引用类型和基本类型基本类型分为八种整型byteshortint,long浮点型floatdouble字符型charBoolean型booleanString不是基本的数据类型是final修饰的java类是引用类型。3.25 Java 中操作字符串都有哪些类它们之间有什么区别主要是一下三种String、StringBuffer、StringBuilder特别是在项目中。先来看一下这三种操作方式的区别String是不可变的对象对每次对String类型的改变时都会生成一个新的对象StringBuffer和StringBuilder是可以改变对象的。对于操作效率StringBuilder StringBuffer String对于线程安全StringBuffer 是线程安全可用于多线程StringBuilder 是非线程安全用于单线程不频繁的字符串操作使用 String。反之StringBuffer 和 StringBuilder 都优于String所以如果在项目中需要拼接字符串最好是采用StringBuffer 而非String.3.26String stri与 String strnew String(“i”)一样吗不一样因为内存的分配方式不一样。String stri的方式Java 虚拟机会将其分配到常量池中而 String strnew String(“i”) 则会被分到堆内存中。3.28String 类的常用方法都有那些indexOf() 返回指定字符得索引charAt() 返回指定索引处得字符repalce() 字符串替换trim() 去除字符串两端的空白split() 分割字符串 返回分割后的字符串数组getBytes() 返回字符串的byte类型数组length() 返回字符串的长度toLowerCase() 字符串转小写toUpperCase() 字符串转大写substring() 截取字符串equals() 字符串比较3.29抽象类必须要有抽象方法吗抽象类可以没有抽象方法但是如果你的一个类已经声明成了抽象类即使这个类中没有抽象方法它也不能再实例化即不能直接构造一个该类的对象。如果一个类中有了一个抽象方法那么这个类必须声明为抽象类否则编译通不过。3.30普通类和抽象类有哪些区别普通类不能包含抽象方法抽象类可以包含抽象方法。抽象类是不能被实例化的就是不能用new调出构造方法创建对象普通类可以直接实例化。如果一个类继承于抽象类则该子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法则必须将子类也定义为abstract类。3.31抽象类能使用 final 修饰吗不能定义抽象类就是让其他类继承的如果定义为 final 该类就不能被继承这样彼此就会产生矛盾所以 final 不能修饰抽象类如下图所示编辑器也会提示错误信息。3.32接口和抽象类有什么区别他们都不能实例化对象都可以包含抽象方法而且抽象方法必须被继承的类全部实现。区别1、抽象类和接口都不能直接实例化如果要实例化抽象类变量必须指向实现所有抽象方法的子类对象接口变量必须指向实现所有接口方法的类对象。2、抽象类要被子类继承接口要被类实现。3、接口只能做方法申明抽象类中可以做方法申明也可以做方法实现4、接口里定义的变量只能是公共的静态的常量抽象类中的变量是普通变量。5、抽象类里的抽象方法必须全部被子类所实现如果子类不能全部实现父类抽象方法那么该子类只能是抽象类。同样一个实现接口的时候如不能全部实现接口方法那么该类也只能为抽象类。6、抽象方法只能申明不能实现接口是设计的结果 抽象类是重构的结果7、抽象类里可以没有抽象方法8、如果一个类里有抽象方法那么这个类只能是抽象类9、抽象方法要被实现所以不能是静态的也不能是私有的。10、接口可继承接口并可多继承接口但类只能单根继承。3.33Java 中 IO 流分为几种按照流的流向分可以分为输入流和输出流按照操作单元划分可以划分为字节流和字符流按照流的角色划分为节点流和处理流。Java Io流共涉及40多个类这些类看上去很杂乱但实际上很有规则而且彼此之间存在非常紧密的联系 Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。InputStream/Reader: 所有的输入流的基类前者是字节输入流后者是字符输入流。OutputStream/Writer: 所有输出流的基类前者是字节输出流后者是字符输出流。按操作方式分类结构图3.34BIO、NIO、AIO 有什么区别1BIO在服务端启动一个ServerSocket然后在客户端启动Socket来对服务端进行通信默认情况下服务端需要对每个请求建立一堆线程等待请求而客户端发送请求后先咨询服务端是否有线程相应如果没有则会一直等待或者遭到拒绝请求如果有的话客户端会线程会等待请求结束后才继续执行。简单来说用户进程在发起一个IO操作以后必须等待IO操作的完成只有当真正完成了IO操作以后用户进程才能运行BIO方式适用于连接数目比较小且固定的架构。2NIONIO基于Reactor当socket有流可读或可写入socket时操作系统会相应的通知引用程序进行处理应用再将流读取到缓冲区或写入操作系统。 当一个连接创建后不需要对应一个线程这个连接会被注册到多路复用器上面所以所有的连接只需要一个线程就可以搞定当这个线程中的多路复用器进行轮询的时候发现连接上有请求的话才开启一个线程进行处理。简单来说用户进程发起一个IO操作以后边可返回做其它事情但是用户进程需要时不时的询问IO操作是否就绪引入不必要的CPU资源浪费NIO方式适用于连接数目多且连接比较短的架构。BIO的每个连接一个单独的线程而NIO则是每个连接共用一个线程。3AIO当有流可读取时操作系统会将可读的流传入read方法的缓冲区并通知应用程序对于写操作而言当操作系统将write方法传递的流写入完毕时操作系统主动通知应用程序。read/write方法都是异步的完成后会主动调用回调函数。 AIO是一个有效请求一个线程。简单来说用户进程只需要发起一个IO操作然后立即返回等IO操作真正的完成以后应用程序会得到IO操作完成的通知。不需要进行实际的IO读写操作因为真正的IO读取或者写入操作已经由内核完成了。AIO方式使用于连接数目多且连接比较长的架构。3.35Files的常用方法都有哪些Files. exists()检测文件路径是否存在。Files. createFile()创建文件。Files. createDirectory()创建文件夹。Files. delete()删除一个文件或目录。Files. copy()复制文件。Files. move()移动文件。Files. size()查看文件个数。Files. read()读取文件。Files. write()写入文件。3.36Java 容器都有哪些如图所示3.37Collection 和 Collections 有什么区别java.util.Collection 是一个集合接口集合类的一个顶级接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式其直接继承接口有List与Set。Collections则是集合类的一个工具类/帮助类其中提供了一系列静态方法用于对集合中元素进行排序、搜索以及线程安全等各种操作。3.38List、Set、Map 之间的区别是什么3.39说一下 HashSet 的实现原理1基于HashMap实现的默认构造函数是构建一个初始容量为16负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存而 HashMap 的 value 则存储了一个 PRESENT它是一个静态的 Object 对象。2当我们试图把某个类的对象当成 HashMap的 key或试图将这个类的对象放入 HashSet 中保存时重写该类的equals(Object obj)方法和 hashCode() 方法很重要而且这两个方法的返回值必须保持一致当该类的两个的 hashCode() 返回值相同时它们通过 equals() 方法比较也应该返回 true。通常来说所有参与计算 hashCode() 返回值的关键属性都应该用于作为 equals() 比较的标准。3HashSet的其他操作都是基于HashMap的。3.40如何决定使用 HashMap 还是 TreeMap对于在Map中插入、删除和定位元素这类操作HashMap是最好的选择。然而假如你需要对一个有序的key集合进行遍历TreeMap是更好的选择。基于你的collection的大小也许向HashMap中添加元素会更快将map换为TreeMap进行有序key的遍历。3.41如何实现数组和 List 之间的转换List转数组toArray(arraylist.size()方法数组转ListArrays的asList(a)方法3.42哪些集合类是线程安全的在集合框架中有些类是线程安全的这些都是jdk1.1中的出现的。在jdk1.2之后就出现许许多多非线程安全的类。 下面是这些线程安全的同步的类vector就比arraylist多了个同步化机制线程安全因为效率较低现在已经不太建议使用。在web应用中特别是前台页面往往效率页面响应速度是优先考虑的。statck堆栈类先进后出hashtable就比hashmap多了个线程安全enumeration枚举相当于迭代器StringBuffer也是线程安全的StringBuilder 不是线程安全的除了这些之外其他的都是非线程安全的类和接口。线程安全的类其方法是同步的每次只能一个访问。是重量级对象效率较低。3.43 Iterator 怎么使用有什么特点1.Iterator在遍历元素过程中有线程修改集合元素会有ConcurrentModificationEception异常2.Iterator本身不具有装载数据功能需依附Collection对象使用3.next是用游标指向的方式返回下一个元素的3.44Iterator 和 ListIterator 有什么区别我们需要知道的第一个则是1所属关系ListIterator是一个Iterator的子类型。2局限只能应用于各种List类的访问。3优势Iterator只能向前移动而ListIterator可以双向移动。还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引nextIndex()、previousIndex()方法。还可以通过set方法替换它访问过的最后一个元素。还可以通过调用listIterator方法产生一个指向List开始处的ListIterator当然也可以有参数即指向索引为参数处的ListIterator。4ListIterator 有 add() 方法可以向 List 中添加对象而 Iterator 不能3.45创建线程有哪几种方式1.继承Thread类实现多线程2.覆写Runnable()接口实现多线程而后同样覆写run().推荐此方式3.覆写Callable接口实现多线程JDK1.54.通过线程池启动多线程3.46说一下 runnable 和 callable 有什么区别Runnable应该是比较熟悉的接口它只有一个run()函数用于将耗时操作写在其中该函数没有返回值不能将结果返回给客户程序。然后使用某个线程去执行runnable即可实现多线程Thread类在调用start()函数后就是执行的是Runnable的run()函数。Callable与Runnable的功能大致相似Callable中有一个call()函数但是call()函数有返回值。主要区别Runnable 接口 run 方法无返回值Callable 接口 call 方法有返回值支持泛型Runnable 接口 run 方法只能抛出运行时异常且无法捕获处理Callable 接口 call 方法允许抛出异常可以获取异常信息3.47sleep() 和 wait() 有什么区别sleep方法属于Thread类中的方法会导致程序暂停执行指定的时间让出cpu该其他线程但是他的监控状态依然保持着当指定时间到了之后又会自动恢复运行状态在调用sleep方法的过程中线程不会释放对象锁。只会让出CPU不会导致锁行为的改变wait方法属于Object类中的方法在调用wait方法的时候线程会放弃对象锁进入等待此对象的等待锁定池只有针对此对象调用notify方法后本线程才进入对象锁定池准备。获取对象锁进入运行状态。不仅让出CPU还释放已经占有的同步资源锁3.48notify()和 notifyAll()有什么区别notify():唤醒一个处于等待状态的线程注意的是在调用此方法的时候并不能确切的唤醒某一个等待状态的线程而是由JVM确定唤醒哪个线程而且不是按优先级。notifyAll():唤醒所有处入等待状态的线程;并可以理解为把他们排进一个队列;只不过只有头部的线程获得了锁才能运行;注意并不是给所有唤醒线程一个对象的锁而是让它们竞争当其中一个线程运行完就开始运行下一个已经被唤醒的线程因为锁已经转移了。3.49线程的 run()和 start()有什么区别run()方法是在主线程中执行方法和调用普通方法一样按顺序执行同步执行start()方法是创建了新的线程在新的线程中执行异步执行3.50线程池中 submit()和 execute()方法有什么区别execute() 参数 Runnable submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)execute() 没有返回值而 submit() 有返回值submit() 的返回值 Future 调用get方法时可以捕获处理异常3.51在 java 程序中怎么保证多线程的运行安全线程的安全性问题体现在原子性一个或者多个操作在 CPU 执行的过程中不被中断的特性可见性一个线程对共享变量的修改另外一个线程能够立刻看到有序性程序执行的顺序按照代码的先后顺序执行导致原因缓存导致的可见性问题线程切换带来的原子性问题编译优化带来的有序性问题解决办法JDK Atomic开头的原子类、synchronized、LOCK可以解决原子性问题synchronized、volatile、LOCK可以解决可见性问题Happens-Before 规则可以解决有序性问题Happens-Before 规则如下程序次序规则在一个线程内按照程序控制流顺序书写在前面的操作先行发生于书写在后面的操作管程锁定规则一个unlock操作先行发生于后面对同一个锁的lock操作volatile变量规则对一个volatile变量的写操作先行发生于后面对这个变量的读操作线程启动规则Thread对象的start()方法先行发生于此线程的每一个动作线程终止规则线程中的所有操作都先行发生于对此线程的终止检测线程中断规则对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生对象终结规则一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始3.52什么是死锁所谓死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局当进程处于这种僵持状态时若无外力作用它们都将无法再向前推进。 因此我们举个例子来描述如果此时有一个线程A按照先锁a再获得锁b的的顺序获得锁而在此同时又有另外一个线程B按照先锁b再锁a的顺序获得锁。如下图所示3.53怎么防止死锁预防死锁资源一次性分配一次性分配所有资源这样就不会再有请求了破坏请求条件只要有一个资源得不到分配也不给这个进程分配其他的资源破坏请保持条件可剥夺资源即当某进程获得了部分资源但得不到其它资源则释放已占有的资源破坏不可剥夺条件资源有序分配法系统给每类资源赋予一个编号每一个进程按编号递增的顺序请求资源释放则相反破坏环路等待条件1、以确定的顺序获得锁如果必须获取多个锁那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子两个线程获得锁的时序图如下那么死锁就永远不会发生。 针对两个特定的锁开发者可以尝试按照锁对象的hashCode值大小的顺序分别获得两个锁这样锁总是会以特定的顺序获得锁那么死锁也不会发生。问题变得更加复杂一些如果此时有多个线程都在竞争不同的锁简单按照锁对象的hashCode进行排序单纯按照hashCode顺序排序会出现“环路等待”可能就无法满足要求了这个时候开发者可以使用银行家算法所有的锁都按照特定的顺序获取同样可以防止死锁的发生该算法在这里就不再赘述了有兴趣的可以自行了解一下。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho2、超时放弃当使用synchronized关键词提供的内置锁时只要线程没有获得锁那么就会永远等待下去然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法该方法可以按照固定时长等待锁因此线程可以在获取锁超时以后主动释放之前已经获得的所有的锁。通过这种方式也可以很有效地避免死锁。 还是按照之前的例子时序图如下避免死锁:预防死锁的几种策略会严重地损害系统性能。因此在避免死锁时要施加较弱的限制从而获得 较满意的系统性能。由于在避免死锁的策略中允许进程动态地申请资源。因而系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态则将资源分配给进程否则进程等待。其中最具有代表性的避免死锁算法是银行家算法。银行家算法首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此状态包含两个向量Resource系统中每种资源的总量和Available未分配给进程的每种资源的总量及两个矩阵Claim表示进程对资源的需求和Allocation表示当前分配给进程的资源。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时假设同意该请求从而改变了系统的状态然后确定其结果是否还处于安全状态。如果是同意这个请求如果不是阻塞该进程知道同意该请求后系统状态仍然是安全的。3.54 synchronized 和 volatile 的区别是什么1)volatile比synchronized更轻量级。2)volatile没有synchronized使用的广泛。3)volatile不需要加锁比synchronized更轻量级不会阻塞线程。4)从内存可见性角度看volatile读相当于加锁volatile写相当于解锁。5)synchronized既能保证可见性又能保证原子性而volatile只能保证可见性无法保证原子性。6)volatile本身不保证获取和设置操作的原子性仅仅保持修改的可见性。但是java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。3.55synchronized 和 Lock 有什么区别3.56 synchronized 和 ReentrantLock 区别是什么基本意义Synchronized是Java语言的关键字因此Synchronized的锁是原生语法层面的互斥需要JVM来实现。具体是通过对象内部的一个叫做监视器锁monitor来实现的。ReentrantLock字面意思可重入锁它是JDK1.5之后提供的API层面的互斥锁锁的功能主要由2个方法完成即lock()和unlock()。易用性Synchronized的使用比较方便简洁由编译器去保证锁的加锁和释放而ReentrantLock需要手动写代码来加锁和释放锁。注意为避免忘记手工释放锁而造成了死锁最好在finally中声明释放锁。灵活度ReentrantLock要优于Synchronized可以灵活控制在哪个位置加锁和解锁。性能区别我们平时写代码的时候用到Java最多的锁是Synchronized单例模式中看到的锁也是Synchronized。这是为什么呢除了Synchronized使用方面一些其实和ReentrantLock对比性能也丝毫不逊色这里面说的当然是JDK1.5以后的版本了。JDK1.6以前的版本没有优化这时和ReentrantLock比肯定差很多。优化以后的Synchronized引入了包括偏向锁轻量级锁等这样就和ReentrantLock性能差不多了。3.57什么是反射Java 反射机制是在运行状态中对于任意一个类都能够获得这个类的所有属性和方法对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。Class 类与java.lang.reflect 类库一起对反射的概念进行了支持该类库包含了Field,Method,Constructor类(每个类都实现了Member 接口)。这些类型的对象时由JVM 在运行时创建的用以表示未知类里对应的成员。这样就可以使用Constructor 创建新的对象用get() 和set() 方法读取和修改与Field 对象关联的字段用invoke() 方法调用与Method 对象关联的方法。另外还可以调用getFields() getMethods() 和 getConstructors() 等很便利的方法以返回表示字段方法以及构造器的对象的数组。这样匿名对象的信息就能在运行时被完全确定下来而在编译时不需要知道任何事情。3.58动态代理是什么有哪些应用动态代理是运行时动态生成代理类。 动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpcJava注解对象获取等。3.59深拷贝和浅拷贝区别是什么浅拷贝创建一个新对象然后将当前对象的非静态字段复制到该新对象如果字段是值类型的那么对该字段执行复制如果该字段是引用类型的话则复制引用但不复制引用的对象。因此原始对象及其副本引用同一个对象。深拷贝创建一个新对象然后将当前对象的非静态字段复制到该新对象无论该字段是值类型的还是引用类型都复制独立的一份。当你修改其中一个对象的任何内容时都不会影响另一个对象的内容。3.60throw 和 throws 的区别throw:(1)throw 语句用在方法体内表示抛出异常由方法体内的语句处理。(2)throw 是具体向外抛出异常的动作所以它抛出的是一个异常实例执行 throw 一定是抛出了某种异常。throws:(1)throws 语句是用在方法声明后面表示如果抛出异常由该方法的调用者来进行异常的处理。(2)throws 主要是声明这个方法会抛出某种类型的异常让它的使用者要知道需要捕获的异常的类型。(3)throws 表示出现异常的一种可能性并不一定会发生这种异常。3.61final、finally、finalize 有什么区别1、finalFinal可以用于成员变量包括方法参数方法、类。Final成员作为变量变量一旦被初始化便不可改变对于基本类型指的是值不变对于对象类型指的是引用不变初始化只可能在两个地方定义处和构造函数。作为方法参数对于基本类型定义成final参数没有什么意义因为基本类型就是传值不会影响调用语句中的变量对于对象类型在方法中如果参数确认不需要改变时定义成final参数可以防止方法中无意的修改而影响到调用方法。Final方法不可覆写编译器将对此方法的调用转化成行内inline调用即直接把方法主体插入到调用处方法主体内容过多的时候反而会影响效率Final类不可继承2、finally异常处理关键字finally中的主体总会执行不管异常发生是否。通常放在try…catch的后面构造最终执行代码块这就意味着程序无论正常执行还是发生异常这里的代码只要JVM不关闭都能执行可以将释放外部资源的代码写在finally块中。2.1、当try中有return时执行顺序return语句并不是函数的最终出口如果有finally语句这在return之后还会执行finallyreturn的值会暂存在栈里面等待finally执行后再返回2.2、return和异常获取语句的位置3、finalize类的Finalize方法可以告诉垃圾回收器应该执行的操作该方法从Object类继承而来。在从堆中永久删除对象之前垃圾回收器调用该对象的Finalize方法。注意无法确切地保证垃圾回收器何时调用该方法也无法保证调用不同对象的方法的顺序。即使一个对象包含另一个对象的引用或者在释放一个对象很久以前就释放了另一个对象也可能会以任意的顺序调用这两个对象的Finalize方法。如果必须保证采用特定的顺序则必须提供自己的特有清理方法。3.64 Java中CyclicBarrier 和 CountDownLatch有什么不同1、CyclicBarrier的某个线程运行到某个点后停止运行直到所有线程都达到同一个点所有线程才会重新运行CountDownLatch线程运行到某个点后计数值-1该线程继续运行直到计数值为0则停止运行2、CyclicBarrier只能唤醒一个任务CountDownLatch可以唤醒多个任务3、CyccliBarrier可以重用CountDownLatch不可重用当计数值为0时CountDownLatch就不可再用了。3.65为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理 又是什么?什么是 CAS它有什么特性?Synchronized显然是一个悲观锁因为它的并发策略是悲观的不管是否会产生竞争任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。随着硬件指令集的发展我们可以使用基于冲突检测的乐观并发策略。先进行操作如果没有其他线程征用数据那操作就成功了如果共享数据有征用产生了冲突那就再进行其他的补偿措施。这种乐观的并发策略的许多实现不需要线程挂起所以被称为非阻塞同步。乐观锁的核心算法是CASCompareandSwap比较并交换它涉及到三个操作数内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是首先检查某块内存的值是否跟之前我读取时的一样如不一样则表示期间此内存值已经被别的线程更改过舍弃本次操作否则说明期间没有其他线程对此内存值操作可以把新值设置给此块内存。CAS具有原子性它的原子性由CPU硬件指令实现保证即使用JNI调用Native方法调用由C编写的硬件级别指令JDK中提供了Unsafe类执行这些操作。3.66 Java中Semaphore是什么Java中的Semaphore是一种新的同步类它是一个计数信号。从概念上讲从概念上讲信号量维护了一个许可集合。如有必要在许可可用前会阻塞每一个 acquire()然后再获取该许可。每个 release()添加一个许可从而可能释放一个正在阻塞的获取者。但是不使用实际的许可对象Semaphore只对可用许可的号码进行计数并采取相应的行动。信号量常常用于多线程的代码中比如数据库连接池。3.67现在有T1、T2、T3三个线程你怎样保证T2在T1执行完后执行T3在T2执行完后执行在多线程中有多种方法让线程按特定顺序执行你可以用线程类的join()方法在一个线程中启动另一个线程另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2T2调用T1)这样T1就会先完成而T3最后完成。3.68你如何在Java中获取线程堆栈Java虚拟机提供了线程转储thread dump的后门通过这个后门可以把线程堆栈打印出来。通常我们将堆栈信息重定向到一个文件中便于我们分析由于信息量太大很可能超出控制台缓冲区的最大行数限制造成信息丢失。这里介绍一个jdk自带的打印线程堆栈的工具jstack用于打印出给定的Java进程ID或core file或远程调试服务的Java堆栈信息。示例$jstack –l 23561 xxx.dump命令 : $jstack [option] pid 文件表示输出到文件尾部实际运行中往往一次dump的信息还不足以确认问题建议产生三次dump信息如果每次dump都指向同一个问题我们才确定问题。3.69提交任务时线程池队列已满会时发会生什么这个问题问得很狡猾许多程序员会认为该任务会阻塞直到线程池队列有空位。但是最大线程数没有满的话就会新建一个非核心线程去执行该任务。如果核心线程数、阻塞队列、最大线程数都满了的话就会执行线程池的拒绝策略如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho3.70什么是乐观锁和悲观锁乐观锁总是认为不会产生并发问题每次去取数据的时候总认为不会有其他线程对数据进行修改因此不会上锁但是在更新时会判断其他线程在这之前有没有对数据进行修改一般会使用版本号机制或CAS操作实现。乐观锁适用于多读的应用类型这样可以提高吞吐量像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。悲观锁顾名思义就是很悲观总是假设最坏的情况每次取数据时都认为其他线程会修改所以都会加锁读锁、写锁、行锁等当其他线程想要访问数据时都需要阻塞挂起。可以依靠数据库实现如行锁、读锁和写锁等都是在操作之前加锁在Java中synchronized的思想也是悲观锁。第4章JVM4.1JVM内存分哪几个区每个区的作用是什么?java虚拟机主要分为以下几个区:1方法区有时候也成为永久代在该区内很少发生垃圾回收但是并不代表不发生GC在这里进行的GC主要是对方法区里的常量池和对类型的卸载方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。该区域是被线程共享的。方法区里有一个运行时常量池用于存放静态编译产生的字面量和符号引用。该常量池具有动态性也就是说常量并不一定是编译时确定运行时生成的常量也会存在这个常量池中。2虚拟机栈:虚拟机栈也就是我们平常所称的栈内存,它为java方法服务每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接和方法出口等信息。虚拟机栈是线程私有的它的生命周期与线程相同。局部变量表里存储的是基本数据类型、returnAddress类型指向一条字节码指令的地址和对象引用这个对象引用有可能是指向对象起始地址的一个指针也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定操作数栈的作用主要用来存储运算结果以及运算的操作数它不同于局部变量表通过索引来访问而是压栈和出栈的方式每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。3本地方法栈本地方法栈和虚拟机栈类似只不过本地方法栈为Native方法服务。4堆java堆是所有线程所共享的一块内存在虚拟机启动时创建几乎所有的对象实例都在这里创建因此该区域经常发生垃圾回收操作。5程序计数器内存空间小字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。4.2heap 和stack 有什么区别1申请方式stack:由系统自动分配。例如声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间heap:需要程序员自己申请并指明大小在 c 中 malloc 函数对于Java 需要手动 new Object()的形式开辟2申请后系统的响应stack只要栈的剩余空间大于所申请空间系统将为程序提供内存否则将报异常提示栈溢出。heap首先应该知道操作系统有一个记录空闲内存地址的链表当系统收到程序的申请时会遍历该链表寻找第一个空间大于所申请空间的堆结点然后将该结点从空闲结点链表中删除并将该结点的空间分配给程序。另外由于找到的堆结点的大小不一定正好等于申请的大小系统会自动的将多余的那部分重新放入空闲链表中。3申请大小的限制stack栈是向低地址扩展的数据结构是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的在 WINDOWS 下栈的大小是 2M也有的说是 1M总之是一个编译时就确定的常数如果申请的空间超过栈的剩余空间时将提示 overflow。因此能从栈获得的空间较小。heap堆是向高地址扩展的数据结构是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的 自然是不连续的而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见 堆获得的空间比较灵活也比较大。4申请效率的比较stack由系统自动分配速度较快。但程序员是无法控制的。heap由 new 分配的内存一般速度比较慢而且容易产生内存碎片,不过用起来最方便。5heap和stack中的存储内容stack在函数调用时第一个进栈的是主函数中后的下一条指令函数调用语句的下一条可执行语句的地址 然后是函数的各个参数在大多数的 C 编译器中参数是由右往左入栈的然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后局部变量先出栈然后是参数最后栈顶指针指向最开始存的地址也就是主函数中的下一条指令程序由该点继续运行。heap一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。4.3java类加载过程?Java类加载需要经历一下几个过程1加载加载时类加载的第一个过程在这个阶段将完成一下三件事情通过一个类的全限定名获取该类的二进制流。将该二进制流中的静态存储结构转化为方法去运行时数据结构。在内存中生成该类的Class对象作为该类的数据访问入口。2验证验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:文件格式验证验证字节流是否符合Class文件的规范如主次版本号是否在当前虚拟机范围内常量池中的常量是否有不被支持的类型.元数据验证:对字节码描述的信息进行语义分析如这个类是否有父类是否集成了不被继承的类等。字节码验证是整个验证过程中最复杂的一个阶段通过验证数据流和控制流的分析确定程序语义是否正确主要针对方法体的验证。如方法中的类型转换是否正确跳转指令是否正确等。符号引用验证这个动作在后面的解析过程中发生主要是为了确保解析动作能正确执行。准备准备阶段是为类的静态变量分配内存并将其初始化为默认值这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存实例变量将会在对象实例化时随着对象一起分配在Java堆中。3解析该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前也有可能在初始化之后。4初始化初始化时类加载的最后一步前面的类加载过程除了在加载阶段用户应用程序可以通过自定义类加载器参与之外其余动作完全由虚拟机主导和控制。到了初始化阶段才真正开始执行类中定义的Java程序代码。4.4什么是类加载器类加载器有哪些?实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。主要有一下四种类加载器:1启动类加载器(Bootstrap ClassLoader)用来加载java核心类库无法被java程序直接引用。2扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。3系统类加载器system class loader也叫应用类加载器它根据 Java 应用的类路径CLASSPATH来加载 Java 类。一般来说Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。4用户自定义类加载器通过继承 java.lang.ClassLoader类的方式实现。4.5java中垃圾收集的方法有哪些?1引用计数法应用于微软的COM/ActionScrip3/Python等a) 如果对象没有被引用就会被回收缺点需要维护一个引用计算器2复制算法年轻代中使用的是Minor GC这种GC算法采用的是复制算法(Copying)a) 效率高缺点需要内存容量大比较耗内存b) 使用在占空间比较小、刷新次数多的新生区3标记清除老年代一般是由标记清除或者是标记清除与标记整理的混合实现a) 效率比较低会差生碎片。4标记压缩老年代一般是由标记清除或者是标记清除与标记整理的混合实现a) 效率低速度慢需要移动对象但不会产生碎片。5标记清除压缩标记清除-标记压缩的集合多次GC后才Compacta) 使用于占空间大刷新次数少的养老区是3 4的集合体4.6如何判断一个对象是否存活?(或者GC对象的判定方法)判断一个对象是否存活有两种方法:1引用计数法所谓引用计数法就是给每一个对象设置一个引用计数器每当有一个地方引用这个对象时就将计数器加一引用失效时计数器就减一。当一个对象的引用计数器为零时说明此对象没有被引用也就是“死对象”,将会被垃圾回收.引用计数法有一个缺陷就是无法解决循环引用问题也就是说当对象A引用对象B对象B又引用者对象A那么此时A,B对象的引用计数器都不为零也就造成无法完成垃圾回收所以主流的虚拟机都没有采用这种算法。2可达性算法(引用链法)该算法的基本思路就是通过一些被称为引用链GC Roots的对象作为起点从这些节点开始向下搜索搜索走过的路径被称为Reference Chain)当一个对象到GC Roots没有任何引用链相连时即从GC Roots节点到该节点不可达则证明该对象是不可用的。在java中可以作为GC Roots的对象有以下几种虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。4.7简述java内存分配与回收策略以及Minor GC和Major GCfullGC内存分配1栈区栈分为java虚拟机栈和本地方法栈2堆区堆被所有线程共享区域在虚拟机启动时创建唯一目的存放对象实例。堆区是gc的主要区域通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区主要放新创建对象From survivor 和 To survivor 保存gc后幸存下的对象默认情况下各自占比 8:1:1。3方法区被所有线程共享区域用于存放已被虚拟机加载的类信息常量静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代permanment generation4程序计数器当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令比如循环分支跳转异常处理线程恢复等都是依赖计数器来完成。线程私有的。回收策略以及Minor GC和Major GC1对象优先在堆的Eden区分配。2大对象直接进入老年代。3长期存活的对象将直接进入老年代。当Eden区没有足够的空间进行分配时虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区在这个区的对象生存期短往往发生GC的频率较高回收速度比较快;Full Gc/Major GC 发生在老年代一般情况下触发老年代GC的时候不会触发Minor GC,但是通过配置可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。4.8什么情况下会发生栈内存溢出。栈是线程私有的他的生命周期与线程相同每个方法在执行的时候都会创建一个栈帧用来存储局部变量表操作数栈动态链接方法出口等信息。局部变量表又包含基本数据类型对象引用类型如果线程请求的栈深度大于虚拟机所允许的最大深度将抛出StackOverflowError异常方法递归调用产生这种结果。如果Java虚拟机栈可以动态扩展并且扩展的动作已经尝试过但是无法申请到足够的内存去完成扩展或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)参数 -Xss 去调整JVM栈的大小4.9 JVM中一次完整的GC流程是怎样的对象如何晋升到老年代对象诞生即新生代-eden在进行minor gc过程中如果依旧存活移动到from变成Survivor进行标记。当一个对象存活默认超过15次都没有被回收掉就会进入老年代。4.10你知道哪几种垃圾收集器各自的优缺点重点讲下cms和G1包括原理流程优缺点。垃圾收集器包括Serial、parNew、ParallelScavenge、SerialOld、ParallelOld、CMS、G1CMS:一、初始标记此时标记需要用户线程停下来二、并发标记此时标记可以和用户线程一起运行三、重新标记此时标记需要用户线程停下来主要母的是为了对并发标记的垃圾进行审核四、并发清除与用户线程一起与运行进行垃圾清除缺点1、CMS收集器对cpu资源非常敏感2、CMS收集器无法清除浮动垃圾3、cms基于标记清除的算法实现的所以内存碎片会产生过多。G1收集器1、初始标记标记GC Root能直接关联的对象并且修改TAMS的值让下一阶段的用户进行并发运行是能够正确运用Region创建新对象这阶段需要停顿但停顿时间很短2、并发标记从GC Root开始对堆进行可达性分析找出存活的对象这段耗时较长但可以与用户线程并发执行。3、最终标记是为了修正在并发标记阶段因用户程序继续运作导致标记产生变动的那一部分的标记记录虚拟机将这部分标记记录在线程Remembered Set中这阶段需要停顿线程但是可并行执行。4、筛选回收首先对各个Region的回收价值和成本进行排序根据用户所期待的GC停顿时间来制定回收计划这个阶段也可以与用户线程并行执行但由于只回收一部分的Region,时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho4.11 JVM内存模型的相关知识了解多少比如重排序内存屏障happen-before主内存工作内存。重排序jvm虚拟机允许在不影响代码最终结果的情况下可以乱序执行。内存屏障可以阻挡编译器的优化也可以阻挡处理器的优化happens-before原则1一个线程的A操作总是在B之前那多线程的A操作肯定实在B之前。2monitor 再加锁的情况下持有锁的肯定先执行。3volatile修饰的情况下写先于读发生4线程启动在一起之前 strat5线程死亡在一切之后 end6线程操作在一切线程中断之前7一个对象构造函数的结束都该对象的finalizer的开始之前8传递性如果A肯定在B之前B肯定在C之前那A肯定是在C之前。主内存所有线程共享的内存空间工作内存每个线程特有的内存空间4.12简单说说你了解的类加载器可以打破双亲委派么怎么打破。1) 什么是类加载器类加载器 就是根据指定全限定名称将class文件加载到JVM内存转为Class对象。启动类加载器Bootstrap ClassLoader由C语言实现针对HotSpot,负责将存放在JAVA_HOME\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。其他类加载器由Java语言实现继承自抽象类ClassLoader。如扩展类加载器Extension ClassLoader负责加载JAVA_HOME\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。应用程序类加载器Application ClassLoader。负责加载用户类路径classpath上的指定类库我们可以直接使用这个类加载器。一般情况如果我们没有自定义类加载器默认就是用这个加载器。2双亲委派模型双亲委派模型工作过程是如果一个类加载器收到类加载的请求它首先不会自己去尝试加载这个类而是把这个请求委派给父类加载器完成。每个类加载器都是如此只有当父加载器在自己的搜索范围内找不到指定的类时即ClassNotFoundException子加载器才会尝试自己去加载。双亲委派模型图3为什么需要双亲委派模型在这里先想一下如果没有双亲委派那么用户是不是可以自己定义一个java.lang.Object的同名类java.lang.String的同名类并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证因此为什么需要双亲委派模型防止内存中出现多份同样的字节码4怎么打破双亲委派模型打破双亲委派机制则不仅要继承ClassLoader类还要重写loadClass和findClass方法。4.13说说你知道的几种主要的JVM参数1堆栈配置相关java -Xmx3550m -Xms3550m -Xmn2g -Xss128k-XX:MaxPermSize16m -XX:NewRatio4 -XX:SurvivorRatio4 -XX:MaxTenuringThreshold0-Xmx3550m 最大堆大小为3550m。-Xms3550m 设置初始堆大小为3550m。-Xmn2g 设置年轻代大小为2g。-Xss128k 每个线程的堆栈大小为128k。-XX:MaxPermSize 设置持久代大小为16m-XX:NewRatio4: 设置年轻代包括Eden和两个Survivor区与年老代的比值除去持久代。-XX:SurvivorRatio4 设置年轻代中Eden区与Survivor区的大小比值。设置为4则两个Survivor区与一个Eden区的比值为2:4一个Survivor区占整个年轻代的1/6-XX:MaxTenuringThreshold0 设置垃圾最大年龄。如果设置为0的话则年轻代对象不经过Survivor区直接进入年老代。2垃圾收集器相关-XX:UseParallelGC-XX:ParallelGCThreads20-XX:UseConcMarkSweepGC-XX:CMSFullGCsBeforeCompaction5-XX:UseCMSCompactAtFullCollection-XX:UseParallelGC 选择垃圾收集器为并行收集器。-XX:ParallelGCThreads20 配置并行收集器的线程数-XX:UseConcMarkSweepGC 设置年老代为并发收集。-XX:CMSFullGCsBeforeCompaction由于并发收集器不对内存空间进行压缩、整理所以运行一段时间以后会产生“碎片”使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。-XX:UseCMSCompactAtFullCollection 打开对年老代的压缩。可能会影响性能但是可以消除碎片3辅助信息相关-XX:PrintGC-XX:PrintGCDetails-XX:PrintGC 输出形式:[GC 118250K-113543K(130112K), 0.0094143 secs] [Full GC 121376K-10414K(130112K), 0.0650971 secs]-XX:PrintGCDetails 输出形式:[GC [DefNew: 8614K-781K(9088K), 0.0123035 secs] 118250K-113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K-8614K(9088K), 0.0000665 secs][Tenured: 112761K-10414K(121024K), 0.0433488 secs] 121376K-10414K(130112K), 0.0436268 secs4.15垃圾收集算法jvm的垃圾回收算法有3种列举如下1标记清除算法。即把标注的可回收对象直接清理这样会带来内存碎片化的问题而且效率不高2标记整理算法。即把标注的可回收对象清理在清理的过程中整理内存解决了内存的碎片化问题3标记复制算法。把标注的对象清理没有清理的对象复制到to区然后互换引用解决了内存碎片化的问题但是需要维护对象关系带来一定代价4.16调优工具用过哪些常用调优工具分为两类,jdk自带监控工具jconsole和jvisualvm第三方有MAT(Memory Analyzer Tool)、GChisto。jconsoleJava Monitoring and Management Console是从java5开始在JDK中自带的java监控和管理控制台用于对JVM中内存线程和类等的监控jvisualvmjdk自带全能工具可以分析内存快照、线程快照监控内存变化、GC变化等。MATMemory Analyzer Tool一个基于Eclipse的内存分析工具是一个快速、功能丰富的Java heap分析工具它可以帮助我们查找内存泄漏和减少内存消耗GChisto一款专业分析gc日志的工具4.17你知道哪些JVM性能调优首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存这个数值不能准确的反应堆内存的真实占用情况因为GC过后这个值是不会变化的因此内存调优的时候要更多地使用JDK提供的内存查看工具比如JConsole和Java VisualVM。对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数过多的GC和Full GC是会占用很多的系统资源主要是CPU影响系统的吞吐量。特别要关注Full GC因为它会对整个堆进行整理导致Full GC一般由于以下几种情况旧生代空间不足调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象Pemanet Generation空间不足增大Perm Gen空间避免太多静态对象统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间控制好新生代和旧生代的比例System.gc()被显示调用垃圾回收不要手动触发尽量依靠JVM自身的机制调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现下面来看看各部分比例不良设置会导致什么后果1). 新生代设置过小一是新生代GC次数非常频繁增大系统消耗二是导致大对象直接进入旧生代占据了旧生代剩余空间诱发Full GC2). 新生代设置过大一是新生代设置过大会导致旧生代过小堆总量一定从而诱发Full GC二是新生代GC耗时大幅度增加一般说来新生代占整个堆1/3比较合适3). Survivor设置过小导致对象从eden直接到达旧生代降低了在新生代的存活时间4). Survivor设置过大导致eden过小增加了GC频率另外通过-XX:MaxTenuringThresholdn来控制新生代存活时间尽量让对象在新生代被回收由内存管理和垃圾回收可知新生代和旧生代都有多种GC策略和组合搭配选择这些策略对于我们这些开发人员是个难题JVM提供两种较为简单的GC策略的设置方式1). 吞吐量优先JVM以吞吐量为指标自行选择相应的GC策略及控制新生代与旧生代的大小比例来达到吞吐量指标。这个值可由-XX:GCTimeRation来设置2). 暂停时间优先JVM以暂停时间为指标自行选择相应的GC策略及控制新生代与旧生代的大小比例尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRation来设置4.18 Eden和Survivor的比例分配等默认比例8:1。部分对象都是朝生夕死。 复制算法的基本思想就是将内存分为两块每次只用其中一块当这一块内存用完就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。4.19说一说你对环境变量classpath的理解如果一个类不在classpath下为什么会抛出ClassNotFoundException异常如果在不改变这个类路径的前期下怎样才能正确加载这个类classpath是javac编译器的一个环境变量。它的作用与import、package关键字有关。package的所在位置就是设置CLASSPATH当编译器面对import packag这个语句时它先会查找CLASSPATH所指定的目录并检视子目录java/util是否存在然后找出名称吻合的已编译文件.class文件。如果没有找到就会报错动态加载包设计模式5.1你所知道的设计模式有哪些Java 中一般认为有 23 种设计模式我们不需要所有的都会但是其中常用的几种设计模式应该去掌握。下面列出了所有的设计模式。需要掌握的设计模式我单独列出来了当然能掌握的越多越好。总体来说设计模式分为三大类创建型模式共5种工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型模式共7种适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。行为型模式共11种策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。5.2单例设计模式见【第2章 手写代码 2.5】5.3工厂设计模式Factory5.3.1什么是工厂设计模式工厂设计模式顾名思义就是用来生产对象的在java中万物皆对象这些对象都需要创建如果创建的时候直接new该对象就会对该对象耦合严重假如我们要更换对象所有new对象的地方都需要修改一遍这显然违背了软件设计的开闭原则如果我们使用工厂来生产对象我们就只和工厂打交道就可以了彻底和对象解耦如果要更换对象直接在工厂里更换该对象即可达到了与对象解耦的目的所以说工厂模式最大的优点就是解耦5.3.2简单工厂Simple Factory定义一个工厂方法依据传入的参数生成对应的产品对象角色1、抽象产品2、具体产品3、具体工厂4、产品使用者使用说明先将产品类抽象出来比如苹果和梨都属于水果抽象出来一个水果类Fruit苹果和梨就是具体的产品类然后创建一个水果工厂分别用来创建苹果和梨。代码如下水果接口publicinterface Fruit {voidwhatIm();}苹果类publicclassAppleimplementsFruit {OverridepublicvoidwhatIm() {System.out.println(苹果);}}篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho梨类publicclassPearimplementsFruit {OverridepublicvoidwhatIm() {System.out.println(梨);}}水果工厂publicclassFruitFactory {publicFruit createFruit(String type) {if(type.equals(apple)) {//生产苹果returnnewApple();}elseif(type.equals(pear)) {//生产梨returnnewPear();}returnnull;}}使用工厂生产产品publicclassFruitApp {publicstaticvoidmain(String[] args) {FruitFactory mFactory newFruitFactory();Apple apple (Apple) mFactory.createFruit(apple);//获得苹果Pear pear (Pear) mFactory.createFruit(pear);//获得梨apple.whatIm();pear.whatIm();}}以上的这种方式每当添加一种水果就必然要修改工厂类违反了开闭原则所以简单工厂只适合于产品对象较少且产品固定的需求对于产品变化无常的需求来说显然不合适。5.3.3工厂方法Factory Method定义将工厂提取成一个接口或抽象类具体生产什么产品由子类决定角色1、抽象产品2、具体产品3、抽象工厂4、具体工厂使用说明和上例中一样产品类抽象出来这次我们把工厂类也抽象出来生产什么样的产品由子类来决定。代码如下水果接口、苹果类和梨类代码和上例一样抽象工厂接口publicinterface FruitFactory {Fruit createFruit();//生产水果}苹果工厂publicclassAppleFactory implements FruitFactory {OverridepublicApple createFruit() {returnnewApple();}}梨工厂publicclassPearFactory implements FruitFactory {OverridepublicPear createFruit() {returnnewPear();}}使用工厂生产产品publicclassFruitApp {publicstaticvoidmain(String[] args){AppleFactory appleFactory newAppleFactory();PearFactory pearFactory newPearFactory();Apple apple appleFactory.createFruit();//获得苹果Pear pear pearFactory.createFruit();//获得梨apple.whatIm();pear.whatIm();}}以上这种方式虽然解耦了也遵循了开闭原则但是如果我需要的产品很多的话需要创建非常多的工厂所以这种方式的缺点也很明显。5.3.4抽象工厂Abstract Factory定义为创建一组相关或者是相互依赖的对象提供的一个接口而不需要指定它们的具体类。角色1、抽象产品2、具体产品3、抽象工厂4、具体工厂使用说明抽象工厂和工厂方法的模式基本一样区别在于工厂方法是生产一个具体的产品而抽象工厂可以用来生产一组相同有相对关系的产品重点在于一组一批一系列举个例子假如生产小米手机小米手机有很多系列小米note、红米note等假如小米note生产需要的配件有825的处理器6英寸屏幕而红米只需要650的处理器和5寸的屏幕就可以了。用抽象工厂来实现cpu接口和实现类publicinterface Cpu {voidrun();classCpu650 implements Cpu {Overridepublicvoidrun() {System.out.println(650 也厉害);}}classCpu825 implements Cpu {Overridepublicvoidrun() {System.out.println(825 更强劲);}}}屏幕接口和实现类publicinterface Screen {voidsize();classScreen5 implements Screen {Overridepublicvoidsize() {System.out.println( 5寸);}}classScreen6 implements Screen {Overridepublicvoidsize() {System.out.println(6寸);}}}抽象工厂接口publicinterface PhoneFactory {Cpu getCpu();//使用的cpuScreen getScreen();//使用的屏幕}小米手机工厂publicclassXiaoMiFactory implements PhoneFactory {OverridepublicCpu.Cpu825 getCpu() {returnnewCpu.Cpu825();//高性能处理器}OverridepublicScreen.Screen6 getScreen() {returnnewScreen.Screen6();//6寸大屏}}红米手机工厂publicclassHongMiFactory implements PhoneFactory {OverridepublicCpu.Cpu650 getCpu() {returnnewCpu.Cpu650();//高效处理器}OverridepublicScreen.Screen5 getScreen() {returnnewScreen.Screen5();//小屏手机}}使用工厂生产产品publicclassPhoneApp {publicstaticvoidmain(String[] args){HongMiFactory hongMiFactory newHongMiFactory();XiaoMiFactory xiaoMiFactory newXiaoMiFactory();Cpu.Cpu650 cpu650 hongMiFactory.getCpu();Cpu.Cpu825 cpu825 xiaoMiFactory.getCpu();cpu650.run();cpu825.run();Screen.Screen5 screen5 hongMiFactory.getScreen();Screen.Screen6 screen6 xiaoMiFactory.getScreen();screen5.size();screen6.size();}}以上例子可以看出抽象工厂可以解决一系列的产品生产的需求对于大批量多系列的产品用抽象工厂可以更好的管理和扩展。5.3.5三种工厂方式总结1、对于简单工厂和工厂方法来说两者的使用方式实际上是一样的如果对于产品的分类和名称是确定的数量是相对固定的推荐使用简单工厂模式2、抽象工厂用来解决相对复杂的问题适用于一系列、大批量的对象生产。5.4代理模式Proxy5.4.1什么是代理模式代理模式给某一个对象提供一个代理对象并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。举个例子来说明假如说我现在想买一辆二手车虽然我可以自己去找车源做质量检测等一系列的车辆过户流程但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢于是我就通过中介公司来买车他们来给我找车源帮我办理车辆过户流程我只是负责选择自己喜欢的车然后付钱就可以了。用图表示如下5.4.2为什么要用代理模式中介隔离作用在某些情况下一个客户类不想或者不能直接引用一个委托对象而代理类对象可以在客户类和委托对象之间起到中介的作用其特征是代理类和委托类实现相同的接口。开闭原则增加功能代理类除了是客户类和委托类的中介之外我们还可以通过给代理类增加额外的功能来扩展委托类的功能这样做我们只需要修改代理类而不需要再修改委托类符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类以及事后对返回结果的处理等。代理类本身并不真正实现服务而是同过调用委托类的相关方法来提供特定的服务。真正的业务功能还是由委托类来实现但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能我们就可以使用代理类来完成而没必要修改已经封装好的委托类。5.4.3有哪几种代理模式我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话可以分为两种静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码再对其编译。在程序员运行之前代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。5.4.4静态代理Static Proxy第一步创建服务类接口publicinterface BuyHouse {voidbuyHouse();}第二步实现服务接口publicclassBuyHouseImpl implements BuyHouse {OverridepublicvoidbuyHouse() {System.out.println(我要买房);}}第三步创建代理类publicclassBuyHouseProxy implements BuyHouse {privateBuyHouse buyHouse;publicBuyHouseProxy(final BuyHouse buyHouse) {this.buyHouse buyHouse;}OverridepublicvoidbuyHouse() {System.out.println(买房前准备);buyHouse.buyHouse();System.out.println(买房后装修);}}第四步编写测试类publicclassHouseApp {publicstaticvoidmain(String[] args) {BuyHouse buyHouse newBuyHouseImpl();BuyHouseProxy buyHouseProxy newBuyHouseProxy(buyHouse);buyHouseProxy.buyHouse();}}静态代理总结优点可以做到在符合开闭原则的情况下对目标对象进行功能扩展。缺点我们得为每一个服务创建代理类工作量太大不易管理。同时接口一旦发生改变代理类也得相应修改。5.4.5 JDK动态代理Dynamic Proxy在动态代理中我们不再需要再手动的创建代理类我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK在运行时为我们动态的来创建。第一步创建服务类接口代码和上例一样第二步实现服务接口代码和上例一样第三步编写动态处理器publicclassDynamicProxyHandler implements InvocationHandler {privateObject object;publicDynamicProxyHandler(final Object object) {this.object object;}OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(买房前准备);Object result method.invoke(object, args);System.out.println(买房后装修);returnresult;}}篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho第四步编写测试类publicclassHouseApp {publicstaticvoidmain(String[] args) {BuyHouse buyHouse newBuyHouseImpl();BuyHouse proxyBuyHouse (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(),newClass[]{BuyHouse.class},newDynamicProxyHandler(buyHouse));proxyBuyHouse.buyHouse();}}Proxy是所有动态生成的代理的共同的父类这个类有一个静态方法Proxy.newProxyInstance()接收三个参数ClassLoader loader指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class?[] interfaces指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler指定动态处理器执行目标对象的方法时,会触发事件处理器的方法JDK动态代理总结优点相对于静态代理动态代理大大减少了开发任务同时减少了对业务接口的依赖降低了耦合度。缺点Proxy是所有动态生成的代理的共同的父类因此服务类必须是接口的形式不能是普通类的形式因为Java无法实现多继承。5.4.6 CGLib动态代理CGLib ProxyJDK实现动态代理需要实现类通过接口定义业务方法对于没有接口的类如何实现动态代理呢这就需要CGLib了。CGLib采用了底层的字节码技术其原理是通过字节码技术为一个类创建子类并在子类中采用方法拦截的技术拦截所有父类方法的调用顺势织入横切逻辑。但因为采用的是继承所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。Cglib子类代理实现方法1引入cglib的jar文件asm的jar文件2代理的类不能为final3目标业务对象的方法如果为final/static那么就不会被拦截即不会执行目标对象额外的业务方法第一步创建服务类publicclassBuyHouse2 {publicvoidbuyHouse() {System.out.println(我要买房);}}第二步创建CGLIB代理类publicclassCglibProxy implements MethodInterceptor {privateObject target;publicCglibProxy(Object target) {this.target target;}/*** 给目标对象创建一个代理对象* return 代理对象*/publicObject getProxyInstance() {//1.工具类Enhancer enhancer newEnhancer();//2.设置父类enhancer.setSuperclass(target.getClass());//3.设置回调函数enhancer.setCallback(this);//4.创建子类(代理对象)returnenhancer.create();}publicObject intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println(买房前准备);//执行目标对象的方法Object result method.invoke(target, args);System.out.println(买房后装修);returnresult;}}第三步创建测试类publicclassHouseApp {publicstaticvoidmain(String[] args) {BuyHouse2 target newBuyHouse2();CglibProxy cglibProxy newCglibProxy(target);BuyHouse2 buyHouseCglibProxy (BuyHouse2) cglibProxy.getProxyInstance();buyHouseCglibProxy.buyHouse();}}CGLib代理总结CGLib创建的动态代理对象比JDK创建的动态代理对象的性能更高但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象因为无需频繁创建对象用CGLIB合适反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法对于final修饰的方法无法进行代理。5.4.7 简述动态代理的原理 常用的动态代理的实现方式动态代理的原理:使用一个代理将对象包装起来然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调 用转到原始对象上动态代理的方式基于接口实现动态代理 JDK动态代理基于继承实现动态代理 Cglib、Javassist动态代理第6章MySql6.1jdbc 操作数据库流程第一步Class.forName()加载数据库连接驱动第二步DriverManager.getConnection()获取数据连接对象;第三步根据SQL 获取 sql 会话对象有 2 种方式 Statement、PreparedStatement ;第四步执行SQL 处理结果集执行 SQL 前如果有参数值就设置参数值 setXXX();第五步关闭结果集、关闭会话、关闭连接。6.2关系数据库中连接池的机制是什么前提为数据库连接建立一个缓冲池。1从连接池获取或创建可用连接2使用完毕之后把连接返回给连接池3在系统关闭前断开所有连接并释放连接占用的系统资源4能够处理无效连接限制连接池中的连接总数不低于或者不超过某个限定值。其中有几个概念需要大家理解最小连接数是连接池一直保持的数据连接。如果应用程序对数据库连接的使用量不大将会有大量的数据库连接资源被浪费掉。最大连接数是连接池能申请的最大连接数。如果数据连接请求超过此数后面的数据连接请求将被加入到等待队列中这会影响之后的数据库操作。如果最小连接数与最大连接数相差太大那么最先的连接请求将会获利之后超过最小连接数量的连接请求等价于建立一个新的数据库连接。不过这些大于最小连接数的数据库连接在使用完不会马上被释放它将被放到连接池中等待重复使用或是空闲超时后被释放。上面的解释可以这样理解数据库池连接数量一直保持一个不少于最小连接数的数量当数量不够时数据库会创建一些连接直到一个最大连接数之后连接数据库就会等待。6.3SQL 的select 语句完整的执行顺序SQL Select 语句完整的执行顺序1from 子句组装来自不同数据源的数据2where 子句基于指定的条件对记录行进行筛选3group by 子句将数据划分为多个分组4使用聚集函数进行计算5使用 having 子句筛选分组6计算所有的表达式7select 的字段8使用order by 对结果集进行排序。6.3MySQL的事务事务的基本要素ACID原子性Atomicity事务开始后所有操作要么全部做完要么全部不做不可能停滞在中间环节。事务执行过程中出错会回滚到事务开始前的状态所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体就像化学中学过的原子是物质构成的基本单位一致性Consistency事务开始前和结束后数据库的完整性约束没有被破坏 。比如A向B转账不可能A扣了钱B却没收到。隔离性Isolation同一时间只允许一个事务请求同一数据不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱在A取钱的过程结束前B不能向这张卡转账。持久性Durability事务完成后事务对数据库的所有更新将被保存到数据库不能回滚。事务的并发问题脏读事务A读取了事务B更新的数据然后B回滚操作那么A读取到的数据是脏数据不可重复读事务 A 多次读取同一数据事务 B 在事务A多次读取的过程中对数据作了更新并提交导致事务A多次读取同一数据时结果 不一致幻读系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级但是系统管理员B就在这个时候插入了一条具体分数的记录当系统管理员A改结束后发现还有一条记录没有改过来就好像发生了幻觉一样这就叫幻读。小结不可重复读的和幻读很容易混淆不可重复读侧重于修改幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行解决幻读需要锁表MySQL事务隔离级别事务隔离级别 脏读 不可重复读 幻读读未提交read-uncommitted 是 是 是不可重复读read-committed 否 是 是可重复读repeatable-read 否 否 是串行化serializable 否 否 否6.4行锁表锁MyISAMInnoDB行表锁表锁即使操作一条记录也会锁住整个表不适合高并发的操作行锁,操作时只锁某一行不对其它行有影响适合高并发的操作6.5索引数据结构BTree一般来说能够达到range就可以算是优化了口诀全值匹配我最爱最左前缀要遵守带头大哥不能死中间兄弟不能断索引列上少计算范围之后全失效LIKE百分写最右覆盖索引不写*不等空值还有OR索引影响要注意VAR引号不可丢SQL优化有诀窍。