海康威视面经
- Java基础
- java常用集合 及其优缺点
- ArrayList
- Vector
- LinkedList
- Jvm调优
- 监控发现问题
- 工具分析问题 :
- 性能调优
- GC频繁 出现内存泄漏 内存溢出
- CPU飙升
- Synchronized和Volatile的比较
- 反射
- 线程池和new thread利弊
- 高并发 集群 分布式 负载均衡
- MySQL调优
- 基础优化
- Maven
- Spring Boot
- spring boot和spring 为什么spring boot轻量级
- 聚集索引与非聚集索引
- 手撕
- 青蛙跳台阶
Java基础
java常用集合 及其优缺点
单列集合Collection
包含List和Set接口
List
特点:
有序:存储和取出位置一致
不唯一:包含重复元素
分类
- ArrayList:底层是数组,查询快,增删慢,线程不安全,效率高
- Vector:底层是数组,查询快,增删慢,线程安全,效率低
- LinkedList:底层是链表,查询慢,增删快,线程不安全,效率高
Set接口的基本介绍
1)无序(添加和取出的顺序不一致),取出的顺序虽然不是添加的顺序,但是它的顺序是固定的。
2)唯一:不允许有重复元素;可以存放null,最多包含一个null
3)没有索引,故不能使用索引的方法来获取元素
1.HashSet
底层是哈希表,而哈希表又依赖两个方法hashcode()和equals()
因为哈希表是无序所以这个也无序
底层具体实现逻辑:
判定哈希值是否相同
相同:通过equals方法进一步判断
返回True,则不处理
返回false,则添加至集合
不同:直接将元素添加至集合
2.TreeSet
底层是红黑树(可以保持平衡的二叉树)
特点:有序,唯一
排序的两种方式 - 自然顺序:通过让元素所属的类继承接口Comparable的compareto()方法,进行具体的大小比对操作
- 比较器排序:主要是实现Compator接口的Compare()方法,一般通过匿名内部类来操作,便于维护
遍历方式:
1.通过迭代器
2.通过增强for
双列集合Map
键值对映射:Map容器中的元素是以键值对的形式存储的,每个键对应一个值。通过键可以快速查找对应的值。
键不重复:Map中的键是唯一的,每个键对应一个值。如果添加已经存在的键,则会更新对应的值。
常用操作:Map提供了添加键值对、获取值、判断是否包含某个键等基本操作。
遍历:可以通过键集、值集或者键值对集合来遍历Map中的元素。
HashTable
线程安全:Hashtable 是同步的,可以在多线程环境中安全地使用。这意味着多个线程可以同时读写 Hashtable 而不会出现并发问题。
不允许 null 键或值:Hashtable 不允许键或值为 null,如果尝试插入 null 键或值,会抛出 NullPointerException 异常。
初始容量和加载因子:Hashtable 有一个初始容量和加载因子,当哈希表中的元素数量超过加载因子乘以容量时,哈希表会自动扩容。
HashMap
非线程安全:HashMap 不是同步的,因此不适合在多线程环境中直接使用。如果需要在多线程环境中使用,可以通过 Collections.synchronizedMap 方法来创建一个线程安全的 Map。
允许 null 键和值:在 Java 8 之后,HashMap 允许 null 作为键和值。
初始容量和负载因子:HashMap 有一个初始容量和负载因子。当哈希表中的元素数量超过负载因子乘以容量时,哈希表会自动扩容。
TreeMap
有序性:TreeMap中的键值对根据键的自然顺序或自定义排序规则进行排序,因此可以按照键的顺序进行遍历。
红黑树:TreeMap内部使用红黑树作为数据结构,这种自平衡二叉搜索树能够保持键值对的有序性,并且提供了较快的插入、删除和查找操作。
键的唯一性:TreeMap中的键是唯一的,如果尝试插入一个已存在的键,则会覆盖原有的值。
性能:TreeMap提供了对数时间复杂度的插入、删除和查找操作,适合于需要有序存储和查找的场景。
导航方法:TreeMap提供了许多导航方法,如firstKey()、lastKey()、lowerKey(K key)、higherKey(K key)等,可以方便地进行范围查找和导航操作。
ArrayList
ArrayList特点:底层是数组,查询快,增删慢,线程不安全,效率高
ArrayList如何扩容:
(1)ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;//transient表示瞬间,短`暂的,表示该属性不会被序列化
(2)当创建ArrayList对象时,如果使用的是无参构造器。则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
(3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
ArrayList为什么线程不安全?
从ArrayList的add函数看
(1)ensureCapacityInternal(size+1)方法不安全
ArrayList默认数组大小为10,假设现在已经添加进去9个元素了,size=9。
①线程A执行完add方法中的ensureCapacityInternal(size+1)挂起来
②线程B开始执行,较验数组容量发现不需要扩容。于是把“b”放在了下标为9的位置,且size自增1
③线程A接着执行,尝试把“a”放在下标为10的位置。但因为数组还没有扩容,最大下标
才为9,所以会抛出数组越界异常。
(2)elementData[size++]方法也不安全
比如刚开始添加元素时size=0,有两个线程,线程A先将元素存放到位置0,但是CPU调度线程A暂停,线程B得到运行的机会。线程B也向此ArrayList中添加元素,因为此时size仍然等于0,所以线程B也将元素存放在位置0,覆盖掉了A。然后线程A和B都继续运行,都增加size的值。这样,线程AB执行完毕后,理想中的情况为size为2,elementData下标为0的位置为A,下标为1的位置为B。而实际情况变成了size为2,elementData下标为0的位置变成了B,下标为1的位置上什么都没有。
Vector
底层是数组,查询快,增删慢,线程安全,效率低
protected Object[] elementData;
Vector 扩容:
1.当我们使用空参构造来创建Vector类对象时,则elementData数组的初始容量默认为10,如需再次扩容,则将elementData数组的当前容量扩容为2倍。
2.如果使用带参构造来创建Vector类对象,则elementData数组的初始容量即为传入形参的指定容量,如果需要扩容,则直接将该数组当前容量扩容至2倍。
Vector 线程安全:
Vector是线程同步的,即线程安全的,这是因为Vector类的操作方法带有synchronized修饰符。因此,在开发中需要线程同步安全时,考虑使用Vector类;如果是单线程情况下,建议优先使用ArrayList类,因为它的效率更高。
LinkedList
LinkedList:底层是链表,查询慢,增删快,线程不安全,效率高
1)LinkedList底层实现了双向链表和双端队列特点
2)可以添加任意元素(元素可以重复),包括null
3)线程不安全,没有实现同步
双列集合Map
Jvm调优
调优步骤:
1、监控发现问题
2、工具分析问题
3、性能调优
监控发现问题
出现以下问题需要调优:
GC频繁
CPU负载过高
OOM 内存溢出(OOM、OutOfMemeory)
内存泄露
死锁
程序响应时间较长
工具分析问题 :
使用分析工具定位oom、内存泄漏等问题。
调优依据:
吞吐量和停顿时长无法兼顾,吞吐量提高的代价是停顿时间拉长。
所以,如果应用程序跟用户基本不交互,就优先提升吞吐量。如果应用程序和用户频繁交互,就优先缩短停顿时间。
JDK自带的命令行调优工具
jps:查看正在运行的 Java 进程。jps -v查看进程启动时的JVM参数;
jstat:查看指定进程的 JVM 统计信息。jstat -gc查看堆各分区大小、YGC,FGC次数和时长。如果服务器没有 GUI 图形界面,只提供了纯文本控制台环境,它是运行期定位虚拟机性能问题的首选工具。
jinfo:实时查看和修改指定进程的 JVM 配置参数。jinfo -flag查看和修改具体参数。
jstack:打印指定进程此刻的线程快照。定位线程长时间停顿的原因,例如死锁、等待资源、阻塞。如果有死锁会打印线程的互相占用资源情况。
** MAT**
MAT可以解析Heap Dump(堆转储)文件dump.hprof,查看GC Roots、引用链、对象信息、类信息、线程信息。可以快速生成内存泄漏报表。
生成dump文件方式
1、通过 jmap
-dump 生成 dump 文件(Java 堆转储快照),-dump:live 只保存堆中的存活对象
2、使用 Visual VM 可以导出堆 dump 文件。
1)首先启动程序(需确保程序一直在运行中)
2)打开JvisualVM工具
3)打开对应的程序进程
3、MAT直接从Java进程导出dump文件
-XX:+HeapDumpOnOutOfMemoryError
// 将生成的堆转储文件保存到 /tmp 目录下,并以进程 ID 和时间戳作为文件名
-XX:HeapDumpPath=/tmp/java_%p_%t.hprof
性能调优
调优Jvm参数
1、查看当前JVM参数配置:
java -XX:+PrintFlagsFinal -version
2、//减少停顿时间 GC停顿时间,垃圾收集器会尝试用各种手段达到这个时间G1回收器默认200ms停顿时长。
-XX:MaxGCPauseMillis=10
3、通过-XX:GCTimeRatio=n参数可以设置吞吐量,99代表吞吐量为99%, 一般吞吐量不能低于95%。
4、调整堆内存大小 -Xms -Xmx -Xmn
根据程序运行时老年代存活对象大小(记为x)进行调整,整个堆内存大小设置为X的3~4倍。年轻代占堆内存的3/8。
-Xms:初始堆内存大小。默认:物理内存小于192MB时,默认为物理内存的1/2;物理内存大192MB且小于128GB时,默认为物理内存的1/4;物理内存大于等于128GB时,都为32GB。
-Xmx:最大堆内存大小,建议保持和初始堆内存大小一样。因为从初始堆到最大堆的过程会有一定的性能开销,而且现在内存不是稀缺资源。
-Xmn:年轻代大小。JDK官方建议年轻代占整个堆大小空间的3/8左右。
5、调整堆内存比例
//伊甸园:幸存区
-XX:SurvivorRatio=8(伊甸园:幸存区=8:2)
//新生代和老年代的占比
-XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整个堆的4/5;默认值=2
6、调整大对象阈值
Young GC时大对象会不顾年龄直接移动到老年代。当Full GC频繁时,我们关闭或提高大对象阈值,让老年代更迟填满。
默认是0,即大对象不会直接在YGC时移到老年代。
//新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。
-XX:PretenureSizeThreshold=1000000
7、【最有效】选择合适的垃圾回收器
JVM调优最实用、最有效的方式是升级垃圾回收器,根据CPU核数,升级当前版本支持的最新回收器。
CPU单核,那么毫无疑问Serial 垃圾收集器是你唯一的选择。
CPU多核,关注吞吐量 ,那么选择Parallel Scavenge+Parallel Old组合(JDK8默认)。
CPU多核,关注用户停顿时间,JDK版本1.6或者1.7,那么选择ParNew+CMS,吞吐量降低但是低停顿。
CPU多核,关注用户停顿时间,JDK1.8及以上,JVM可用内存6G以上,那么选择G1。
GC频繁 出现内存泄漏 内存溢出
使用MAT分析堆转储日志中的大对象,看是否合理。大对象会直接进入老年代,导致Full GC频繁。
内存溢出: 申请的内存大于系统能提供的内存。
溢出原因:
1、本地直接内存溢出:本地直接内存设的太小导致溢出。
2、虚拟机栈和本地方法栈溢出:如果虚拟机的栈内存允许动态扩展,并且方法递归层数太深时,导致扩展栈容量时无法申请到足够内存。
3、方法区溢出:运行时生成大量动态类时会内存溢出。
4、堆溢出
OOM的排查和解决
使用JDK自带的命令行调优工具 ,判断是否有OOM:
使用jsp命令查看当前Java进程;
使用jstat命令多次统计GC,比较GC时长占运行时长的比例;
如果比例超过20%,就代表堆压力已经很大了;
如果比例超过98%,说明这段时期内几乎一直在GC,堆里几乎没有可用空间,随时都可能抛出 OOM 异常。
解决方案:
通过jinfo命令查看并修改JVM参数,直接增加内存。如-Xmx256m
检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
对代码进行走查和分析,找出可能发生内存溢出的位置。
使用内存查看工具动态查看内存使用情况。
内存泄漏: 不再使用的对象仍然被引用,导致GC无法回收;
内存泄露的9种情况:
静态容器里的对象:静态集合类的生命周期与 JVM 程序一致,容器里的对象引用也将一直被引用得不到GC;Java里不准静态方法引用非静态方法也是防止内存泄漏。
单例对象引用的外部对象:单例模式里,如果单例对象如果持有外部对象的引用,因为单例对象不会被回收,那么这个外部对象也不会被回收
外部类跟随内部类被引用:内部类持有外部类,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。
数据库、网络、IO等连接忘记关闭:在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用 close 方法来释放与数据库的连接。如果对 Connection、Statement 或 ResultSet 不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
变量作用域不合理:例如一个变量只会在某个方法中使用,却声明为成员变量,并且被使用后没有被赋值为null,将会导致这个变量明明已经没用了,生命周期却还跟对象一致。
HashSet中对象改变哈希值:当一个对象被存储进 HashSet 集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则对象哈希值改变,找不到对应的value。
缓存引用忘删除:一旦你把对象引用放入到缓存中,他就很容易遗忘,缓存忘了删除,将导致引用一直存在。
逻辑删除而不是真实删除:监听器和其他回调:如果客户端在你实现的 API 中注册回调,却没有显示的取消,那么就会积聚。需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为 软WeakHashMap 中的键。例如出栈只是移动了指针,而没有将出栈的位置赋值null,导致已出栈的位置还存在引用。
线程池时,ThreadLocal忘记remove():使用线程池的时候,ThreadLocal 需要在使用完线程中的线程变量手动 remove(),否则会内存泄漏。因为线程执行完后没有销毁而是被线程池回收,导致ThreadLocal中的对象不能被自动垃圾回收。
CPU飙升
原因
CPU利用率过高,大量线程并发执行任务导致CPU飙升。例如锁等待(例如CAS不断自旋)、多线程都陷入死循环、Redis被攻击、网站被攻击、文件IO、网络IO。
定位步骤
定位进程ID:通过top命令查看当前服务CPU使用最高的进程,获取到对应的pid(进程ID)
定位线程ID:使用top -Hp pid,显示指定进程下面的线程信息,找到消耗CPU最高的线程id
线程ID转十六进制:转十六进制是因为下一步jstack打印的线程快照(线程正在执行方法的堆栈集合)里线程id是十六进制。
定位代码:使用jstack pid | grep tid(十六进制),打印线程快照,找到线程执行的代码。一般如果有死锁的话就会显示线程互相占用情况。
解决问题:优化代码、增加系统资源(增多服务器、增大内存)
Synchronized和Volatile的比较
Synchronized:保证可见性和原子性
Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁→先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
(2)Volatile:保证可见性,但不保证操作的原子性
Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。Volatile不需要加锁(忙等待,做自旋),比Synchronized更轻量级,并不会阻塞线程
反射
对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
线程池和new thread利弊
new Thread优点:
通过new Thread()创建线程的API简单易用,结构清晰,对于执行单一的一次性任务十分便利。
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 降低资源消耗。重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
c. 提高线程的可管理性。提供定时执行、定期执行、单线程、并发数控制等功能
高并发 集群 分布式 负载均衡
添加链接描述
MySQL调优
基础优化
缓存优化
mysql调整缓冲池大小等参数;引入redis。tip:InnoDB使用缓冲池缓存记录和索引
硬件优化
服务器加内存条、升级SSD固态硬盘、把磁盘I/O分散在多个设备、配置多处理器。
参数优化
关闭不必要的服务和日志:调优结束关闭慢查询日志;
调整最大连接数:max_connections ;
线程池缓存线程数:thread_cache_size,缓存空闲线程,有连接时直接分配该线程处理连接;
缓冲池大小:innodb_buffer_pool_size 。
定期清理垃圾
对于不再使用的表、数据、日志、缓存等,应该及时清理,避免占用过多的MySQL资源,从而提高MySQL的性能。
使用合适的存储引擎
MyISAM:适合读取频繁,写入较少的场景(因为表级锁、B+树叶存地址)
InnoDB:适合并发写入的场景(因为行级锁、B+树叶存记录)
读写分离
读写分离:读写分离能有效提高查询性能。主从同步用到bin log和relay log。
分库分表
分库分表:数据量级到达千万级以上后,垂直拆分(分库)、水平拆分(分表)、垂直+水平拆分(分库分表)。
Maven
Maven是⼀个项⽬管理⼯具, 通过 pom.xml ⽂件的配置获取 jar 包,⽽不⽤手动去添加 jar 包
Spring Boot
spring boot和spring 为什么spring boot轻量级
Spring:Spring是一个轻量级的开源框架,它的核心原则是面向切面编程(AOP)和控制反转(IoC)。Spring通过IoC容器管理和组织应用程序中的对象,通过AOP提供横切关注点的支持,如事务管理和日志记录等。Spring提供了一系列的模块,如Spring MVC用于Web开发,Spring Data用于数据库操作,Spring Security用于身份验证和授权等。
Spring Boot:Spring Boot是基于Spring的快速开发框架,它旨在简化Spring应用程序的配置和部署。Spring Boot使用了约定大于配置的理念,通过自动配置和起步依赖简化了项目的搭建和配置过程,使得开发者可以更专注于业务逻辑的开发,而无需过多关注底层的技术细节。它集成了许多常用的开发工具和第三方库,提供了一个独立运行的可执行JAR包,可以快速构建独立的、可部署的Spring应用程序。
Spring Boot创建流程:
1、创建一个新的 Spring Boot 项目。
2、在 pom.xml 文件中添加以下依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
创建一个新的 Spring Boot 应用程序类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建一个新的控制器类:
@Controller
public class HelloWorldController {
@RequestMapping("/")
public String helloWorld() {
return "Hello, World!";
}
}
运行应用程序:
./mvnw spring-boot:run
访问 http://localhost:8080/ ,您将看到以下内容:
Hello, World!
聚集索引与非聚集索引
聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物理顺序。
非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。
手撕
青蛙跳台阶
添加链接描述