● JDK(Java SE Development Kit),Java标准开发包,它提供了编译、运⾏Java程序所需的各种⼯具和资源,包括Java编译器、Java运⾏时环境,以及常⽤的Java类库等
● JRE( Java Runtime Environment) ,Java运⾏环境,⽤于运⾏Java的字节码⽂件。JRE中包括了
JVM以及JVM⼯作所需要的类库,普通⽤户⽽只需要安装JRE来运⾏Java程序,⽽程序开发者必须安装JDK来编译、调试程序。
● JVM(Java Virtual Mechinal),Java虚拟机,是JRE的⼀部分,它是整个java实现跨平台的最核⼼的部分,负责运⾏字节码⽂件。
我们写Java代码,⽤txt就可以写,但是写出来的Java代码,想要运⾏,需要先编译成字节码,那就需要编译器,⽽JDK中就包含了编译器javac,编译之后的字节码,想要运⾏,就需要⼀个可以执⾏字节码的程序,这个程序就是JVM(Java虚拟机),专⻔⽤来执⾏Java字节码的。
如果我们要开发Java程序,那就需要JDK,因为要编译Java源⽂件。
如果我们只想运⾏已经编译好的Java字节码⽂件,也就是*.class⽂件,那么就只需要JRE。
JDK中包含了JRE,JRE中包含了JVM。
另外,JVM在执⾏Java字节码时,需要把字节码解释为机器指令,⽽不同操作系统的机器指令是有可能不⼀样的,所以就导致不同操作系统上的JVM是不⼀样的,所以我们在安装JDK时需要选择操作系统。 另外,JVM是⽤来执⾏Java字节码的,所以凡是某个代码编译之后是Java字节码,那就都能在JVM上运
⾏,⽐如Apache Groovy, Scala and Kotlin 等等。
在Java中,每个对象都可以调⽤⾃⼰的hashCode()⽅法得到⾃⼰的哈希值(hashCode),相当于对象的 指纹信息,通常来说世界上没有完全相同的两个指纹,但是在Java中做不到这么绝对,但是我们仍然可 以利⽤hashCode来做⼀些提前的判断,⽐如:
● 如果两个对象的hashCode不相同,那么这两个对象肯定不同的两个对象
● 如果两个对象的hashCode相同,不代表这两个对象⼀定是同⼀个对象,也可能是两个对象
● 如果两个对象相等,那么他们的hashCode就⼀定相同
在Java的⼀些集合类的实现中,在⽐较两个对象是否相等时,会根据上⾯的原则,会先调⽤对象的
hashCode()⽅法得到hashCode进⾏⽐较,如果hashCode不相同,就可以直接认为这两个对象不相
同,如果hashCode相同,那么就会进⼀步调⽤equals()⽅法进⾏⽐较。⽽equals()⽅法,就是⽤来最终确定两个对象是不是相等的,通常equals⽅法的实现会⽐较重,逻辑⽐较多,⽽hashCode()主要就是得到⼀个哈希值,实际上就⼀个数字,相对⽽⾔⽐较轻,所以在⽐较两个对象时,通常都会先根据
hashCode想⽐较⼀下。
所以我们就需要注意,如果我们重写了equals()⽅法,那么就要注意hashCode()⽅法,⼀定要保证能遵守上述规则。
泛型中extends和super的区别
● ==:如果是基本数据类型,⽐较是值,如果是引⽤类型,⽐较的是引⽤地址
● equals:具体看各个类重写equals⽅法之后的⽐较逻辑,⽐如String类,虽然是引⽤类型,但是
String类中重写了equals⽅法,⽅法内部⽐较的是字符串中的各个字符是否全部相等。
重载和重写的区别
● 重载(Overload): 在⼀个类中,同名的⽅法如果有不同的参数列表(⽐如参数类型不同、参数个数不同)则视为重载。
● 重写(Override): 从字⾯上看,重写就是 重新写⼀遍的意思。其实就是在⼦类中把⽗类本身有的⽅法重新写⼀遍。⼦类继承了⽗类的⽅法,但有时⼦类并不想原封不动的继承⽗类中的某个⽅法,所 以在⽅法名,参数列表,返回类型都相同(⼦类中⽅法的返回值可以是⽗类中⽅法返回值的⼦类)的 情况下, 对⽅法体进⾏修改,这就是重写。但要注意⼦类⽅法的访问修饰权限不能⼩于⽗类的。
● List:有序,按对象插⼊的顺序保存对象,可重复,允许多个Null元素对象,可以使⽤Iterator取出所有元素,在逐⼀遍历,还可以使⽤get(int index)获取指定下标的元素
● Set:⽆序,不可重复,最多允许有⼀个Null元素对象,取元素时只能⽤Iterator接⼝取得所有元素,在逐⼀遍历各个元素
1.7 版本
1.7版本的ConcurrentHashMap是基于Segment分段实现的
每个Segment相对于⼀个⼩型的HashMap
每个Segment内部会进⾏扩容,和HashMap的扩容逻辑类似
先⽣成新的数组,然后转移元素到新数组中
扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8 版本
Jdk1.7到Jdk1.8 HashMap 发⽣了什么变化(底层)?
先说HashMap的Put⽅法的⼤体流程:
a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对象,并使⽤头插法添加到当前位置的链表中
b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node
ⅰ. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value
ⅱ. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会 判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链 表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成 红⿊树
ⅲ. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就扩容,如果不需要就结束PUT⽅法
深拷⻉和浅拷⻉就是指对象的拷⻉,⼀个对象中存在两种类型的属性,⼀种是基本数据类型,⼀种是实例对象的引⽤。
HashMap的扩容机制原理
1.7 版本
1.8 版本
编译器(javac)将Java源⽂件(.java)⽂件编译成为字节码⽂件(.class),可以做到⼀次编译到处运⾏,
windows上编译好的class⽂件,可以直接在linux上运⾏,通过这种⽅式做到跨平台,不过Java的跨平台有⼀个前提条件,就是不同的操作系统上安装的JDK或JRE是不⼀样的,虽然字节码是通⽤的,但是需要把字节码解释成各个操作系统的机器码是需要不同的解释器的,所以针对各个操作系统需要有各⾃ 的JDK或JRE。
采⽤字节码的好处,⼀⽅⾯实现了跨平台,另外⼀⽅⾯也提⾼了代码执⾏的性能,编译器在编译源代码 时可以做⼀些编译期的优化,⽐如锁消除、标量替换、⽅法内联等。
● Java中的所有异常都来⾃顶级⽗类Throwable。
● Throwable下有两个⼦类Exception和Error。
● Error表示⾮常严重的错误,⽐如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError, 通常这些错误出现时,仅仅想靠程序⾃⼰是解决不了的,可能是虚拟机、磁盘、操作系统层⾯出现 的问题了,所以通常也不建议在代码中去捕获这些Error,因为捕获的意义不⼤,因为程序可能已经 根本运⾏不了了。
● Exception表示异常,表示程序出现Exception时,是可以靠程序⾃⼰来解决的,⽐如
NullPointerException、IllegalAccessException等,我们可以捕获这些异常来做特殊处理。
● Exception的⼦类通常⼜可以分为RuntimeException和⾮RuntimeException两类
● RunTimeException表示运⾏期异常,表示这个异常是在代码运⾏过程中抛出的,这些异常是⾮检查
异常,程序中可以选择捕获处理,也可以不处理。这些异常⼀般是由程序逻辑错误引起的,程序应该从逻辑⻆度尽可能避免这类异常的发⽣,⽐如NullPointerException、IndexOutOfBoundsException等。
● ⾮RuntimeException表示⾮运⾏期异常,也就是我们常说的检查异常,是必须进⾏处理的异常,如果
不处理,程序就不能检查异常通过。如IOException、SQLException等以及⽤户⾃定义的Exception异常
异常相当于⼀种提示,如果我们抛出异常,就相当于告诉上层⽅法,我抛了⼀个异常,我处理不了这个异常,交给你来处理,⽽对于上层⽅法来说,它也需要决定⾃⼰能不能处理这个异常,是否也需要交给它的上层。
所以我们在写⼀个⽅法时,我们需要考虑的就是,本⽅法能否合理的处理该异常,如果处理不了就继续 向上抛出异常,包括本⽅法中在调⽤另外⼀个⽅法时,发现出现了异常,如果这个异常应该由⾃⼰来处理,那就捕获该异常并进⾏处理。
JDK⾃带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。
● BootStrapClassLoader是ExtClassLoader的⽗类加载器,默认负责加载%JAVA_HOME%lib下的
jar包和class⽂件。
● ExtClassLoader是AppClassLoader的⽗类加载器,负责加载%JAVA_HOME%/lib/ext⽂件夹下的
jar包和class类。
● AppClassLoader是⾃定义类加载器的⽗类,负责加载classpath下的类⽂件。
JVM中存在三个默认的类加载器:
BootstrapClassLoader
ExtClassLoader
AppClassLoader
AppClassLoader的⽗加载器是ExtClassLoader,ExtClassLoader的⽗加载器是BootstrapClassLoader。
JVM在加载⼀个类时,会调⽤AppClassLoader的loadClass⽅法来加载这个类,不过在这个⽅法中,会 先使⽤ExtClassLoader的loadClass⽅法来加载类,同样ExtClassLoader的loadClass⽅法中会先使⽤
BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果
BootstrapClassLoader没有加载到,那么ExtClassLoader就会⾃⼰尝试加载该类,如果没有加载到, 那么则会由AppClassLoader来加载这个类。
所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进⾏加载,如果没加载到才由⾃⼰进⾏加载。
堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的
对于还在正常运⾏的系统:
对于已经发⽣了OOM的系统:
总之,调优不是⼀蹴⽽就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题
怎么确定⼀个对象到底是不是垃圾?
STW: Stop-The-World,是在垃圾回收算法执⾏过程当中,需要将JVM内存冻结的⼀种状态。在STW 状态下,JAVA的所有线程都是停⽌执⾏的-GC线程除外,native⽅法可以执⾏,但是,不能与JVM交互。GC各种算法优化的重点,就是减少STW,同时这也是JVM调优的重点。
JVM参数⼤致可以分为三类:
线程安全指的是,我们写的某段代码,在多个线程同时执⾏这段代码时,不会产⽣混乱,依然能够得到 正常的结果,⽐如i++,i初始化值为0,那么两个线程来同时执⾏这⾏代码,如果代码是线程安全的,那 么最终的结果应该就是⼀个线程的结果为1,⼀个线程的结果为2,如果出现了两个线程的结果都为1,则表示这段代码是线程不安全的。
所以线程安全,主要指的是⼀段代码在多个线程同时执⾏的情况下,能否得到正确的结果。
线程分为⽤户线程和守护线程,⽤户线程就是普通线程,守护线程就是JVM的后台线程,⽐如垃圾回收 线程就是⼀个守护线程,守护线程会在其他普通线程都停⽌运⾏之后⾃动关闭。我们可以通过设置
thread.setDaemon(true)来把⼀个线程设置为守护线程。
造成死锁的⼏个原因:
这是造成死锁必须要达到的4个条件,如果要避免死锁,只需要不满⾜其中某⼀个条件即可。⽽其中前3 个条件是作为锁要符合的条件,所以要避免死锁就需要打破第4个条件,不出现循环等待锁的关系。
在开发过程中:
线程池内部是通过队列+线程实现的,当我们利⽤线程池执⾏任务时:
当线程池中的核⼼线程都在忙时,如果继续往线程池中添加任务,那么任务会先放⼊队列,队列满了之 后,才会新开线程。这就相当于,⼀个公司本来有10个程序员,本来这10个程序员能正常的处理各种需求,但是随着公司的发展,需求在慢慢的增加,但是⼀开始这些需求只会增加在待开发列表中,然后这
10个程序员加班加点的从待开发列表中获取需求并进⾏处理,但是某⼀天待开发列表满了,公司发现现有的10个程序员是真的处理不过来了,所以就开始新招员⼯了。
⾸先不管是公平锁和⾮公平锁,它们的底层实现都会使⽤AQS来进⾏排队,它们的区别在于:线程在使
⽤lock()⽅法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队, 则当前线程也进⾏排队,如果是⾮公平锁,则不会去检查是否有线程在排队,⽽是直接竞争锁。
不管是公平锁还是⾮公平锁,⼀旦没竞争到锁,都会进⾏排队,当锁释放时,都是唤醒排在最前⾯的线 程,所以⾮公平锁只是体现在了线程加锁阶段,⽽没有体现在线程被唤醒阶段。
另外,ReentrantLock是可重⼊锁,不管是公平锁还是⾮公平锁都是可重⼊的。
CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,⼀个线程调⽤
CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对
CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。
对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中 排队的线程依次唤醒
Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使⽤该信号量,通 过acquire()来获取许可,如果没有许可可⽤则线程阻塞,并通过AQS来排队,可以通过release()
⽅法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第⼀个线程开始依次唤 醒,直到没有空闲许可。
通常,我们认为Spring有两⼤特性IoC和AOP,那到底该如何理解IoC呢?
对于很多初学者来说,IoC这个概念给⼈的感觉就是我好像会,但是我说不出来。
那么IoC到底是什么,接下来来说说我的理解,实际上这是⼀个⾮常⼤的问题,所以我们就把它拆细了来回答,IoC表示控制反转,那么:
但是,我们也知道,当程序运⾏时,⽤的是具体的UserService对象、OrderService对象,那这些对象是什么时候创建的?谁创建的?包括对象⾥的属性是什么时候赋的值?谁赋的?所有这些都是我们程序 员做的,以为我们只是写了类⽽已,所有的这些都是Spring做的,它才是幕后⿊⼿。
这就是控制:
如果我们不⽤Spring,那我们得⾃⼰来做这两件事,反过来,我们⽤Spring,这两件事情就不⽤我们做了,我们要做的仅仅是定义类,以及定义哪些属性需要Spring来赋值(⽐如某个属性上加
@Autowired),⽽这其实就是第⼆个问题的答案,这就是反转,表示⼀种对象控制权的转移。那反转有什么⽤,为什么要反转?
如果我们⾃⼰来负责创建对象,⾃⼰来给对象中的属性赋值,会出现什么情况?
⽐如,现在有三个类:
现在程序要运⾏,这三个类的对象都需要创建出来,并且相应的属性都需要有值,那么除开定义这三个类之外,我们还得写:
这五⾏代码是不⽤Spring的情况下多出来的代码,⽽且,如果类在多⼀些,类中的属性在多⼀些,那相应的代码会更多,⽽且代码会更复杂。所以我们可以发现,我们⾃⼰来控制⽐交给Spring来控制,我们 的代码量以及代码复杂度是要⾼很多的,反⾔之,将对象交给Spring来控制,减轻了程序员的负担。
总结⼀下,IoC表示控制反转,表示如果⽤Spring,那么Spring会负责来创建对象,以及给对象内的属 性赋值,也就是如果⽤Spring,那么对象的控制权会转交给Spring。
单例模式表示JVM中某个类的对象只会存在唯⼀⼀个。
⽽单例Bean并不表示JVM中只能存在唯⼀的某个类的Bean对象。
多个事务⽅法相互调⽤时,事务如何在这些⽅法间传播,⽅法A是⼀个事务的⽅法,⽅法A执⾏过程中调
⽤了⽅法B,那么⽅法B有⽆事务以及⽅法B对事务的要求不同都会对⽅法A的事务具体执⾏造成影响, 同时⽅法A的事务对⽅法B的事务执⾏也有影响,这种影响具体是什么就由两个⽅法所定义的事务传播类 型所决定。
REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则⾃⼰新建⼀个事务,如果当前存在事务,则加⼊这个事务
SUPPORTS:当前存在事务,则加⼊当前事务,如果当前没有事务,就以⾮事务⽅法执⾏
MANDATORY:当前存在事务,则加⼊当前事务,如果当前事务不存在,则抛出异常。
REQUIRES_NEW:创建⼀个新事务,如果存在当前事务,则挂起该事务。
NOT_SUPPORTED:以⾮事务⽅式执⾏,如果当前存在事务,则挂起当前事务
NEVER:不使⽤事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执⾏,否则REQUIRED的操作⼀样(开启⼀个事 务)
spring事务的原理是AOP,进⾏了切⾯增强,那么失效的根本原因是这个AOP不起作⽤了!常⻅情况有如下⼏种
1、发⽣⾃调⽤,类⾥⾯使⽤this调⽤本类的⽅法(this通常省略),此时这个this对象不是代理类,⽽是
UserService对象本身!
解决⽅法很简单,让那个this变成UserService的代理类即可!
2、⽅法不是public的:@Transactional 只能⽤于 public 的⽅法上,否则事务不会失效,如果要⽤在
⾮ public ⽅法上,可以开启 AspectJ 代理模式。
3、数据库不⽀持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)