Java中sleep和wait的区别
① 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。 sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。 ② 锁: 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。 Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。 ③ 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。 synchronized(x){ x.notify() //或者wait() }
JAVA中的几种基本类型,各占用多少字节?
下图单位是bit,非字节 1B=8bit
String能被继承吗?为什么?
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。平常我们定义的String str=”a”;其实和String str=new String(“a”)还是有差异的。
前者默认调用的是String.valueOf来返回String实例对象,至于调用哪个则取决于你的赋值,比如String num=1,调用的是
public static String valueOf(int i) {
return Integer.toString(i);
}
后者则是调用如下部分:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
最后我们的变量都存储在一个char数组中
private final char value[];
String, Stringbuffer, StringBuilder 的区别。
String 字符串常量(final修饰,不可被继承),String是常量,当创建之后即不能更改。(可以通过StringBuffer和StringBuilder创建String对象(常用的两个字符串操作类)。)
StringBuffer 字符串变量(线程安全),其也是final类别的,不允许被继承,其中的绝大多数方法都进行了同步处理,包括常用的Append方法也做了同步处理(synchronized修饰)。其自jdk1.0起就已经出现。其toString方法会进行对象缓存,以减少元素复制开销。
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder 字符串变量(非线程安全)其自jdk1.5起开始出现。与StringBuffer一样都继承和实现了同样的接口和类,方法除了没使用synch修饰以外基本一致,不同之处在于最后toString的时候,会直接返回一个新对象。
public String toString() {
// Create a copy, don’t share the array
return new String(value, 0, count);
}
ArrayList 和 LinkedList 有什么区别。
ArrayList和LinkedList都实现了List接口,有以下的不同点:
1、ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
2、相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
3、LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
讲讲类的实例化顺序,
父类静态代变量--->父类静态代码块--->子类静态变量--->子类静态代码块---->父类非静态变量(父类实例成员变量)--->
父类构造函数--->子类非静态变量(子类实例成员变量)--->子类构造函数。
测试demo:http://blog.csdn.net/u014042066/article/details/77574956
参阅我的博客《深入理解类加载》:http://blog.csdn.net/u014042066/article/details/77394480
用过哪些 Map 类,都有什么区别
hashMap是线程不安全的,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,采用哈希表来存储的,
参照该链接:https://zhuanlan.zhihu.com/p/21673805
JAVA8 的 ConcurrentHashMap 为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
参照:https://yq.aliyun.com/articles/36781
有没有有顺序的 Map 实现类, 如果有, 他们是怎么保证有序的。
TreeMap和LinkedHashMap是有序的(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。
参照:http://uule.iteye.com/blog/1522291
抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现。abstract void abc();不能写成abstract void abc(){}。
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
继承和聚合的区别在哪。
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识,在设计时一般没有争议性;
聚合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即has-a的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享;比如计算机与CPU、公司与员工的关系等;表现在代码层面,和关联关系是一致的,只能从语义级别来区分;
参考:http://www.cnblogs.com/jiqing9006/p/5915023.html
讲讲你理解的 nio和 bio 的区别是啥,谈谈 reactor 模型。
IO是面向流的,NIO是面向缓冲区的
参考:https://zhuanlan.zhihu.com/p/23488863
http://developer.51cto.com/art/201103/252367.htm
http://www.jianshu.com/p/3f703d3d804c
反射的原理,反射创建类实例的三种方式是什么
参照:http://www.jianshu.com/p/3ea4a6b57f87?amp
http://blog.csdn.net/yongjian1092/article/details/7364451
反射中,Class.forName 和 ClassLoader 区别。
https://my.oschina.net/gpzhang/blog/486743
描述动态代理的几种实现方式,分别说出相应的优缺点。
Jdk cglib jdk底层是利用反射机制,需要基于接口方式,这是由于
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
Cglib则是基于asm框架,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于jdk
http://lrd.ele.me/2017/01/09/dynamic_proxy/
动态代理与 cglib 实现的区别
同上(基于invocationHandler和methodInterceptor)
为什么 CGlib 方式可以对接口实现代理。
同上
final 的用途
类、变量、方法
http://www.importnew.com/7553.html
写出三种单例模式实现。
懒汉式单例,饿汉式单例,双重检查等
参考:https://my.oschina.net/dyyweb/blog/609021
如何在父类中为子类自动完成所有的 hashcode 和 equals 实现?这么做有何优劣。
同时复写hashcode和equals方法,优势可以添加自定义逻辑,且不必调用超类的实现。
参照:http://java-min.iteye.com/blog/1416727
谈谈访问修饰符 public、private、protected、default 在应用设计中的作用。
访问修饰符,主要标示修饰块的作用域,方便隔离防护
同一个类 同一个包 不同包的子类 不同包的非子类
- 1
- 2
Private √
Default √ √
Protected √ √ √
Public √ √ √ √
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不
仅可以跨类访问,而且允许跨包(package)访问。
private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的类、属性以
及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
protect: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护形”。被其修饰的类、
属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
default:即不加任何访问修饰符,通常称为“默认访问模式“。该模式下,只允许在同一个包中进行访
问。
深拷贝和浅拷贝区别。
http://www.oschina.net/translate/java-copy-shallow-vs-deep-in-which-you-will-swim
数组和链表数据结构描述,各自的时间复杂度
http://blog.csdn.net/snow_wu/article/details/53172721
error 和 exception 的区别,CheckedException,RuntimeException 的区别
http://blog.csdn.net/woshixuye/article/details/8230407
请列出 5 个运行时异常。
同上
在自己的代码中,如果创建一个 java.lang.String 对象,这个对象是否可以被类加载器加载?为什么
类加载无须等到“首次使用该类”时加载,jvm允许预加载某些类。。。。
http://www.cnblogs.com/jasonstorm/p/5663864.html
Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法。
参考上边试题
在 jdk1.5 中,引入了泛型,泛型的存在是用来解决什么问题。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率
http://baike.baidu.com/item/java%E6%B3%9B%E5%9E%8B
这样的 a.hashcode() 有什么用,与 a.equals(b)有什么关系。
hashcode
hashcode()方法提供了对象的hashCode值,是一个native方法,返回的默认值与System.identityHashCode(obj)一致。
通常这个值是对象头部的一部分二进制位组成的数字,具有一定的标识对象的意义存在,但绝不定于地址。
作用是:用一个数字来标识对象。比如在HashMap、HashSet等类似的集合类中,如果用某个对象本身作为Key,即要基于这个对象实现Hash的写入和查找,那么对象本身如何实现这个呢?就是基于hashcode这样一个数字来完成的,只有数字才能完成计算和对比操作。
hashcode是否唯一
hashcode只能说是标识对象,在hash算法中可以将对象相对离散开,这样就可以在查找数据的时候根据这个key快速缩小数据的范围,但hashcode不一定是唯一的,所以hash算法中定位到具体的链表后,需要循环链表,然后通过equals方法来对比Key是否是一样的。
equals与hashcode的关系
equals相等两个对象,则hashcode一定要相等。但是hashcode相等的两个对象不一定equals相等。
https://segmentfault.com/a/1190000004520827
有没有可能 2 个不相等的对象有相同的 hashcode。
有
Java 中的 HashSet 内部是如何工作的。
底层是基于hashmap实现的
http://wiki.jikexueyuan.com/project/java-collection/hashset.html
什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
http://www.importnew.com/17964.html
Java中HashMap和HashTable的区别
① 历史原因: Hashtable是给予陈旧的Dictonary类的, HashMap是Java1.2引进的Map接口的一个实现 ② HashMap允许空的键值对, 而HashTable不允许 ③ HashTable同步,而HashMap非同步,效率上比HashTable要高
Java中synchronized 和 ReentrantLock 有什么不同?
相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。
区别:
这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。
什么情况下会发生栈内存溢出。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error异常。 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
参照:http://wiki.jikexueyuan.com/project/java-vm/storage.html
JVM 的内存结构,Eden 和 Survivor 比例。
eden 和 survior 是按8比1分配的
http://blog.csdn.net/lojze_ly/article/details/49456255
jvm 中一次完整的 GC 流程是怎样的,对象如何晋升到老年代,说说你知道的几种主要的jvm 参数。
对象诞生即新生代->eden,在进行minor gc过程中,如果依旧存活,移动到from,变成Survivor,进行标记代数,如此检查一定次数后,晋升为老年代,
http://www.cnblogs.com/redcreen/archive/2011/05/04/2037056.html
http://ifeve.com/useful-jvm-flags/
https://wangkang007.gitbooks.io/jvm/content/jvmcan_shu_xiang_jie.html
你知道哪几种垃圾收集器,各自的优缺点,重点讲下 cms,包括原理,流程,优缺点
Serial、parNew、ParallelScavenge、SerialOld、ParallelOld、CMS、G1
https://wangkang007.gitbooks.io/jvm/content/chapter1.html
垃圾回收算法的实现原理。
http://www.importnew.com/13493.html
当出现了内存溢出,你怎么排错。
首先分析是什么类型的内存溢出,对应的调整参数或者优化代码。
https://wangkang007.gitbooks.io/jvm/content/4jvmdiao_you.html
JVM 内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等。
内存屏障:为了保障执行顺序和可见性的一条cpu指令
重排序:为了提高性能,编译器和处理器会对执行进行重拍
happen-before:操作间执行的顺序关系。有些操作先发生。
主内存:共享变量存储的区域即是主内存
工作内存:每个线程copy的本地内存,存储了该线程以读/写共享变量的副本
http://ifeve.com/java-memory-model-1/
http://www.jianshu.com/p/d3fda02d4cae
http://blog.csdn.net/kenzyq/article/details/50918457
简单说说你了解的类加载器。
类加载器的分类(bootstrap,ext,app,curstom),类加载的流程(load-link-init)
http://blog.csdn.net/gjanyanlig/article/details/6818655/
讲讲 JAVA 的反射机制。
Java程序在运行状态可以动态的获取类的所有属性和方法,并实例化该类,调用方法的功能
http://baike.baidu.com/link?url=C7p1PeLa3ploAgkfAOK-4XHE8HzQuOAB7K5GPcK_zpbAa_Aw-nO3997K1oir8N–1_wxXZfOThFrEcA0LjVP6wNOwidVTkLBzKlQVK6JvXYvVNhDWV9yF-NIOebtg1hwsnagsjUhOE2wxmiup20RRa#7
你们线上应用的 JVM 参数有哪些。
-server
Xms6000M
-Xmx6000M
-Xmn500M
-XX:PermSize=500M
-XX:MaxPermSize=500M
-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0
-Xnoclassgc
-XX:+DisableExplicitGC
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=90
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log
g1 和 cms 区别,吞吐量优先和响应优先的垃圾收集器选择。
Cms是以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。比较占用cpu资源,切易造成碎片。
G1是面向服务端的垃圾收集器,是jdk9默认的收集器,基于标记-整理算法实现。可利用多核、多cpu,保留分代,实现可预测停顿,可控。
http://blog.csdn.net/linhu007/article/details/48897597
请解释如下 jvm 参数的含义:
-server -Xms512m -Xmx512m -Xss1024K
-XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=20
XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly。
Server模式启动
最小堆内存512m
最大512m
每个线程栈空间1m
永久代256
最大永久代256
最大转为老年代检查次数20
Cms回收开启时机:内存占用80%
命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期
开源框架知识
简单讲讲 tomcat 结构,以及其类加载器流程。
Server- –多个service
Container级别的:–>engine–》host–>context
Listenter
Connector
Logging、Naming、Session、JMX等等
通过WebappClassLoader 加载class
http://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/
http://blog.csdn.net/dc_726/article/details/11873343
http://www.cnblogs.com/xing901022/p/4574961.html
http://www.jianshu.com/p/62ec977996df
tomcat 如何调优,涉及哪些参数。
硬件上选择,操作系统选择,版本选择,jdk选择,配置jvm参数,配置connector的线程数量,开启gzip压缩,trimSpaces,集群等
http://blog.csdn.net/lifetragedy/article/details/7708724
讲讲 Spring 加载流程。
通过listener入口,核心是在AbstractApplicationContext的refresh方法,在此处进行装载bean工厂,bean,创建bean实例,拦截器,后置处理器等。
https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/
讲讲 Spring 事务的传播属性。
七种传播属性。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
https://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/
Spring 如何管理事务的。
编程式和声明式
同上
Spring 怎么配置事务(具体说出一些关键的 xml 元素)。
说说你对 Spring 的理解,非单例注入的原理?它的生命周期?循环注入的原理, aop 的实现原理,说说 aop 中的几个术语,它们是怎么相互工作的。
核心组件:bean,context,core,单例注入是通过单例beanFactory进行创建,生命周期是在创建的时候通过接口实现开启,循环注入是通过后置处理器,aop其实就是通过反射进行动态代理,pointcut,advice等。
Aop相关:http://blog.csdn.net/csh624366188/article/details/7651702/
Springmvc 中 DispatcherServlet 初始化过程。
入口是web.xml中配置的ds,ds继承了HttpServletBean,FrameworkServlet,通过其中的init方法进行初始化装载bean和实例,initServletBean是实际完成上下文工作和bean初始化的方法。
http://www.mamicode.com/info-detail-512105.html
Java运行时内存是如何划分的
Java虚拟机在执行Java程序的时候会把他所管理的内存划分为若干个不同的数据区域,各个区域有各自的用途,以及创建和销毁的时间。有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户线程的启动和结束而创建和销毁。
Java虚拟机会把运行时的数据区域分为以下几个区域:
程序计数器:
程序计数器是一块很小的内存空间,代表着当前线程执行的字节码的行号指示器,记录着所执行的行号,java虚拟机执行的是由后缀名为.java的文件编译而来的.class文件(字节码文件),所以字节码解释器根据程序计数器来执行字节码文件。每个线程都有自己的程序解释器,这样才能保证程序的正确执行,也就是说程序计数器是线程私有的。
Java虚拟机栈:
java虚拟机栈是用来描述java方法的内存模型,每个方法在执行的同时都会创建一个栈帧,而这个栈帧存储的是方法中的局部变量,操作的数据,方法的入口返回值等信息,当一个方法被调用的时候,就代表着栈帧的入栈直至方法的结束代表着栈帧的出栈。因为虚拟机栈存储的数据决定了他也是线程私有的,每个线程都拥有一个虚拟机栈记录着方法的内容。我们平时所说的栈就是指的是虚拟机栈,其中存储着基本数据类型和指向堆内存中对象的指针(对象的引用)。
本地方法栈:
这块区域和虚拟机栈执行的操作其实是一致的,但是他们之间的服务对象不一样,虚拟机栈为java方法服务,而本地方法栈为native方法服务,我们在看源码的时候经常了一看到用native关键字修饰的方法,这种方法的实现是用c/c++实现的,我们在平时是看不到他的源码实现的。
Java堆:
堆内存是这几块内存区域中最大的一块,堆内存存在的目的是存放对象的实例(通过new创建的对象,对象的引用放在虚拟机栈中指向堆中的实例),在虚拟机启动的时候堆内存也就被创建了,这块内存被所有线程共享,在虚拟机运行期间的所有线程创建的对象的实例都被存储在堆内存中。既然堆被线程所共享,那么线程创建的对象不能一直存放在这里,总会有装不下的时候,在一定条件下,java虚拟机会触发垃圾回收机制(GC),来回收这里被看作没有用的对象,虚拟机所管理的垃圾回收器总是会对这块区域进行管理操作。关于垃圾回收(GC)机制可以看另一篇文章:Java GC垃圾回收机制
方法区:
方法区和堆内存一样,是各个线程共享的数据区域,看到这个方法区这个名字很快能想到这个区域存方法信息,事实上方法区存放的数据很多,包括被虚拟机加载的类信息,用final修饰的常量,String对象,用static修饰的静态变量。
运行时常量池:
准确的说这块区域属于方法区,也就受到了方法区的空间限制,之前所说的String对象,就是字符串常量就是存放在这里,编译期生成各种字面值和符号引用,将在类价在后放入方法区的运行时常量池的。运行时常量池的存储具有动态性,并不是在类加载时才能放入数据,在程序运行期间也可以有新的常量放入。String类中的intern()方法就是这种特点,详看之前转载的一篇文章:String的intern()方法详解
直接内存:
这块区域和java中的新的io方式(NIO)有关,不属于虚拟机的运行时数据区。NIO是一种基于通道,缓冲区的io方式。
XSS,CSRF,SQL注入
XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。比如这些代码包括HTML代码和客户端脚本。攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击“,而JavaScript是新型的“ShellCode”。
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 [1]比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击.
java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐?
有两种实现方法,分别是继承Thread类与实现Runnable接口用synchronized关键字修饰同步方法反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被“挂起”的线程恢复运行。
对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。
请简述在异常当中,throw和throws有什么区别
① throw代表动作,表示抛出一个异常的动作;throws代表一种状态,代表方法可能有异常抛出
② throw用在方法实现中,而throws用在方法声明中
③ throw只能用于抛出一种异常,而throws可以抛出多个异常
forward和redirect的区别
forward: A -> B -> C
redirect: A -> B A -> C
1.从地址栏显示来说
forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.
redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.
2.从数据共享来说
forward:转发页面和转发到的页面可以共享request里面的数据.
redirect:不能共享数据.
3.从运用地方来说
forward:一般用于用户登陆的时候,根据角色转发到相应的模块.
redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等.
4.从效率来说
forward:高.
redirect:低.
关于内存溢出的小结
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
java为什么要重写hashCode和equals方法?
如果不被重写(原生)的hashCode和equals是什么样的?
- 不被重写(原生)的hashCode值是根据内存地址换算出来的一个值。
- 不被重写(原生)的equals方法是严格判断一个对象是否相等的方法(object1 == object2)。
为什么需要重写equals和hashCode方法?
在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了
所以这个时候我们需要重写equals方法,来满足我们的业务系统上的需求。那么为什么在重写equals方法的时候需要重写hashCode方法呢?
我们先来看一下Object.hashCode的通用约定(摘自《Effective Java》第45页)
1.在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
2.如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
3.如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)
同时对于HashSet和HashMap这些基于散列值(hash)实现的类。HashMap的底层处理机制是以数组的方法保存放入的数据的(Node<K,V>[] table),其中的关键是数组下标的处理。数组的下标是根据传入的元素hashCode方法的返回值再和特定的值异或决定的。
如果该数组位置上已经有放入的值了,且传入的键值相等则不处理,若不相等则覆盖原来的值,如果数组位置没有条目,则插入,并加入到相应的链表中。检查键是否存在也是根据hashCode值来确定的。所以如果不重写hashCode的话,可能导致HashSet、HashMap不能正常的运作、
如果我们将某个自定义对象存到HashMap或者HashSet及其类似实现类中的时候,如果该对象的属性参与了hashCode的计算,那么就不能修改该对象参数hashCode计算的属性了。有可能会移除不了元素,导致内存泄漏。
扩展:
在重写equals方法的时候,需要遵守下面的通用约定:
1、自反性。
对于任意的引用值x,x.equals(x)一定为true。
2、对称性。
对于任意的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)也一定返回true。
3、传递性。
对于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。
4、一致性。
对于任意的引用值x和y,如果用于equals比较的对象没有被修改的话,那么,对此调用x.equals(y)要么一致地返回true,要么一致的返回false。
5、对于任意的非空引用值x,x.equals(null)一定返回false。
重写hashCode方法的大致方式:
a、把某个非零常数值,比如说17(最好是素数),保存在一个叫result的int类型的变量中。
b、对于对象中每一个关键域f(值equals方法中考虑的每一个域),完成一些步骤:
1、为该域计算int类型的散列吗c:
1)、如果该域是boolean类型,则计算(f?0:1)。
2)、如果该域是byte、char、short或者int类型,则计算(int)f。
3)、如果该域是float类型,则计算Float.floatToIntBits(f)。
4)、如果该域是long类型,则计算(int)(f ^ (f>>>32))。
5)、如果该域是double类型,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后按照步骤4,对该long型值计算散列值。
6)、如果该域是一个对象引用,并且该类的equals方法通过递归调用equals的方式来比较这个域,则同样对这个域递归调用hashCode。如果要求一个更为复杂的比较,则为这个域计算一个“规范表示”,然后针对这个范式表示调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数)
7)、如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤下面的做法把这些散列值组合起来。
2、按照下面的公式,把步骤1中计算得到的散列码C组合到result中:
result = 31*result+c。
c、返回result。
d、写完hashCode方法之后,问自己“是否相等的实例具有相等的散列码”。如果不是的话,找出原因,并修改。
可以通过org.apache.commons.lang.builder.HashCodeBuilder这个工具类来方便的重写hashCode方法。
关于枚举的小结
枚举类型的那些事:https://www.cnblogs.com/hyl8218/p/5088287.html
枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。
HTTP返回码分析
301:- 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302:- 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
304:- 如果客户端发送了一个带条件的GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个304状态码..
403:- 代表客户端错误,指的是服务器端有能力处理该请求,但是拒绝授权访问。
404:- 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500:- 服务器内部错误,无法完成请求
Java程序中启动一个线程是用run()还是start()?
1.start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码:
通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。
然后通过此Thread类调用方法run()来完成其运行操作的,这里方法run()称为线程体,它包含了要执行的这个线程的内容,
Run方法运行结束,此线程终止,而CPU再运行其它线程。
2.run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码:
而如果直接用Run方法,这只是调用一个方法而已,程序中依然只有主线程--这一个线程,其程序执行路径还是只有一条,
这样就没有达到写线程的目的。
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际就是在操作系统中启动了一个进程。
在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。
线程状态说明
线程状态从大的方面来说,可归结为:初始状态、可运行状态、不可运行状态和消亡状态,具体可细分为上图所示7个状态,说明如下:
1)线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,当我们new了thread实例后,线程就进入了初始状态;
2)当该对象调用了start()方法,就进入可运行状态;
3)进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4)进入运行状态后case就比较多,大致有如下情形: ﹒run()方法或main()方法结束后,线程就进入终止状态; 当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停 止当前线程,但并不释放所占有的资源)。当
sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片; 当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被锁牢(synchroniza,lock),将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线
程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配 CPU时间片; 当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,
是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒
有所线程),线程被唤醒后会进入锁池,等待获取锁标记。 当线程调用stop方法,即可使线程进入消亡状态,但是由于stop方法是不安全的,不鼓励使用,大家可以通过run方法里的条件变通实现线程的 stop。
Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?
- byte——1 byte——Byte
- short——2 bytes——Short
- int——4 bytes——Integer
- long——8 bytes——Long
- float——4 bytes——Float
- double——8 bytes——Double
- char——2 bytes——Character
- boolean——————Boolean
boolean数据类型非true即false。
这个数据类型表示1 bit,但是它的大小并没有精确定义。
《Java虚拟机规范》中如是说:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1个字节。
那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗?
实际上,使用int的原因是,对于当下32位的CPU来说,一次进行32位的数据交换更加高效。
综上,我们可以知道:官方文档对boolean类型没有给出精确的定义,《Java虚拟机规范》给出了“单独时使用4个字节,boolean数组时1个字节”的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是一种时空权衡。 boolean类型的封装类是Boolean。
位运算符的基本用法
案例演示 >>,>>>,<<的用法:
<<:左移左边最高位丢弃,右边补齐0
>>:右移最高位是0,左边补齐0;最高为是1,左边补齐1
>>>:无符号右移 无论最高位是0还是1,左边补齐0
最有效率的算出2 * 8的结果
//最有效率的算出2 * 8的结果
System.out.println(2 << 3);
谈一谈”==“与”equals()"的区别。
《Think in Java》中说:“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。 "=="判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型(它们的变量存储的是值本身,而引用类型变量存储的是引用);equals是Object类的方法,Object对它的实现是比较内存地址,我们可以重写这个方法来自定义“相等”这个概念。比如类库中的String、Date等类就对这个方法进行了重写。 综上,对于枚举类型和原始数据类型的相等性比较,应该使用"==";对于引用类型的相等性比较,应该使用equals方法。
Java中的四种引用及其应用场景是什么?
- 强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用
- 软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
- 弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收
- 虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。
object中定义了哪些方法?
- clone()
- equals()
- hashCode()
- toString()
- notify()
- notifyAll()
- wait()
- finalize()
- getClass()
hashCode的作用是什么?
请参考散列表的基本原理与实现
ArrayList, LinkedList, Vector的区别是什么?
- ArrayList:内部采用数组存储元素,支持高效随机访问,支持动态调整大小
- LinkedList:内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问
- Vector: 可以看作线程安全版的ArrayList
String, StringBuilder, StringBuffer的区别是什么?
- String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象
- StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)
- StringBuffer: 可以看作线程安全版的StringBuilder
Map, Set, List, Queue、Stack的特点及用法。
- Map<k, v="">:Java中存储键值对的数据类型都实现了这个接口,表示“映射表”。支持的两个核心操作是get(Object key)以及put(K key, V value),分别用来获取键对应的值以及向映射表中插入键值对。</k,>
- Set<e>:实现了这个接口的集合类型中不允许存在重复的元素,代表数学意义上的“集合”。它所支持的核心操作有add(E e), remove(Object o), contains(Object o),分别用于添加元素,删除元素以及判断给定元素是否存在于集中。
- List<e>: Java中集合框架中的列表类型都实现了这个接口,表示一种有序序列。支持get(int index), add(E e)等操作。
- Queue<e>:Java集合框架中的队列接口,代表了“先进先出”队列。支持add(E element),remove()等操作。
- Stack<e>:Java集合框架中表示堆栈的数据类型,堆栈是一种“后进先出”的数据结构。支持push(E item), pop()等操作。
更详细的说明请参考官方文档,对相关数据结构不太熟悉的同学可以参考《算法导论》或其他相关书籍。
HashMap和HashTable的区别
- HashTable是线程安全的,而HashMap不是
- HashMap中允许存在null键和null值,而HashTable中不允许
HashMap的实现原理
简单的说,HashMap的底层实现是“基于拉链法的散列表”。
详细分析请参考 Map源码解析之HashMap源码分析
ConcurrentHashMap的实现原理
ConcurrentHashMap是支持并发读写的HashMap,它的特点是读取数据时无需加锁,写数据时可以保证加锁粒度尽可能的小。由于其内部采用“分段存储”,只需对要进行写操作的数据所在的“段”进行加锁。关于ConcurrentHashMap底层实现的详细分析请参考 Java并发编程:并发容器之ConcurrentHashMap
TreeMap, LinkedHashMap, HashMap的区别是什么?
- HashMap的底层实现是散列表,因此它内部存储的元素是无序的;
- TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。
- LinkedHashMap可以看作能够记住插入元素的顺序的HashMap。
Collection与Collections的区别是什么?
- Collection<e>是Java集合框架中的基本接口;
- Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。
对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?
会执行。只有两种情况finally块中的语句不会被执行:
- 调用了System.exit()方法;
- JVM“崩溃”了。
Java中的异常层次结构
Java中的异常层次结构如下图所示:
我们可以看到Throwable类是异常层级中的基类。
Error类表示内部错误,这类错误使我们无法控制的;Exception表示异常,RuntimeException及其子类属于未检查异常,这类异常包括ArrayIndexOutOfBoundsException、NullPointerException等,我们应该通过条件判断等方式语句避免未检查异常的发生。IOException及其子类属于已检查异常,编译器会检查我们是否为所有可能抛出的已检查异常提供了异常处理器,若没有则会报错。对于未检查异常,我们无需捕获(当然Java也允许我们捕获,但我们应该做的事避免未检查异常的发生)。
Java面向对象的三个特征与含义
三大特征:封装、继承、多态。
Override, Overload的含义与区别
- Override表示“重写”,是子类对父类中同一方法的重新定义
- Overload表示“重载”,也就是定义一个与已定义方法名称相同但签名不同的新方法
接口与抽象类的区别
- 接口是一种约定,实现接口的类要遵循这个约定;
- 抽象类本质上是一个类,使用抽象类的代价要比接口大。
- 接口与抽象类的对比如下:
抽象类中可以包含属性,方法(包含抽象方法与有着具体实现的方法),常量;接口只能包含常量和方法声明。
抽象类中的方法和成员变量可以定义可见性(比如 public、private等);而接口中的方法只能为public(缺省为public)。
一个子类只能有一个父类(具体类或抽象类);而一个接口可以继承一个多个接口,一个类也可以实现多个接口。
子类中实现父类中的抽象方法时,可见性可以大于等于父类中的;而接口实现类中的接口 方法的可见性只能与接口中相同(public)。
静态内部类与非静态内部类的区别
静态内部类不会持有外围类的引用,而非静态内部类会隐式持有外围类的一个引用。
Java中多态的实现原理
所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。详细介绍请戳 Java动态绑定的内部实现机制
简述Java中创建新线程的两种方法
- 继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。
- 实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。
简述Java中进行线程同步的方法
- volatile: Java Memory Model保证了对同一个volatile变量的写happens before对它的读;
- synchronized:可以来对一个代码块或是对一个方法上锁,被“锁住”的地方称为临界区,进入临界区的线程会获取对象的monitor,这样其他尝试进入临界区的线程会因无法获取monitor而被阻塞。由于等待另一个线程释放monitor而被阻塞的线程无法被中断。
- ReentrantLock: 尝试获取锁的线程可以被中断并可以设置超时参数。
简述Java中具有哪几种粒度的锁
Java中可以对类、对象、方法或是代码块上锁。
给出“生产者-消费者”问题的一种解决方案
使用阻塞队列:
public class BlockingQueueTest { private int size = 20; private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(size); public static void main(String[] args) { BlockingQueueTest test = new BlockingQueueTest(); Producer producer = test.new Producer(); Consumer consumer = test.new Consumer(); producer.start(); consumer.start(); } class Consumer extends Thread{ @Override public void run() { while(true){ try { //从阻塞队列中取出一个元素 queue.take(); System.out.println("队列剩余" + queue.size() + "个元素"); } catch (InterruptedException e) { } } } } class Producer extends Thread{ @Override public void run() { while (true) { try { //向阻塞队列中插入一个元素 queue.put(1); System.out.println("队列剩余空间:" + (size - queue.size())); } catch (InterruptedException e) { } } } } }
ThreadLocal的设计理念与作用
ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。ThreadLocal最常用于以下这个场景:多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这时候可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。
concurrent包的整体架构
ArrayBlockingQueue, CountDownLatch类的作用
- CountDownLatch:允许线程集等待直到计数器为0。适用场景: 当一个或多个线程需要等待指定数目的事件发生后再继续执行。
- ArrayBlockingQueue: 一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。通过阻塞队列,我们可以按以下模式来工作:工作者线程可以周期性的将中间结果放入阻塞队列中,其它线程可以取出中间结果并进行进一步操作。若工作者线程的执行比较慢(还没来得及向队列中插入元素),其他从队列中取元素的线程会等待它(试图从空队列中取元素从而阻塞);若工作者线程执行较快(试图向满队列中插入元素),则它会等待其它线程取出元素再继续执行。
wait(),sleep() 的区别
- wait():Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。
- sleep():Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。
线程池的用法与优势
- 优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。
- 用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。
for-each与常规for循环的效率对比
关于这个问题我们直接看《Effective Java》给我们做的解答:
for-each能够让代码更加清晰,并且减少了出错的机会。 下面的惯用代码适用于集合与数组类型:
for (Element e : elements) {
doSomething(e);
}
使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上,在有些场合下它还能带来微小的性能提升,因为它只计算一次数组索引的上限。
简述Java IO与NIO的区别
Java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。
Java IO是阻塞IO,而NIO是非阻塞IO。
Java NIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情。
反射的作用与原理
反射的作用概括地说是运行时获取类的各种定义信息,比如定义了哪些属性与方法。原理是通过类的class对象来获取它的各种信息。
Java中的泛型机制
关于泛型机制的详细介绍请直接戳 Java核心技术点之泛型
常见设计模式
所谓“设计模式”,不过是面向对象编程中一些常用的软件设计手法,并且经过实践的检验,这些设计手法在各自的场景下能解决一些需求,因此它们就成为了如今广为流传的”设计模式“。也就是说,正式因为在某些场景下产生了一些棘手的问题,才催生了相应的设计模式。明确了这一点,我们在学习某种设计模式时要充分理解它产生的背景以及它所解决的主要矛盾是什么。
常用的设计模式可以分为以下三大类:
- 创建型模式: 包括工厂模式(又可进一步分为简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式、单例模式。
- 结构型模式: 包括适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式。
- 行为型模式: 包括命令模式、中介者模式、观察者模式、状态模式、策略模式。
注解的基本概念与使用
注解可以看作是“增强版的注释”,它可以向编译器、虚拟机说明一些事情。 注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。注解本身是“被动”的信息,只有主动解析它才有意义。 除了向编译器/虚拟机传递信息,我们也可以使用注解来生成一些“模板化”的代码。
HashMap的那些事
1.1、HashMap的实现原理
1.1.1、结构
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。如下图所示:
image.png
当新建一个HashMap的时候,就会初始化一个数组。哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。这些元素一般情况是通过hash(key)%len的规则存储到数组中,也就是元素的key的哈希值对数组长度取模得到。
1.1.2、核心变量
image.png
1.1.3、put存储逻辑
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标), 如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
这里有一个特殊的地方。在JDK1.6中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
红黑树
红黑树和平衡二叉树(AVL树)类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树和AVL树的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡。
1.1.4、get读取逻辑
从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
如果第一个节点是TreeNode,说明采用的是数组+红黑树结构处理冲突,遍历红黑树,得到节点值。
1.1.5、归纳
简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Node 对象。HashMap 底层采用一个 Node<K,V>[] 数组来保存所有的 key-value 对,当需要存储一个 Node 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Node。
1.1.6、HashMap的resize(rehash)
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,在对HashMap数组进行扩容时,就会出现性能问题:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过160.75=12的时候,就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
1.2、HashMap在并发场景下的问题和解决方案
1.2.1、多线程put后可能导致get死循环
问题原因就是HashMap是非线程安全的,多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。
当另外一个线程get 这个Entry List 死循环的key的时候,这个get也会一直执行。最后结果是越来越多的线程死循环,最后导致服务器dang掉。我们一般认为HashMap重复插入某个值的时候,会覆盖之前的值,这个没错。但是对于多线程访问的时候,由于其内部实现机制(在多线程环境且未作同步的情况下,对同一个HashMap做put操作可能导致两个或以上线程同时做rehash动作,就可能导致循环键表出现,一旦出现线程将无法终止,持续占用CPU,导致CPU使用率居高不下),就可能出现安全问题了。
为HashMap以链表组形式存在,初始数组16位(为何16位,又是一堆移位算法,下一篇文章再写吧),如果长度超过75%,长度增加一倍,多线程操作的时候,恰巧两个线程插入的时候都需要扩容,形成了两个链表,这时候读取,size不一样,报错了。其实这时候报错都是好事,至少不会陷入死循环让cpu死了,有种情况,假如两个线程在读,还有个线程在写,恰巧扩容了,这时候你死都不知道咋死的,直接就是死循环,假如你是双核cpu,cpu占用率就是50%,两个线程恰巧都进入死循环了,得!中奖了。
1.2.2、多线程put的时候可能导致元素丢失
主要问题出在addEntry方法的new Entry (hash, key, value, e),如果两个线程都同时取得了e,则他们下一个元素都是e,然后赋值给table元素的时候有一个成功有一个丢失。
1.2.3、解决方案
ConcurrentHashMap替换HashMap
Collections.synchronizedMap将HashMap包装起来
1.3、ConcurrentHashMap PK HashTable
ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。
从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。
以空间换时间的结构,跟分布式缓存结构有点像,创建的时候,内存直接分为了16个segment,每个segment实际上还是存储的哈希表(Segment其实就是一个HashMap ),写入的时候,先找到对应的segment,然后锁这个segment,写完,解锁,锁segment的时候,其他segment还可以继续工作。
ConcurrentHashMap如此的设计,优势主要在于: 每个segment的读写是高度自治的,segment之间互不影响。这称之为“锁分段技术”;
二、线程,多线程,线程池的那些事
2.1、线程的各个状态及切换
Java中的线程的生命周期大体可分为5种状态:新建、可运行、运行、阻塞、死亡。
1、新建(NEW):新创建了一个线程对象。
2、可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3、运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4、阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。
阻塞的情况分三种:
1)等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
2)同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
3)其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5、死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
几个方法的比较:
1)Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。
2)Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
3)t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
4)obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
5)obj.notify(),唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
2.2、多线程的实现方式Thread、Runnable、Callable
继承Thread类,实现Runnable接口,实现Callable接口。
这三种方法的介绍和比较:
1、实现Runnable接口相比继承Thread类有如下优势:
1)可以避免由于Java的单继承特性而带来的局限
2)增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
3)适合多个相同程序代码的线程去处理同一资源的情况
2、实现Runnable接口和实现Callable接口的区别
1)Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
2)实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的任务线程不能返回结果
3)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛
4)加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法
注:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取返回结果,当不调用此方法时,主线程不会阻塞
2.3、线程池原理和运行机制
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。
在ThreadPoolExecutor类中提供了四个构造方法,主要参数包括下面的参数:
1、int corePoolSize:核心池的大小。
线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。这里需要注意的是:在刚刚创建ThreadPoolExecutor的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads事先启动核心线程。再考虑到keepAliveTime和allowCoreThreadTimeOut超时参数的影响,所以没有任务需要执行的时候,线程池的大小不一定是corePoolSize。
2、int maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程,注意与corePoolSize区分。
线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。
3、long keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
4、TimeUnit unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性。
5、BlockingQueue<Runnable> workQueue:一个阻塞队列,用来存储等待执行的任务。
6、ThreadFactory threadFactory:线程工厂,主要用来创建线程。
7、RejectedExecutionHandler handler:表示当拒绝处理任务时的策略。
还有一个成员变量比较重要:poolSize
线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止。同一时刻,poolSize不会超过maximumPoolSize。
2.4、线程池对任务的处理
1、如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
2、如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务(maximumPoolSize);
3、如果当前线程池中的线程数目达到maximumPoolSize(此时线程池的任务缓存队列已满),则会采取任务拒绝策略进行处理;
任务拒绝策略,通常有以下四种策略:
1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
4、如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
2.5、线程池的状态
1、线程池的状态说明:
RUNNING(运行):接受新任务并处理排队任务。
SHUTDOWN(关闭):不接受新任务,会处理队列任务。
STOP(停止):不接受新任务,不处理队列任务,并且中断进程中的任务。
TIDYING(整理):所有任务都已终止,工作计数为零,线程将执行terminated()方法终止线程。
TERMINATED(已终止):terminated()方法已完成。
2、各个状态之间的转换
RUNNING -> SHUTDOWN:调用shutdown()方法。
RUNNING or SHUTDOWN-> STOP:调用shutdownNow()方法。
SHUTDOWN -> TIDYING:当队列和池均都为空时。
STOP -> TIDYING:当池为空时。
TIDYING -> TERMINATED:当terminated()方法已完成。
线程和进程有什么区别?
进程和线程的区别:
对于进程来说,子进程是父进程的复制品,从父进程那里获得父进程的数据空间,堆和栈的复制品。
而线程,相对于进程而言,是一个更加接近于执行体的概念,可以和同进程的其他线程之间直接共享数据,而且拥有自己的栈空间,拥有独立序列。
共同点: 它们都能提高程序的并发度,提高程序运行效率和响应时间。线程和进程在使用上各有优缺点。 线程执行开销比较小,但不利于资源的管理和保护,而进程相反。同时,线程适合在SMP机器上运行,而进程可以跨机器迁移。
他们之间根本区别在于 多进程中每个进程有自己的地址空间,线程则共享地址空间。所有其他区别都是因为这个区别产生的。比如说:
1. 速度。线程产生的速度快,通讯快,切换快,因为他们处于同一地址空间。
2. 线程的资源利用率好。
3. 线程使用公共变量或者内存的时候需要同步机制,但进程不用。
而他们通信方式的差异也仍然是由于这个根本原因造成的。
通信方式之间的差异
因为那个根本原因,实际上只有进程间需要通信,同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。
而进程间通信无论是信号,管道pipe还是共享内存都是由操作系统保证的,是系统调用.
进程间的通信方式
管道( pipe ):
线程间的通信方式
- 锁机制:包括互斥锁、条件变量、读写锁
- 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
- 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
- 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
三、JVM的那些事
3.1、JVM的结构
每个JVM都包含:方法区、Java堆、Java栈、本地方法栈、程序计数器等。
image.png
1、方法区:共享
各个线程共享的区域,存放类信息、常量、静态变量。
2、Java堆:共享
也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此Java堆的空间也是最大的。如果Java堆空间不足了,程序会抛出OutOfMemoryError异常。
3、Java栈:私有
每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个Java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈。如果Java栈空间不足了,程序会抛出*Error异常,想一想什么情况下会容易产生这个错误,对,递归,递归如果深度很深,就会执行大量的方法,方法越多Java栈的占用空间越大。
4、本地方法栈:私有
角色和Java栈类似只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件交互的目的。
5、程序计数器:私有
说到这里我们的类已经加载了,实例对象、方法、静态变量都去了自己该去的地方,那么问题来了,程序该怎么执行,哪个方法先执行,哪个方法后执行,这些指令执行的顺序就是程序计数器在管,它的作用就是控制程序指令的执行顺序。
6、执行引擎当然就是根据PC寄存器调配的指令顺序,依次执行程序指令。
3.2、Java堆的介绍及典型的垃圾回收算法介绍
3.2.1、Java堆的介绍
Java堆是虚拟机管理的最大的一块内存,堆上的所有线程共享一块内存区域,在启动虚拟机时创建。此内存唯一目的就是存放对象实例,几乎所有对象实例都在这里分配,这一点Java虚拟机规范中的描述是:所有对象实例及数组都要在堆上分配。
Java堆是垃圾收集器管理的主要区域,也被称为“GC堆”,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以分为:新生代和老年代。
堆的内存模型大致为:
image.png
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小,老年代 ( Old ) = 2/3 的堆空间大小。
其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to以示区分。 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。 因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
新生代是 GC 收集垃圾的频繁区域。
当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳 ( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,然后清理所使用过的 Eden 以及 Survivor 区域 ( 即 from 区域 ),并且将这些对象的年龄设置为1,以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,当对象的年龄达到某个值时 ( 默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设定 ),这些对象就会成为老年代。
但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
From Survivor区域与To Survivor区域是交替切换空间,在同一时间内两者中只有一个不为空。
3.2.2、如何确定某个对象是可回收的(垃圾)
1、引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。
这种方式的问题是无法解决循环引用的问题,当两个对象循环引用时,就算把两个对象都设置为null,因为他们的引用计数都不为0,这就会使他们永远不会被清除。
2、根搜索算法(可达性分析)
为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
比较常见的将对象视为可回收对象的原因:
显式地将对象的唯一强引用指向新的对象。
显式地将对象的唯一强引用赋值为Null。
局部引用所指向的对象(如,方法内对象)。
只有弱引用与其关联的对象。
1)强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 ps:强引用其实也就是我们平时A a = new A()这个意思。
2)软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3)弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4)虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
3.2.3、典型的垃圾回收算法介绍
1、标记-清除算法(Mark-Sweep)
最基础的垃圾回收算法,分为“标注”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
标记过程:为了能够区分对象是live的,可以为每个对象添加一个marked字段,该字段在对象创建的时候,默认值是false。
清除过程:去遍历堆中所有对象,并找出未被mark的对象,进行回收。与此同时,那些被mark过的对象的marked字段的值会被重新设置为false,以便下次的垃圾回收。
缺点:效率低,空间问题(产生大量不连续的内存碎片),后续可能发生大对象不能找到可利用空间的问题。
image.png
2、复制算法(Copying)——新生代的收集算法就是这种,但是比例不是1:1,而是(8+1):1
为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为大小相等的两块,每次只使用其中一块。当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存空间一次清理掉。这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。
image.png
3、标记-整理算法(Mark-Compact)——老年代的收集算法
结合了以上两个算法,标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将所有存活对象移向内存的一端,然后清除端边界外的对象。如图:
image.png
4、分代收集算法(Generational Collection)
分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。
老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
目前大部分JVM的GC对于新生代都采取复制算法(Copying),因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
老年代中的对象存活率高,没有额外空间对它进行分配,使用“标记-清理”或“标记-整理”算法来进行回收。
3.3、JVM处理FULLGC经验
3.3.1、内存泄漏
1、产生原因
1)JVM内存过小。
2)程序不严密,产生了过多的垃圾。
2、一般情况下,在程序上的体现为:
1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
3)代码中存在死循环或循环产生过多重复的对象实体。
4)使用的第三方软件中的BUG。
5)启动参数内存值设定的过小。
3.3.2、Java内存泄漏的排查案例
1、确定频繁Full GC现象,找出进程唯一ID
使用jps(jps -l)或ps(ps aux | grep tomat)找出这个进程在本地虚拟机的唯一ID(LVMID,Local Virtual Machine Identifier)
2、再使用“虚拟机统计信息监视工具:jstat”(jstat -gcutil 20954 1000)查看已使用空间站总空间的百分比,可以看到FGC的频次。
image.png
3、找出导致频繁Full GC的原因,找出出现问题的对象
分析方法通常有两种:
1)把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。
2)更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件)。
jmap命令格式:jmap -histo:live 20954
4、一个工具:BTrace,没有使用过
HTTP、HTTPS、协议相关
5.1、HTTP请求报文和响应报文
image.png
5.2、HTTPS为什么是安全的?HTTPS的加密方式有哪些?
5.2.1、HTTPS的工作原理说明HTTPS是安全的
image.png
客户端在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
1、客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
2、Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
3、客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
4、客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
5、Web服务器利用自己的私钥解密出会话密钥。
6、Web服务器利用会话密钥加密与客户端之间的通信。
image.png
5.2.2、HTTPS的加密方式有哪些?
1、对称加密
对称加密是指加密和解密使用相同密钥的加密算法。它要求发送方和接收方在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信至关重要。
2、更多的加密方式的了解
5.3、TCP三次握手协议,四次挥手
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
完成三次握手,主机A与主机B开始传送数据。
5.4、OAuth协议介绍
image.png
5.5、防盗链Referer
Referer请求头: 代表当前访问时从哪个网页连接过来的。
当Referer未存在或者是从其他站点访问我们资源的时候就直接重定向到我们的主页,这样既可以防止我们的资源被窃取。
六、Spring
6.1、AOP的实现原理
spring框架对于这种编程思想的实现基于两种动态代理模式,分别是JDK动态代理 及 CGLIB的动态代理,这两种动态代理的区别是 JDK动态代理需要目标对象实现接口,而 CGLIB的动态代理则不需要。下面我们通过一个实例来实现动态代理,进而帮助我们理解面向切面编程。
JDK的动态代理要使用到一个类 Proxy 用于创建动态代理的对象,一个接口 InvocationHandler用于监听代理对象的行为,其实动态代理的本质就是对代理对象行为的监听。
6.2、Spring MVC工作原理
Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。
6.2.1、SpringMVC原理图
image.png
6.2.2、SpringMVC运行原理
1、客户端请求提交到DispatcherServlet
2、由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller
3、DispatcherServlet将请求提交到Controller
4、Controller调用业务逻辑处理后,返回ModelAndView
5、DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图
6、视图负责将结果显示到客户端
6.2.3、SpringMVC核心组件
1、DispatcherServlet:*控制器,把请求给转发到具体的控制类
2、Controller:具体处理请求的控制器
3、HandlerMapping:映射处理器,负责映射*处理器转发给controller时的映射策略
4、ModelAndView:服务层返回的数据和视图层的封装类
5、ViewResolver:视图解析器,解析具体的视图
6、Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作
6.2.4、Servlet 生命周期
Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
1、Servlet 通过调用 init () 方法进行初始化。
2、Servlet 调用 service() 方法来处理客户端的请求。
3、Servlet 通过调用 destroy() 方法终止(结束)。
4、最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
6.2.5、Spring容器初始化过程
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配号Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
image.png
七、分布式
7.1、分布式下如何保证事务一致性
分布式事务,常见的两个处理办法就是两段式提交和补偿。
7.1.1、两段式提交
分布式事务将提交分成两个阶段:
prepare;
commit/rollback
在分布式系统中,每个节点虽然可以知晓自己的操作是成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(参与者)的操作结果并最终指示这些节点是否需要把操作结果进行真正的提交。算法步骤如下:
第一阶段:
1、协调者会问所有的参与者,是否可以执行提交操作。
2、各个参与者开始事务执行的准备工作,如:为资源上锁,预留资源。
3、参与者响应协调者,如果事务的准备工作成功,则回应“可以提交”,否则回应“拒绝提交”。
第二阶段:
1、如果所有的参与者都回应“可以提交”。那么协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交并释放所有资源,然后回应“完成”,协调者收集各节点的“完成”回应后结束这个Global Transaction
2、如果有一个参与者回应“拒绝提交”,那么协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各节点的“回滚”回应后,取消这个Global Transaction。
7.1.2、三段式提交
三段提交的核心理念是:在询问的时候并不锁定资源,除非所有人都同意了,才开始锁资源。他把二段提交的第一个段break成了两段:询问,然后再锁资源。最后真正提交。
7.1.2、事务补偿,最终一致性
补偿比较好理解,先处理业务,然后定时或者回调里,检查状态是不是一致的,如果不一致采用某个策略,强制状态到某个结束状态(一般是失败状态)。
九、中间件和架构
9.1、kafka消息队列
1、避免数据丢失
producer:
加大重试次数
同步发送
对于单条数据过大,要设置可接收的单条数据的大小
对于异步发送,通过回调函数来感知丢消息
block.on.buffer.full = true
consumer:
enable.auto.commit=false 关闭自动提交位移
2、避免消息乱序
假设a,b两条消息,a先发送后由于发送失败重试,这时顺序就会在b的消息后面,可以设置max.in.flight.requests.per.connection=1来避免。
max.in.flight.requests.per.connection:限制客户端在单个连接上能够发送的未响应请求的个数,设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求,但吞吐量会下降。
3、避免消息重复
使用第三方redis的set
9.2、ZooKeeper的原理
9.3、SOA相关,RPC两种实现方式:基于HTTP和基于TCP
9.4、Netty
java虚拟机
什么时候会触发full gc
- System.gc()方法的调用
- 老年代空间不足
- 永生区空间不足(JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据)
- GC时出现promotion failed和concurrent mode failure
- 统计得到的Minor GC晋升到旧生代平均大小大于老年代剩余空间
- 堆中分配很大的对象
JAVA 线程状态转换
JAVA 线程状态转换图示
synchronized 的底层怎么实现
- 同步代码块(Synchronization)基于进入和退出管程(Monitor)对象实现。每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
- 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
- 被 synchronized 修饰的同步方法并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成
B树和B+树的区别
- B树,每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为nul,叶子结点不包含任何关键字信息。
- B+树,所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接,所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 (而B 树的非终节点也包含需要查找的有效信息)
为什么说B+比B树更适合实际应用中操作系统的文件索引和数据库索引?
- B+的磁盘读写代价更低 B+的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。
- B+-tree的查询效率更加稳定 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
JVM,JRE,JDK的区别
JVM :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心,具体的下文会详细说明。
JRE :英文名称(Java Runtime Environment),我们叫它:Java 运行时环境。它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。
JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。
借一张图简单说明一下
然后是前端技术一些概念,虽然现在基本是前后端分离的模式开发,但也需要理解一些基础吧!
javascript==js
js是一种脚本语言,在html中,css管理位置,html主管内容,而js主管动作,减轻后台的操作,可以很简单的就实现对输入的数据的验证。比如说注册的时候,用js中的ajax到后台实时验证本用户名是否已经被注册,验证码是否正确或者用来实现异步更新,为用户带来更好的体验。用jquery来验证密码的合法性,输入密码与确认密码是否一样,从而反馈到html页面上,可以通过操控css或者html页面内容来确认输入内容是否有错。但是ajax和jquery都是js的一个库。
js!=jsp
js(javascript) jsp(java server pages)
以前在知乎里面看到了一句相当经典的回答,js与jsp的关系就是雷锋和雷峰塔的关系。从这句话可以看出它们俩没有任何联系,而实际上也是这样。jsp其实可以看做一种脚本语言,需要用servlet来编译实现,然而作为一种脚本语言它有相当强大,在其中可以嵌入java代码,jsp中几乎可以使用全部的java类 。其实就是可以把jsp当做html来作为网页显示出来,而且其上还可以嵌套java语言,也可以嵌套其他的语言类似,当然都序言用servlet来编译实现。jsp作为字节码文件执行后可以直接运行,不必每次都要编译,速度快。可能我表述还是有点问题,但是jsp和js大多都应用于web网页的编写上,jsp可以看做html和java的结合体,当然js就可以在jsp上实现一些动作,特效,验证功能,与在html中所实现的效果一样。因为jsp是在服务器端解释执行的,服务器执行转化后的.class程序,客户端接收到的只是服务器发来的html代码,看不到jsp代码。而js可以在客户端通过查看源代码显示出来。
java、jsp
java是一种编程语言,jsp只是相当于java里面的servlet部分
后言
记得刚开始开发网站的时候,我的一个学长叫我去看jsp,然后我就用了一个星期看js去了,我还以为js就是jsp,然后就jj了,javascript和jsp也没差多少,所以做了很多的无用功,多走了不该走的弯路,不过后来两个东西都用上了,而且用处都还非常大。
重要的事情说三遍,
js!=jsp,js!=jsp,js!=jsp。
js==javascript
jsp==java server pages
待补充答案的部分
基础题目
- Java线程的状态
问了对Java的Map了解吗,请说出其中的几个接口,怎么获取key,怎么判断是否包含key,哪些实现了Map接口,map的hash以及数据结构,1.7和1.8的区别等。
2、写一个栈的实现,用数组,确保最大队列长度为k(我当时问了满了要不要什么策略),第一次我用front和end两个指针,然后他问我一个变量可以不(我说的不可以),然后我说front + size(队列中的元素)可以,然后手写。
3、问了一些juc并发库有哪些类(futrue,futruetask,excutors,executorthreadpool,countdownlatch,cyclicbarrier,symphone等)
4、怎么确保当所有线程执行到某个点等待,直到所有线程都执行到时一起往下执行(cyclicbarrier)。
5、cyclicbarrier和countdownlatch有什么区别,以及应用场景。
怎么查看是哪一条SQL执行慢(这个之前在面拼多多的时候也被问到过,但楼主没去看,被自己不善于总结坑了),slow_query_log,long_query_time,slow_query_log_file,log_queries_not_using_indexes这个命令大家可以去看下
4、ACID的意义(原子性,一致性,隔离性,持久性,当时脑子懵逼了,只说出了两个)
5、数据库的四种隔离级别,怎么避免脏读
6、hashmap和currenthashmap的数据结构,volatile了解吗,写个volatile的应用场景(我就说了单列的双重检验)
7、jdk1.8的特性。
9、还有什么我没有问到的吗(我说我什么方向的都有一点研究),然后面试官看了下学历,渣渣本科,好吧不问了。
- 进程和线程的区别,进程间如何通讯,线程间如何通讯
- HashMap的数据结构是什么?如何实现的。和HashTable,ConcurrentHashMap的区别
- Cookie和Session的区别
- 索引有什么用?如何建索引?
- ArrayList是如何实现的,ArrayList和LinkedList的区别?ArrayList如何实现扩容。
- equals方法实现
- 面向对象
- 线程状态,BLOCKED和WAITING有什么区别
- JVM如何加载字节码文件
- JVM GC,GC算法。
- 什么情况会出现Full GC,什么情况会出现yong GC。
- JVM内存模型
- Java运行时数据区
- 事务的实现原理
- Java读取一个文件, 有哪些方法, 考虑性能, 用哪一个类
- BIO, NIO, AIO区别
- Java 类加载机制
- 介绍线程安全的list(copyonwritelist)
- 线程安全的map,具体是怎么实现的,与Hashtable什么区别。
- 为什么使用双亲委派模型
- NIO的组件, Selector
- 什么是序列化, IO的序列化方式, 为什么需要序列化(包括在网络传输的情况下)
- Error和Exception区别
- RuntimeException和非RuntimeException区别
- 什么叫线程安全, 保证线程安全的方法
- 加锁的方式, synchronized作用在方法上和代码块区别
- synchronized(this)和synchronized(Xx.class)区别
- 知道的线程池(ThreadPoolExecutor属于一类,forkjoin知道吗)
- 线程池怎么自己去实现?给你一个Runnable、一个容器怎么实现。
- AQS中线程等待怎么做的(线程挂起状态怎么做的)?
- 阻塞非阻塞的区别?
- 异步和同步的区别?异步是使用的内核级线程还是用户态线程。
- AIO在Java中应用到了吗?
1. junit用法,before,beforeClass,after, afterClass的执行顺序
2. 分布式锁
3. nginx的请求转发算法,如何配置根据权重转发
4. 用hashmap实现redis有什么问题(死锁,死循环,可用ConcurrentHashmap)
5. 线程的状态
5. 线程的阻塞的方式
6. sleep和wait的区别
7. hashmap的底层实现
8. 一万个人抢100个红包,如何实现(不用队列),如何保证2个人不能抢到同一个红包,可用分布式锁
9. java内存模型,垃圾回收机制,不可达算法
10. 两个Integer的引用对象传给一个swap方法在方法内部交换引用,返回后,两个引用的值是否会发现变化
11. aop的底层实现,动态代理是如何动态,假如有100个对象,如何动态的为这100个对象代理
12. 是否用过maven install。 maven test。git(make install是安装本地jar包)
13. tomcat的各种配置,如何配置docBase
14. spring的bean配置的几种方式
15. web.xml的配置
16. spring的监听器。
17. zookeeper的实现机制,有缓存,如何存储注册服务的
18. IO会阻塞吗?readLine是不是阻塞的
19. 用过spring的线程池还是java的线程池?
20. 字符串的格式化方法 (20,21这两个问题问的太低级了)
21. 时间的格式化方法
22. 定时器用什么做的
23. 线程如何退出结束
24. java有哪些锁?乐观锁 悲观锁 synchronized 可重入锁 读写锁,用过reentrantlock吗?reentrantlock与synmchronized的区别
25. ThreadLocal的使用场景
26. java的内存模型,垃圾回收机制
27. 为什么线程执行要调用start而不是直接run(直接run,跟普通方法没什么区别,先调start,run才会作为一个线程方法运行)
28. qmq消息的实现机制(qmq是去哪儿网自己封装的消息队列)
29. 遍历hashmap的三种方式
30. jvm的一些命令
31. memcache和redis的区别
32. mysql的行级锁加在哪个位置
33. ConcurrentHashmap的锁是如何加的?是不是分段越多越好
34. myisam和innodb的区别(innodb是行级锁,myisam是表级锁)
35. mysql其他的性能优化方式
36. linux系统日志在哪里看
37. 如何查看网络进程
38. 统计一个整数的二进制表示中bit为1的个数
39. jvm内存模型,java内存模型
40. 如何把java内存的数据全部dump出来
41. 如何手动触发全量回收垃圾,如何立即触发垃圾回收
42. hashmap如果只有一个写其他全读会出什么问题
43. git rebase
44. mongodb和hbase的区别
45. 如何解决并发问题
46. volatile的用途
47. java线程池(好像之前我的理解有问题)
48. mysql的binlog
49. 代理模式
50. mysql是如何实现事务的
51. 读写分离何时强制要读主库,读哪个从库是通过什么方式决定的,从库的同步mysql用的什么方式
52. mysql的存储引擎
53. mysql的默认隔离级别,其他隔离级别
54. 将一个链表反转(用三个指针,但是每次只发转一个)
55. spring Aop的实现原理,具体说说
56. 何时会内存泄漏,内存泄漏会抛哪些异常
57. 是否用过Autowire注解
58. spring的注入bean的方式
59. sql语句各种条件的执行顺序,如select, where, order by, group by
60. select xx from xx where xx and xx order by xx limit xx; 如何优化这个(看explain)
61. 四则元算写代码
62. 统计100G的ip文件中出现ip次数最多的100个ip
63. zookeeper的事物,结点,服务提供方挂了如何告知消费方
64. 5台服务器如何选出leader(选举算法)
65. 适配器和代理模式的区别
66. 读写锁
67. static加锁
68. 事务隔离级别
69. 门面模式,类图(外观模式)
70. mybatis如何映射表结构
71. 二叉树遍历
72. 主从复制
73. mysql引擎区别
74. 静态内部类加载到了哪个区?方法区
75. class文件编译后加载到了哪
76. web的http请求如何整体响应时间变长导致处理的请求数变少,该如何处理?用队列,当处理不了那么多http请求时将请求放到队列
中慢慢处理,web如何实现队列
77. 线程安全的单例模式
78. 快速排序性能考虑
79. volatile关键字用法
80. 求表的size,或做数据统计可用什么存储引擎
81. 读多写少可用什么引擎
82. 假如要统计多个表应该用什么引擎
83. concurrenhashmap求size是如何加锁的,如果刚求完一段后这段发生了变化该如何处理
84. 1000个苹果放10个篮子,怎么放,能让我拿到所有可能的个数
85. 可重入的读写锁,可重入是如何实现的?
86. 是否用过NIO
87. java的concurrent包用过没
88. sting s=new string("abc")分别在堆栈上新建了哪些对象
89. java虚拟机的区域分配,各区分别存什么
90. 分布式事务(JTA)
91. threadlocal使用时注意的问题(ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享)
92. java有哪些容器(集合,tomcat也是一种容器)
93. 二分查找算法
94. myisam的优点,和innodb的区别
95. redis能存哪些类型
96. http协议格式,get和post的区别
97. 可重入锁中对应的wait和notify
98. redis能把内存空间交换进磁盘中吗(这个应该是可以的,但是那个面试官非跟我说不可以)
99. java线程池中基于缓存和基于定长的两种线程池,当请求太多时分别是如何处理的?定长的事用的队列,如果队列也满了呢?交换进磁盘?基于缓存的线程池解决方法呢?
100. synchronized加在方法上用的什么锁
101. 可重入锁中的lock和trylock的区别
102. innodb对一行数据的读会枷锁吗?不枷锁,读实际读的是副本
103. redis做缓存是分布式存的?不同的服务器上存的数据是否重复?guava cache呢?是否重复?不同的机器存的数据不同
104. 用awk统计一个ip文件中top10
105. 对表做统计时可直接看schema info信息,即查看表的系统信息
106. mysql目前用的版本
107. 公司经验丰富的人给了什么帮助?(一般boss面会问这些)
108. 自己相对于一样的应届生有什么优势
109. 自己的好的总结习惯给自己今后的工作带了什么帮助,举例为证
110. 原子类,线程安全的对象,异常的处理方式
111. 4亿个int数,如何找出重复的数(用hash方法,建一个2的32次方个bit的hash数组,每取一个int数,可hash下2的32次方找到它在hash数组中的位置,然后将bit置1表示已存在)
112. 4亿个url,找出其中重复的(考虑内存不够,通过hash算法,将url分配到1000个文件中,不同的文件间肯定就不会重复了,再分别找出重复的)
有1万个数组,每个数组有1000个整数,每个数组都是降序的,从中找出最大的N个数,N<1000
113. LinkedHashmap的底层实现
114. 类序列化时类的版本号的用途,如果没有指定一个版本号,系统是怎么处理的?如果加了字段会怎么样?
115. Override和Overload的区别,分别用在什么场景
116. java的反射是如何实现的
技术深度
- 有没有看过JDK源码,看过的类实现原理是什么。
- HTTP协议
- TCP协议
- 一致性Hash算法
- JVM如何加载字节码文件
- 类加载器如何卸载字节码
- IO和NIO的区别,NIO优点
- Java线程池的实现原理,keepAliveTime等参数的作用。
- HTTP连接池实现原理
- HTTP1.0与HTTP1.1区别
- TCP与UDP区别
- 数据库连接池实现原理
- 数据库的实现原理
技术框架
- 看过哪些开源框架的源码
- 为什么要用Redis,Redis有哪些优缺点?Redis如何实现扩容?
- Netty是如何使用线程池的,为什么这么使用
- 为什么要使用Spring,Spring的优缺点有哪些
- Spring的IOC容器初始化流程
- Spring的IOC容器实现原理,为什么可以通过byName和ByType找到Bean
- Spring AOP实现原理
- 消息中间件是如何实现的,技术难点有哪些
- Spring AOP实现原理
- 一个页面, 一个提交按钮, 如何防止重复提交, 我说验证码, 还有其它方式吗?
- ajax实现跨域请求(前端或者后台实现方法)
- cookie和session区别
- forward和redirect区别
- redis
- rabbitMq
- memcached等的使用场景和设计原理
- redis部署方式。
- redis怎么加入节点
- redis持久化
手撕字符串转int
2.数据库事务隔离级别
3.spring 事务
4.数据库索引失效的场景
5.组合索引(A,B)若查询B列是否用到了索引
6.如果A是string类型,而查询的时候是1314151617 用到了索引吗?能查到这条数据吗?
7.数据库怎么实现乐观锁?
8.redis pipeline了解吗?
9.秒杀场景?怎么实现。redis怎么限流,限流算法。
10.redis实现队列、实现优先级队列。
11.分布式锁。
系统架构
- 如何搭建一个高可用系统
- 哪些设计模式可以增加系统的可扩展性
- 介绍设计模式,如模板模式,命令模式,策略模式,适配器模式、桥接模式、装饰模式,观察者模式,状态模式,访问者模式。
- 抽象能力,怎么提高研发效率。
- 什么是高内聚低耦合,请举例子如何实现
- 什么情况用接口,什么情况用消息
- 如果AB两个系统互相依赖,如何解除依赖
- 如何写一篇设计文档,目录是什么
- 什么场景应该拆分系统,什么场景应该合并系统
- 系统和模块的区别,分别在什么场景下使用
分布式系统
- 分布式事务,两阶段提交。
- 如何实现分布式锁
- 如何实现分布式Session
- 如何保证消息的一致性
- 负载均衡
- 正向代理(客户端代理)和反向代理(服务器端代理)
- CDN实现原理
- 怎么提升系统的QPS和吞吐量
- 你用过的JVM命令有哪些
- 如果一个程序发生OOM, 该怎么处理
- JVM内存模型
- JDK7与JDK8的JVM内存模型区别
- 回收算法
- 回收器
- 虚拟机client和server的区别
- 对象和引用之间一般用什么连接?句柄池的作用是什么。
实战能力
- 有没有处理过线上问题?出现内存泄露,CPU利用率标高,应用无响应时如何处理的。
- 开发中有没有遇到什么技术问题?如何解决的
- 如果有几十亿的白名单,每天白天需要高并发查询,晚上需要更新一次,如何设计这个功能。
- 新浪微博是如何实现把微博推给订阅者
- Google是如何在一秒内把搜索结果返回给用户的。
- 12306网站的订票系统如何实现,如何保证不会票不被超卖。
- 如何实现一个秒杀系统,保证只有几位用户能买到某件商品。
软能力
- 如何学习一项新技术,比如如何学习Java的,重点学习什么
- 有关注哪些新的技术
- 工作任务非常多非常杂时如何处理
- 项目出现延迟如何处理
- 和同事的设计思路不一样怎么处理
- 如何保证开发质量
- 职业规划是什么?短期,长期目标是什么
- 团队的规划是什么
- 能介绍下从工作到现在自己的成长在那里?
0.基础部分
无论是哪一种编程语言,基础永远是你不能忽视的部分。以下是比较常出现的十个点,当然最好是全都能熟悉。
- 使用length属性获取数组长度,public、private、protected、friendly区别
- 最有效率的方法算2*8等于几
- 两个对象值相同,x.equal(y)==true,但是却可有不同的hashcode,这句话对不对。
- Collection和Collections区别
- Set里面的元素不能重复,用什么方法区分重复与否。
- 给出一个常见的runtime exception。
- try{}里有一个return语句,紧跟在try后的finally里的code会不会被执行,什么时候执行,return前执行还是return后执行。4、short s1;
- s1=s1+1;是否有错?
- 7.运行时异常和一般异常的区别
- Java中的异常处理机制的简单原理和应用
1.Java高级部分
- 写出单例模式。
- 写出一种11位手机号的正则表达式
- 写出知道的设计模式。
- Webservice介绍。
- tcp/ip协议三次握手。
- Ajax请求是否可以实现同步。
- 隐藏URL方式。
- 简单概括取到一个网页内容的实现步骤,(简单来说就是爬虫),考察逻辑思维能力。
- 简述form表单提交post方法与get方法在字符编码、http协议方面的区别。
- 一个http请求从开始到结束都经历了哪些过程,简写流程图。
- Spring框架以及数据库和JVM三个方面,也会交流到分布式、线程池的实现
1、Java内存结构,spring的aop的实现方法,java数据库问题定位和性能调优;
2、关于Java异常的续承层次结构,讲述异常的续承关系;
3、java中重载和重写有什么区别,分别用什么关键字;
4、关于分布式消息队列,分布式缓存;
5、关于hashmap源码实现, jdk
6、关于设计模式,uml,jvm 内存回收机制问题
7、java线程如何启动?java中加锁的方式有哪些,怎么个写法?
8、对乐观锁和悲观锁的理解;
9、ORACLE中的SQL如何进行优化,都有哪些方式?事务有哪些特性,在ORACLE中隔离有哪些级别?
10、介绍一下自己最近做的一个典型的项目;
11、在项目中遇到了哪些问题,自己是如何解决的 ;
12、目前系统支撑的用户量是多少,假如用户量提升10倍,系统会出现什么样的问题,如何重新设计系统【这里主要是想了解您的问题预见能力以及问题解决能力,考查思路】
13、使用memcached是一个什么样的原理
14、如何存放数据到memcached集群中,介绍一下这个过程。跟进的问题,讲一下一致性哈希算法的实现原理。
15、JVM中堆是如何管理的,JVM的内存回收机制,介绍一下
16、分布式事务实现方式
17、热点账户问题(项目中有就会问)
阿里技术面试(电面)涉及Java基础点(可参考):
- session
- java锁
- gc原理
- hashmap
- listlink arraylist 区别
- aop 原理
- 多线程
- kafka 原理和容错
- spark hadoop 原理
- redis 同步机制
- classLoader 机制
- Http 协议
- cookie的限制
- HashMap key的要求 , hashcode一致两个对象是否相等
- JVM的原理
- 涉及面较广,包括谈到项目上遇到的问题,以此深入探讨。
- JMS的两种模式是那些;
- 富客户端怎么更新;
- 自己设计类似ESB这样的企业数据总线应该有哪些功能;
- 系统架构设计;
- 集群中怎么控制session;
- 怎么样知道java存中是哪一些模块消耗内存比较多;
- 应用服务器的参数调整。
- 人选介绍目前目前技术管理侧重;
- 对阿里推荐对应岗位的理解度;
- 系统设计是怎么样的;
- 做的一些架构,数据源来自于哪里并深入问;
- 模块边界,缓存机制;
- 选用技术选型时,A和B的区别;
- 多线程如何用,有什么好处,和进程的区别;
- 多线程的过程;
- 多线程同步的方法,X项目中如何去实现;
- 分布式架构在你目前项目中的体现;
- 跨主机通信用过什么,接口等;
- 大数据存储;
- 介绍自己的工作职责、技术or管理怎么分配的
- 介绍项目,涉及的技术把整个流程说下。然后他感兴趣就会深入问
- 然后就是聊一些jvm、nosql之类的
- 假如服务器反应很慢,你应该怎么排查问题
- 怎么跟踪一个方法的用时,并且最小限度干涉业务程序
Java面试题分享:
2.框架部分
关于这部分,主要考的也是一些框架部门中较为基础的内容。
- Mybatis与Hibernate区别。
- 关于hibernate的相关问题。
- 关于Spring的相关问题。
- 关于struts1/2的工作流程。
- 介绍IOC和AOP。
3.数据库
- 学生成绩表的常用查询。(基础到不能再基础拉。)
- 如何防止SQL注入。
- 简述悲观锁和乐观锁。
- 了解redis,memcache,mongodb等(如果有项目经验最佳)。
- 数据库性能优化常用基础知识,百万级数据优化。(这也是目前比较常被用到的)
5.前端基础
- 列举3种数据类型。
- jquery取值赋值基本方法。
- CSS盒子模型的4个顺序。
- 获取单选按钮的值,获取复选框的值,获取下拉列表的值,后去复选框的值,获取单选按钮组的值,文本框、文本域赋值。
- javascript变量范围有什么不同,全局变量和局部变量。
HashSet 与TreeSet和LinkedHashSet的区别
Set接口
Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。
HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子接口SortedSet的实现类。Set接口及其子接口、实现类的结构如下所示:
|——SortedSet接口——TreeSet实现类
Set接口——|——HashSet实现类
|——LinkedHashSet实现类
HashSet
HashSet有以下特点
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素可以是null,但只能放入一个null
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其 hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
TreeSet
TreeSet类型是J2SE中唯一可实现自动排序的类型
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向 TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(To1,To2)方法
LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,
我来解释一下吧。首先,想要明白hashCode的作用,你必须要先知道Java中的集合。
java的HashCode方法
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。
你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。 也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。 哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。
初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。 这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。 所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
上面说的对象相同指的是用eqauls方法比较。你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
hashcode这个方法是用来鉴定2个对象是否相等的。 那你会说,不是还有equals这个方法吗? 不错,这2个方法都是用来判断2个对象是否相等的。但是他们是有区别的。 一般来讲,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等 了。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。
举个例子,有个学生类,属性只有姓名和性别,那么我们可以 认为只要姓名和性别相等,那么就说这2个对象是相等的。
hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode 这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了!所以简单来讲,hashcode相 当于是一个对象的编码,就好像文件中的md5,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。
举个例子,还是刚刚的例子,如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名 的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。 要从物理上判断2个对象是否相等,用==就可以了。
其他
10.1、系统做了哪些安全防护
1、XSS(跨站脚本攻击)
全称是跨站脚本攻击(Cross Site Scripting),指攻击者在网页中嵌入恶意脚本程序。
XSS防范:
XSS之所以会发生,是因为用户输入的数据变成了代码。因此,我们需要对用户输入的数据进行HTML转义处理,将其中的“尖括号”、“单引号”、“引号”之类的特殊字符进行转义编码。
2、CSRF(跨站请求伪造)
攻击者盗用了你的身份,以你的名义向第三方网站发送恶意请求。
CSRF的防御:
1)尽量使用POST,限制GET
2)将cookie设置为HttpOnly
3)增加token
4)通过Referer识别
3、SQL注入
使用预编译语句(PreparedStatement),这样的话即使我们使用sql语句伪造成参数,到了服务端的时候,这个伪造sql语句的参数也只是简单的字符,并不能起到攻击的作用。
做最坏的打算,即使被’拖库‘('脱裤,数据库泄露')。数据库中密码不应明文存储的,可以对密码使用md5进行加密,为了加大破解成本,所以可以采用加盐的(数据库存储用户名,盐(随机字符长),md5后的密文)方式。
4、DDOS
最直接的方法增加带宽。但是攻击者用各地的电脑进行攻击,他的带宽不会耗费很多钱,但对于服务器来说,带宽非常昂贵。
云服务提供商有自己的一套完整DDoS解决方案,并且能提供丰富的带宽资源
Spring框架面试问答
1. 什么是spring?
Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。
2. 使用Spring框架的好处是什么?
- 轻量:Spring 是轻量的,基本的版本大约2MB
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开
- 容器:Spring 包含并管理应用中对象的生命周期和配置
- MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品
- 事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
- 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常
3. Spring由哪些模块组成?
以下是Spring 框架的基本模块:
- Core module
- Bean module
- Context module
- Expression Language module
- JDBC module
- ORM module
- OXM module
- Java Messaging Service(JMS) module
- Transaction module
- Web module
- Web-Servlet module
- Web-Struts module
- Web-Portlet module
4. 核心容器(应用上下文) 模块
这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是 任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。
5. BeanFactory – BeanFactory 实现举例
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。
6. XMLBeanFactory
最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
7. 解释AOP模块
AOP模块用于发给我们的Spring应用做面向切面的开发, 很多支持由AOP联盟提供,这样就确保了Spring和其他AOP框架的共通性。这个模块将元数据编程引入Spring。
8. 解释JDBC抽象和DAO模块
通过使用JDBC抽象和DAO模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题,它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用Spring的AOP 模块给Spring应用中的对象提供事务管理服务。
9. 解释对象/关系映射集成模块
Spring 通过提供ORM模块,支持我们在直接JDBC之上使用一个对象/关系映射映射(ORM)工具,Spring 支持集成主流的ORM框架,如Hiberate,JDO和 iBATIS SQL Maps。Spring的事务管理同样支持以上所有ORM框架及JDBC。
10. 解释WEB 模块
Spring的WEB模块是构建在application context 模块基础之上,提供一个适合web应用的上下文。这个模块也包括支持多种面向web的任务,如透明地处理多个文件上传请求和程序级请求参数的绑定到你的业务对象。它也有对Jakarta Struts的支持。
12. Spring配置文件
Spring配置文件是个XML 文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。
13. 什么是Spring IOC 容器?
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
14. IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试,单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。
15. ApplicationContext通常的实现是什么?
- FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
- ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
- WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
16. Bean 工厂和 Application contexts 有什么区别?
Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。
17. 一个Spring的应用看起来象什么?
- 一个定义了一些功能的接口
- 这实现包括属性,它的Setter , getter 方法和函数等
- Spring AOP
- Spring 的XML 配置文件
- 使用以上功能的客户端程序
依赖注入
18. 什么是Spring的依赖注入?
依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。
19. 有哪些不同类型的IOC(依赖注入)方式?
- 构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
- Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
20. 哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?
你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
Spring Beans
21.什么是Spring beans?
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中<bean/> 的形式定义。
Spring 框架定义的beans都是单件beans。在bean tag中有个属性”singleton”,如果它被赋为TRUE,bean 就是单件,否则就是一个 prototype bean。默认是TRUE,所以所有在Spring框架中的beans 缺省都是单件。点击这里一图Spring Bean的生命周期。
22. 一个 Spring Bean 定义 包含什么?
一个Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。
23. 如何给Spring 容器提供配置元数据?
这里有三种重要的方法给Spring 容器提供配置元数据。
XML配置文件。
基于注解的配置。
基于java的配置。
24. 你怎样定义类的作用域?
当定义一个<bean> 在Spring里,我们还能给这个bean声明一个作用域。它可以通过bean 定义中的scope属性来定义。如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype。另一方面,一个bean每次使用的时候必须返回同一个实例,这个bean的scope 属性 必须设为 singleton。
25. 解释Spring支持的几种bean的作用域
Spring框架支持以下五种bean的作用域:
- singleton : bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
缺省的Spring bean 的作用域是Singleton。
26. Spring框架中的单例bean是线程安全的吗?
不,Spring框架中的单例bean不是线程安全的。
27. 解释Spring框架中bean的生命周期
- Spring容器 从XML 文件中读取bean的定义,并实例化bean。
- Spring根据bean的定义填充所有的属性。
- 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
- 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
- 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
- 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
- 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
- 如果bean实现了 DisposableBean,它将调用destroy()方法。
点击这里一图Spring Bean的生命周期。
28. 哪些是重要的bean生命周期方法? 你能重载它们吗?
有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。
The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。
29. 什么是Spring的内部bean?
当一个bean仅被用作另一个bean的属性时,它能被声明为一个内部bean,为了定义inner bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。
30. 在 Spring中如何注入一个java集合?
Spring提供以下几种集合的配置元素:
- <list>类型用于注入一列值,允许有相同的值。
- <set> 类型用于注入一组值,不允许有相同的值。
- <map> 类型用于注入一组键值对,键和值都可以为任意类型。
- <props>类型用于注入一组键值对,键和值都只能为String类型。
31. 什么是bean装配?
装配,或bean 装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。
32. 什么是bean的自动装配?
Spring 容器能够自动装配相互合作的bean,这意味着容器不需要<constructor-arg>和<property>配置,能通过Bean工厂自动处理bean之间的协作。
33. 解释不同方式的自动装配
有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入
- no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
- byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
- byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
- constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
- autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
34.自动装配有哪些局限性?
自动装配的局限性是:
- 重写:你仍需用 <constructor-arg>和 <property> 配置来定义依赖,意味着总要重写自动装配。
- 基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。
- 模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。
35. 你可以在Spring中注入一个null 和一个空字符串吗?
可以。
Spring注解
36. 什么是基于Java的Spring注解配置? 给一些注解的例子
基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。
以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。点击这里学习JAVA几大元注解。
37. 什么是基于注解的容器配置?
相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。
开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。
38. 怎样开启注解装配?
注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置 <context:annotation-config/>元素。
39. @Required 注解
这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required注解的bean属性未被设置,容器将抛出BeanInitializationException。
40. @Autowired 注解
@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和@Required一样,修饰setter方法、构造器、属性或者具有任意名称和/或多个参数的PN方法。
41. @Qualifier 注解
当有多个相同类型的bean却只有一个需要自动装配时,将@Qualifier 注解和@Autowire 注解结合使用以消除这种混淆,指定需要装配的确切的bean。点击这里学习更多常用注解。
Spring数据访问
42.在Spring框架中如何更有效地使用JDBC?
使用SpringJDBC 框架,资源管理和错误处理的代价都会被减轻。所以开发者只需写statements 和 queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate (例子见这里here)
43. JdbcTemplate
JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
44. Spring对DAO的支持
Spring对数据访问对象(DAO)的支持旨在简化它和数据访问技术如JDBC,Hibernate or JDO 结合使用。这使我们可以方便切换持久层。编码时也不用担心会捕获每种技术特有的异常。
45. 使用Spring通过什么方式访问Hibernate?
在Spring中有两种方式访问Hibernate:
- 控制反转 Hibernate Template和 Callback
- 继承 HibernateDAOSupport提供一个AOP 拦截器
46. Spring支持的ORM
Spring支持以下ORM:
- Hibernate
- iBatis
- JPA (Java Persistence API)
- TopLink
- JDO (Java Data Objects)
- OJB
47.如何通过HibernateDaoSupport将Spring和Hibernate结合起来?
用Spring的 SessionFactory 调用 LocalSessionFactory。集成过程分三步:
- 配置the Hibernate SessionFactory
- 继承HibernateDaoSupport实现一个DAO
- 在AOP支持的事务中装配
48. Spring支持的事务管理类型
Spring支持两种类型的事务管理:
- 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
- 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
49. Spring框架的事务管理有哪些优点?
- 它为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
- 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如
- 它支持声明式事务管理。
- 它和Spring各种数据访问抽象层很好得集成。
50. 你更倾向用那种事务管理类型?
大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。
Spring面向切面编程(AOP)
51. 解释AOP
面向切面的编程,或AOP, 是一种编程技术,允许程序模块化横向切割关注点,或横切典型的责任划分,如日志和事务管理。
52. Aspect 切面
AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
52. 在Spring AOP 中,关注点和横切关注的区别是什么?
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。
54. 连接点
连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
55. 通知
通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。
Spring切面可以应用五种类型的通知:
- before:前置通知,在一个方法执行前被调用
- after:在方法执行之后调用的通知,无论方法执行是否成功
- after-returning:仅当方法成功完成后执行的通知
- after-throwing:在方法抛出异常退出时执行的通知
- around:在方法执行之前和之后调用的通知
56. 切点
切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
57. 什么是引入?
引入允许我们在已存在的类中增加新的方法和属性。
58. 什么是目标对象?
被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。
59. 什么是代理?
代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。
60. 有几种不同类型的自动代理?
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
Metadata autoproxying
61. 什么是织入。什么是织入应用的不同点?
织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。
织入可以在编译时,加载时,或运行时完成。
62. 解释基于XML Schema方式的切面实现
在这种情况下,切面由常规类以及基于XML的配置实现。
63. 解释基于注解的切面实现
在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。
Spring 的MVC
64. 什么是Spring的MVC框架?
Spring 配备构建Web 应用的全功能MVC框架。Spring可以很便捷地和其他MVC框架集成,如Struts,Spring 的MVC框架用控制反转把业务对象和控制逻辑清晰地隔离。它也允许以声明的方式把请求参数和业务对象绑定。
65. DispatcherServlet
Spring的MVC框架是围绕DispatcherServlet来设计的,它用来处理所有的HTTP请求和响应。
66. WebApplicationContext
WebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。
67. 什么是Spring MVC框架的控制器?
控制器提供一个访问应用程序的行为,此行为通常通过服务接口实现。控制器解析用户输入并将其转换为一个由视图呈现给用户的模型。Spring用一个非常抽象的方式实现了一个控制层,允许用户创建多种用途的控制器。
68. @Controller 注解
该注解表明该类扮演控制器的角色,Spring不需要你继承任何其他控制器基类或引用Servlet API。
69. @RequestMapping 注解
该注解是用来映射一个URL到一个类或一个特定的方处理法上。
【学习参考】
[荐]https://www.toutiao.com/c/user/84982888621/#mid=1589035076683780
[荐]https://www.toutiao.com/i6592700941210747405/
https://www.toutiao.com/a6590673631498469901/