本文是记录自己慢慢积累的Java知识点, 后续会慢慢更新, 欢迎给我留言指导, 我会用心写好每篇文章与你分享.
1.==与equals的区别:
==是直接比较的两个对象的内存地址
基本数据类型和String作为常量在方法区中的常量池里面以HashSet策略存储起来,在常量池中,一个常量只会对应一个地址, 所以第一个为true不难理解
Integer 在常量池中的存储范围为[-128,127] , 128这个值不在这个范围, 会在堆内存中创建一个新的对象来保存这个值, 在堆中存储是用内存地址引用指向值, 所以第二个结果为false
下面摘录有equals底层源码, 看下得知, 在Object类型的equals方法是直接通过来比较的,在没有重写该方法时, 和是没有任何区别, 所以第四个为true, 值相等
Q和W是new出来的实例对象, 在堆内存中以地址值存储, 第五个为false, 第六个比对的比对的依然是长量值, 为true.
经测试, 区别就很明显了: ==是比较的两个对象的内存地址, equals可以重写, 传参数是地址值就比较地址值, 传参比较的是值就比较值, 有自己定义决定
/**
* @author Jeady
* @Date 2020/2/8
*/
public class EqualsTest {
public static void main(String[] args) {
Integer a1 = 127;
Integer b1 = 127;
System.out.println(a1==b1);//true
Integer a2 = 128;
Integer b2 = 128;
System.out.println(a2==b2);//false
System.out.println(a2.equals(b2));//true
Q q = new Q();
W w = new W();
System.out.println(q.equals(w));//false
System.out.println(q.a.equals(w.b));//true
}
}
class Q{
Integer a = -128;
@Override
public boolean equals(Object w) {
return a.equals(w);
}
}
class W{
Integer b = -128;
}
2.equals底层源码:
/**
* Compares this object to the specified object. The result is
* {@code true} if and only if the argument is not
* {@code null} and is an {@code Integer} object that
* contains the same {@code int} value as this object.
*
* @param obj the object to compare with.
* @return {@code true} if the objects are the same;
* {@code false} otherwise.
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
3.String, StringBuffer, StringBuilder的区别:
1.String是字符串常量,其他两者属于字符串变量,String对象创建后不可以改变,对String的操作实际上是不断创建和回收的过程,执行速度慢
2.StringBuilder是线程不安全的,而StringBuffer是线程安全的
3.String适用于少量的字符串操作;StringBuilder适用于单线程下的在字符串缓冲区进行大量的操作的情况下;StringBuffer适用于多线程下的在字符串缓冲区进行大量的操作的情况下。
4.ArrayList,LinkedList的区别:
1.ArrayList以索引对应的元素(可随机访问),有索引访问速度就快,LinkedList需遍历整个链表来获取对应的元素。
2.ArrayList的底层是数组,增删需要复制和移动数组实现, LinkedList的底层是双向链表,只需要修改对应的指针即可,增删速度更快
3.但是, 如果增删在末尾(调的是remove()和add()方法), 数据量有百万级别了, ArrayList更快, 同理如果增删在中间位置较多,同时数据量也到了百万级别,Array List的性能会更高, ArrayList的消耗主要是在移动和复制上, 而LinkedList的遍历速度是要慢于ArrayList的复制移动速度的
5.HashMap和HashTable的区别
1.都是使用key-value的形式来存储数据,区别是HashTable基于Dictionary类,而HshMap是基于abstractMap。
2.HashMap是单线程安全的,HashTable是多线程安全的, 我们一般更多的用ConcurrentHashMap来支持多线程安全
3.HashMap仅仅支持Iterator的遍历方式,HashTable支持Iterrator和Enumeration两种遍历方式
4.HashMap可以允许存在一个为null的key和若干个为null的value,但是HashTable中的key和value都不允许为null
6.MySql分页:
Limit [offset, row]: offset指定要返回的第一行的偏移量(索引号),rows第二个指定返回行的最大数目。初始行的偏移量为0
公式:start=(currentPage-1)*pageSize
7.类加载机制:
1.系统通过加载、连接、初始化三个步骤对类进行初始化;
2.类的加载:是指将类的class文件加载进内存,并创建一个java.lang.Class对象
3.类加载器由系统提供,通常称为系统加载器,可以通过继承ClassLoader积累来创建自己的类加载器。
4.类的连接:把类的二进制数据合并到JRE中;分为三个阶段:
(1)验证:检验被加载的类是否有正确的内部结构,并且和其他类协调一致;
(2)准备:为类的类变量分配内存,设置默认初始值;
(3)解析:将二进制中的符号引用替换成直接引用;
5.类的初始化:虚拟机扶负责对类进行初始化,对类变量进行初始化
8. ConcurrentHashMap的实现原理
线程安全并且高效的HashMap
锁分段技术:将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他段的数据也能被其他线程访问;
JDK1.7
ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成;采用了分段锁的技术;
JDK1.8
Node数组+链表+红黑树的数据结构;并发控制使用Synchronized和CAS来操作;(红黑树是为了提高查找效);
将Node的value和next加上volatile来保持可见性和禁止重排序;(只允许对数据进行查找,不允许进行修改);
Node:Node是ConcurrentHashMap存储结构的基本单元,继承于HashMap中的Entry,用于存储数据;
在ConcurrentHashMap中,初始化操作并不是在构造函数实现的,而是在put操作中实现;
9. Sleep、yield、wait、join方法的区别
Sleep:在指定时间内让当前正在执行的线程暂停执行,但不释放锁;
Yield:暂停当前正在执行的线程,不释放锁;使线程重新回到可执行状态;(会出现yield的线程可能进入到可执行状态后,当场抢到cpu资源,又被执行,甚至,无限连任)
Wait:调用该方法,使线程处于阻塞状态,需调用notify或notifyAll方法唤醒;在等待中会释放锁;
Join:等待调用join方法的线程结束后,在继续执行下面的代码;
Redis缓存和数据库的数据不一致怎么解决
Mysql binlog 增量发布订阅消费+消息队列+增量数据更新到redis
1) 读请求走Redis:热数据基本都是在Redis;
2) 写请求走Mysql:增删改都操作Mysql;
3) 更新redis数据:mysql的数据操作binlog,来更新redis;
2.更新Redis
1)数据操作主要分为两块:
A.一个是全量(将全部数据一次写入到redis)
b.一个是增量(实时更新)
这里的增量,指的是mysql的update、insert、delete变更数据
这样一旦mysql中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至redis,redis在根据binlog中的记录,对redis进行更新,就无需再业务线去操作缓存内容;
8.垃圾收集器:
GC的常见算法:
主要有标记-清除算法,标记-压缩算法,复制算法,分代收集算法, 目前最主流的是分代收集算法
1.标记-清除算法是为每个对象存储一个标记位,记录对象的状态(活着或是死亡). 分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
2.标记-压缩法是标记-清除法的一个改进版, 不同的是,在第二个阶段,该算法并没有直接对死亡的对象进行清理,而是将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除
3.复制算法: 该算法将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去
串行垃圾收集器(Serial)
单线程一个收集器,JVM在发现我们内存不够用的时候就会暂停应用程序的执行。开启一个垃圾回收线程来回收垃圾,一般适用于小的嵌入式设备
并行垃圾收集器(Parallel)
垃圾收集线程之间并行,但它们和用户线程并不是并行的。多条垃圾收集线程并行工作时,用户线程仍然处于等待状态。即内存不够的时候暂停应用程序,启动多个垃圾收集线程做垃圾回收,回收完重新启动应用程序。适合那种科学计算比如说模型运算、后台处理这样的弱交互场景,也就是不需要实时交互的那种场景。(吞吐量优先)
并发垃圾收集器(Concurrent)
垃圾收集线程和用户线程之间是并行的,用户线程和垃圾收集线程同时执行(不能说完全同时,可能是交替执行),垃圾收集线程工作的时候不会停顿用户程序正常运行,适用于对响应时间有要求并且交互性较强的场景(响应时间优先)(如:Web)
在JDK8里有两种并发垃圾收集器:CMS 和 G1
CMS: 一种老年代垃圾收集器,其特点是响应时间优先,低延迟,低停顿
CMS的缺点
-
CPU敏感:当启用一个线程去进行垃圾回收,那么现在就只有这一个线程,一个CPU可以去响应垃圾的回收
-
CMS会产生浮动的垃圾:CMS在做垃圾回收的时候,应用程序也在运行,所以有可能产生新的垃圾,需要等到下一次GC才能清除。成为浮动垃圾。
-
有可能物理不连续产生空间碎片
G1收集器就是适用于大内存并且停顿时间非常小,如:大于6G的内存并且停顿时间小于0.5s
G1的堆结构逻辑上也是Old(老年代)和Young(新生代)
GC调优参数:
停顿时间:
垃圾收集器做垃圾回收时需要中断应用程序的时间。由参数 -XX:MaxGCPauseMillis决定
吞吐量:
在垃圾收集上的耗时与在应用上的耗时的占比。由参数 -XX:GCTimeRatio=决定,比如设置n为 10,那么垃圾收集的时间的比重为 1 / 10 + 1 = 1 / 11
GC的过程:
1.初始创建对象的时候,对象放在Eden中
若是大对象则直接进入Old(老年代),对大对象的定义依赖于JVM的一个参数:-XX:PretenureSizeThreshold
2.随着时间的流逝,Eden种存放的对象越来越多,当Eden区域满了,会进行minor GC,会将Eden种还在使用的对象放入到servivor0中,不需要的对象清楚掉.清除掉之后的Eden就变空了
3.Eden中有可以重新接纳对象,随时间的流逝,Eden中存放的对象越来越多,当Eden区域满了,又进行GC ,会将Eden与servivor中还在使用的对象放入到survivor1中,Eden与servivor中使用的对象清除掉
4.循环第三个步骤
5.当survivor中空间不足,会将还在使用的对象放入到老年代中
6.循环3-5步骤
7.随着时间流逝,老年代中空间不足,这时候就需要full GC , Full GC的时间短则几分钟,长的十几分钟.期间,进行对暂停一切操作,优先GC, 这个时候可能还会出现拉取shuffle数据失败
如果在GC的时候拉取shuffle数据失败,会有一个重试次数,如果重试之后,任然没有拉取到数据,任务报错,停止执行
针对这个问题,可以调整拉取shuffle失败的参数:
spark.shuffle.io.maxRetries=6 shuffle拉取数据失败的时候最大重试次数
spark.shuffle.io.retryWait=10s拉取失败的时候,每次重试的时间间隔
什么时候选择G1收集器?
-
50%以上的堆被存活对象占用
-
对象分配和进入Old(老年代)的速度变化很大
-
垃圾回收时间很长,超过1s
垃圾收集器的选择:
Young(新生代)和Old(老年代)可以采用不同的垃圾收集器,可以搭配使用,JDK8推荐使用G1性能高
1、调整堆内存大小让服务器自行选择
2、内存小于100M使用串行收集器
3、单核并没有停顿时间需求使用串行或者JVM自己选择
4、能接受停顿时间超过1秒,选择并行垃圾收集器或者JVM自己选择
5、若响应时间最重要,尽量不超1s(反正响应越快越好),使用并发垃圾收集器
Server模式与Client模式
JVM如果不显式指定是Server模式还是Client模式的话,虚拟机启动检测主机是否为服务器,如果是,则以Server模式启动,否则以client模式启动
Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。
Java - version: 查看运行模式