当前位置:   article > 正文

java面试题总结(继续更新中~)_ygcin

ygcin

Java基础

1.Stream的用法

2.数据加密方式

3.jackson和fastjson的原理和区别,及Serializable的原理

1.Jackson 可以只解析需要的属性,而 Fastjson 默认会解析所有的属性。

2.Jackson 是使用流操作,只需要在内存中记录流所需的对象信息,可以处理无限大的 JSON 数据。而 Fastjson 是将 JSON 数据一次性读入内存,并使用递归算法进行解析,存在内存占用问题。

3.Jackson 虽然可以使用注解,但是比较麻烦,比如使用 JsonIgnoreProperties 忽略某些属性时,还需要设置 JsonInclude(JsonInclude.Include.NON_NULL) 才能起作用。而 Fastjson 的注解使用比较方便。

4.Jackson 除了 JSON 输出外,还可以输出 XML、YAML 等其他文本格式,而 Fastjson 目前只支持 JSON 格式。

Serializable的原理
序列化是通过将对象的属性及数据转换为字节流,并写入到输出流中实现的。可以通过实现 Serializable 接口使 Java 对象变成可序列化的,通过 ObjectOutputStream 可以将这个对象转换为字节流写入到磁盘或者网络中传输。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.雪花算法

5.阻塞队列和非阻塞队列

1.阻塞队列
阻塞队列是一种线程安全的队列,当队列为空时,尝试从队列中取出元素的线程会被阻塞,直到队列中有元素可取;
如果队列已满,尝试往队列中添加元素的线程也会被阻塞,直到队列中有空闲位置可用。
阻塞队列支持多个线程同时访问,可以避免线程之间的竞争和锁的使用,是在高并发环境下非常实用的一种队列。
Java中提供了多种阻塞队列的实现,如ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue等。

2.非阻塞队列
非阻塞队列则是一种不会阻塞线程的队列,当队列为空或者已满时,
弹出或者插入操作会立即返回一个特殊值(比如null)来表示操作失败。
这种队列使用CAS(compare and swap)原子操作实现,避免了使用锁的开销和竞争。
Java中提供了ConcurrentLinkedQueue和LinkedTransferQueue等非阻塞队列的实现。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

6.TCP和HTTP的区别

1.通信方式
TCP是面向连接的通信协议,通信双方在传输数据之前必须先建立连接,然后进行数据的传输和通信,传输数据完成后再关闭连接。

HTTP是无连接的协议,即在发送请求和接收响应后立即断开连接。在HTTP/1.1中,支持哑代理(keep-alive)机制,
即连接复用,来减少连接建立和断开的开销。

2.数据传输方式
TCP直接传输二进制数据,不对传输的数据做任何处理,不限制传输数据的大小和格式。

HTTP传输的数据是以ASCII码表示的文本(称为HTTP消息),主要由请求行、请求头、响应行、响应头和响应体等组成,
数据格式有一定的限制,一般被限制在8KB以内。

3.端口号
TCP使用端口号来确定客户端和服务器之间的通道,通常使用默认端口号,
如FTP(21)、SSH(22)、HTTP(80)、HTTPS(443)等。

HTTP使用TCP协议在传输过程中使用的默认端口号是80。

综上所述,TCP是一种协议,主要负责将数据传输到网络上,而HTTP则是一种规范,
主要规定了通信双方进行数据传输和通信的方式。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

7.鉴权方式

8.equals

9.web socket

10.异常处理机制

11.ThreadLocal内存泄露

12.重载和重写的区别

13.String为什么是不可变的

14.方法的参数可以是接口吗

15.什么是拆箱装箱,为什么要转换

自动装箱和拆箱,就是基本类型和引用类型之间的相互转换
为什么要转换 因为不能直接向集合里放入原始类型的值,因为集合只接收对象。
  • 1
  • 2

16.list,map,set有什么区别

17.双亲委派模型

18.ThreadLocal实现原理,父线程的ThreadLocal怎么传递到子线程中,什么场景中用ThreadLocal

19.类加载器都有哪些(1.启动类加载器,2.应用类加载器,3.扩展类加载器,4.自定义类加载器)

20.子类能否重写父类的静态方法?

子类能否重写父类的静态方法

21.JAVA的IO模型(BIO, NIO, AIO)

四种常见的IO模型

22.说说JVM的内存模型

Java虚拟机(JVM)的内存模型定义了Java程序在运行时如何使用计算机内存。
JVM的内存模型分为线程私有和线程共享两部分。

线程私有内存:
每个线程在JVM中都有自己的线程栈(Thread Stack),
线程栈存储了线程的方法调用和局部变量等信息。
线程栈是线程私有的,每个线程有独立的线程栈。
每个方法的调用,JVM会在线程栈中创建一个栈帧(Stack Frame),
用于存储方法的参数、局部变量和部分运算结果。栈帧的大小在编译阶段就确定下来,
以提高执行效率。

另外,每个线程还有自己的程序计数器(Program Counter),
用于指示当前执行的字节码指令的地址。

线程共享内存:
JVM中的线程共享内存包括堆(Heap)和方法区(Method Area)。
堆是Java程序运行时对象实例的存储区域。所有通过new关键字创建的对象都在堆中分配内存。
堆是线程共享的,不同线程的对象可以存储在堆中。

方法区用于存储类的结构信息、运行时常量池、静态变量和方法代码等。
方法区也是线程共享的。

此外,还有一个运行时常量池,它是方法区的一部分,
用于存储编译期生成的字面量和符号引用。
运行时常量池是每个类或接口的常量池表的运行时表示形式。

JVM的内存模型对于多线程的并发访问和内存管理起着重要的作用。
它确保了线程私有的数据不会互相干扰,而线程共享的数据可以被多个线程同时访问。同时,JVM通过垃圾回收器(Garbage Collector)来管理堆中的对象,自动回收不再使用的内存,以提高资源利用效率。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

java 深堆浅堆

深堆(Deep Heap):深堆是指一个对象引用的所有对象的总存储空间,包括该对象本身以及其所有子对象,
无论这些子对象是否被该对象直接引用。简而言之,深堆表示对象及其引用链所占用的全部内存空间。

浅堆(Shallow Heap):浅堆是指一个对象本身在内存中所需的存储空间,
仅考虑该对象本身的实例变量,而不考虑它引用的其他对象。换句话说,
浅堆只表示一个对象本身的大小,不包括其引用的对象所占的空间。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

java 支配树是什么

在Java中,"支配树"通常指的是对象的引用图(Reference Graph)或对象的可达性分析(Reachability Analysis)。支配树用于确定对象之间的引用关系,以及对象的可达性和生命周期。

支配树描述了Java堆中所有对象之间的引用关系。在Java中,每个对象都可以通过引用来访问其他对象。当一个对象引用另一个对象时,我们称之为从一个对象到另一个对象存在一条引用路径。

支配树的构建是通过执行可达性分析算法来完成的。可达性分析从一个或多个根对象开始,递归地遍历对象图,标记所有可以通过引用访问到的对象。任何未标记的对象被认为是不可达的,即它们无法从根对象到达。不可达对象被认为是垃圾,可以由垃圾回收器进行回收。

通过支配树,我们可以确定哪些对象是活动对象,即仍然可以通过引用访问的对象,以及哪些对象已经不可达,可以被回收。这对于Java的垃圾回收和内存管理非常重要。

支配树还可以帮助识别内存泄漏问题。如果一个对象被认为是活动对象(在支配树上可达),但实际上它应该是不可达的,那么可能存在潜在的内存泄漏问题,即对象无法被正确释放。

总而言之,支配树是描述Java堆中对象之间引用关系的一种结构,它对于垃圾回收、内存管理和内存泄漏问题的识别都具有重要作用。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

tomcat

Tomcat 请求过程

用户请求(访问80端口)————>
发送请求发送给tomcat————>
tomcat有一个端口在监听8080端口)
从8080端口进入流量的会被在此端口的线程监听到
给与我们的web容器进行处理(有index.html页面显示出来  从8080端口来时会有交互界面  会读取到index.jsp这种java代码)
但是java代码无法直接使用————>
需要利用到jsp容器
将index.jsp中的java的servlet代码进行翻译
servlet容器处理 通过catalina程序/脚本来处理从JSP容器这过来的servlet代码
并不是直接对接mysql数据库
而是直接对接用户的请求例如app中的某个功能模块
比方说看个人信息  servlet容器与app接是api进行对接(具体的URL)
数据流向最终会通过用户请求内容  去mysql数据库寻找相应的内容
最后返回给用户
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Spring

1.spring循环依赖是怎么解决的

2.静态代理和动态代理(编译器代理和运行期代理,jdk代理和cglib代理)

3.AOP哪些情况下不能做切面 静态方法可以吗,private,final,内部类呢

1.静态代码块:无法动态地将代码块织入代码中,因此无法使用 AOP 进行增强。

2.构造函数:由于构造函数的执行在对象创建时只执行一次,因此无法通过 AOP 对构造函数进行增强。

3.实例化之间的调用:由于 AOP 的切面是在运行时进行织入,因此无法对实例化之间的调用进行增强。

4.私有方法和属性:私有方法和属性只能在类内部访问,无法通过继承和代理方式进行访问。因此,无法使用 AOP 进行切面。

5.静态方法和属性:静态方法和属性属于类级别的,不归属于实例,因此不能被代理,也就无法使用 AOP 切面。

6.装饰器模式:装饰器模式是一种为对象添加功能的模式,其核心思想和 AOP 很相似,但是装饰器模式是通过组合的方式实现增强,无法像 AOP 一样通过动态代理实现横切逻辑。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.transactionTemplate和@transaction的区别

transactionTemplate
优点:编程式管理事务灵活性高,可以更加精细地控制事务的细节。
缺点:需要编写大量的重复代码。

@transaction
优点:可以通过注解的方式管理事务,省去了大量的重复代码,使用方便。
缺点:注解配置的灵活性不如编程式管理事务高,同一个方法中不同的事务配置需要拆分成多个方法,
     业务逻辑不利于阅读和管理。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5.过滤器和拦截器的区别

6.注解的实现原理

7.Spring Bean生命周期

8.Bean是线程安全的还是不安全的

9.spring注入有哪些方式,为什么spring不建议用字段方式注入

10.spring Bean创建的过程

类 ---> 实例化(推断构造方法) ---> 普通对象 ---> 依赖注入 ---> 初始化(afterPropertiesSet)
 ---> 初始化后(aop)---> 代理对象 ---> Map<beanName, Bean对象> 单例池
  • 1
  • 2

11.如果普通对象的类申明带参构造器,不明确指定无参构造器,使用构造器注入,Spring在初始化的普通对象的时候也会调用无参构造方法吗? 不会

12.@value的原理 (Environment 类)

    @Autowired
    private Environment environment;
  • 1
  • 2

13.spring的大事务是什么 (事务执行时间长,回滚时间长)减少不必要的select语句 把sql语句分开写

14.spring事务的原理

1.开启事务
2.事务管理器 DataSource对象去创建数据库连接conn 然后放到本地线程变量ThreadLocal里面  ThreadLocal<Map<DataSource, conn>>
比如一个方法里面有三条更新的语句
1.事务管理器 DataSource对象去创建数据库连接conn 然后放到本地线程变量ThreadLocal里面
2.设置conn.autocommit = false
3.执行一二三条更新语句的时候,从本地线程变量ThreadLocal里面把数据库连接取出来,这样执行的sql连接就是同一个
4.有错误就rollback 没有错误就commit
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

15.如果在加了事务注解的方法里面 新开一个线程,那么这个新开的线程的方法里面出错 事务是否会回滚

答:不会 因为在同一个线程里面 从ThreadLocal里面拿到的数据库连接是同一个,但是不同的线程,拿到的就不是同一个数据库连接
  • 1

16.为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,
这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。
  • 1
  • 2
  • 3

17.Spring注解导入:@Import使用及原理详解

@Import使用及原理详解

18.浅析Spring中AOP的实现原理——动态代理

浅析Spring中AOP的实现原理——动态代理

19.方法上面加@Transactional之后,spring使用的代理是什么代理 JDK动态代理?CGLib动态代理?

答:使用的CGLib动态代理,因为JDK动态代理需要为每个代理类生成一个接口,但是我们不可能会给每个加了@Transactional
的方法或者类单独生成一个接口,所以使用了CGLib动态代理
  • 1
  • 2

SpringMVC

1.SpringMVC 工作流程

SpringWeb

SpringBoot

1.SpringBoot 自动装配原理

2.@Autowired和构造函数注入的区别

3.SpringBoot的starter是什么

4.springboot的启动流程

Spring Boot 的启动流程主要包括以下几个步骤:

1.加载配置:Spring Boot 使用默认的约定来加载应用程序的配置。
它会查找主配置类(通常是带有@SpringBootApplication注解的类),
并基于约定从不同的位置加载配置文件,如application.properties或application.yml等。

2.启动应用上下文:Spring Boot 会创建一个应用上下文(Application Context),
该上下文是 Spring 框架的核心容器,用于管理和协调各个组件的生命周期和依赖关系。
应用上下文根据配置文件和注解等信息进行初始化,并加载所有的 Bean 定义。在这一步,
也会启动 Spring Boot 的自动配置机制,根据类路径上的依赖和配置,自动配置应用程序的各个组件。

3.执行命令行运行器和启动监听器:在应用上下文完全初始化之后,
Spring Boot 会执行命令行运行器(CommandLineRunner)和启动监听器(ApplicationRunner)。
这些组件允许开发人员在应用程序启动完成后执行特定的代码逻辑,如数据初始化、缓存预热等。

4.启动内嵌的 Web 服务器:如果应用程序是一个 Web 应用程序,
Spring Boot 会启动内嵌的 Web 服务器,如 Tomcat、Jetty 或 Undertow。
Spring Boot 提供了自动配置来简化 Web 服务器的配置,开发者只需提供一些基本的配置信息即可。

5.运行应用程序:一旦 Web 服务器启动成功,Spring Boot 会将控制权交给应用程序,应用程序开始处理用户请求。
根据路由和控制器的配置,Spring Boot 将请求分派给相应的处理程序,并将结果返回给客户端。

除了以上的主要步骤,Spring Boot 还涉及其他一些重要的概念和特性,如自动配置、依赖管理、热部署等,这些都为快速开发和部署应用程序提供了便利。总的来说,Spring Boot 的启动流程是一个自动化的、基于约定的过程,旨在简化应用程序的配置和部署,提高开发效率。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Mysql

1.mysql如何解决幻读,脏读,不可重复读

2.如何解决大表查询

3.or和in是否会用到索引

在 SQL 查询中使用 OR 和 IN 等逻辑操作符时,数据库系统可以使用索引进行优化查询性能,
但是否能使用索引仍然具有一定的限制。
对于 OR 操作符,在有些情况下可以使用索引,有些情况下则无法使用。
例如,当 SQL 语句使用 OR 操作符连接多个列时,如果每个列上都存在一个独立的索引,
那么数据库系统可以通过这些索引查询相应的行,然后将结果进行合并。
这种情况下,OR 操作符可以使用索引进行优化。
但是,如果 SQL 语句中多个 OR 条件涉及到同一个列时,并且该列上有一个索引,
那么数据库系统很可能无法使用索引。
这是因为,如果要使用索引,那么必须扫描整个索引来查找满足条件的行,但当一个列上有多个 OR 条件时,
每个条件可能返回大量的行,因此使用索引可能会比全表扫描更慢。在这种情况下,使用索引反而可能会降低查询性能。
对于 IN 操作符,如果 IN 列上有一个索引,那么数据库系统可以使用索引,从而加速查询速度。如果 IN 列上没有索引,
则数据库系统通常会将其转换为一组 OR 条件,并尝试使用现有的索引优化查询。当 IN 序列中的值非常少时,
使用索引通常比全表扫描更高效。
综上所述,使用 OR 和 IN 等操作符是否能够使用索引,还要看具体的情况。
如果有独立列的索引,或 IN 列上有索引,那么可以使用索引进行优化,否则可能无法使用索引或使用索引反而会降低查询性能。
因此,在编写 SQL 语句时,应该根据具体情况,合理使用这些操作符,并考虑如何最大限度地利用索引优化查询性能。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.mysql间隙锁

5.索引的基本原理

6.mysql聚簇和非聚簇索引的区别

7.mysql索引结构 各自的优劣势

8.索引的设计原则

9.mysql锁的类型有哪些

10.mysql执行计划怎么看

11.事务的基本特性和隔离级别

12.怎么统计慢查询(开启MySQL慢日志),怎么处理慢查询

13.ACID靠什么保证

14.什么是MVCC

15.mysql 主从同步的原理

16.简述mysql中索引类型及对数据库性能的影响

17.innodb和Myismal的区别

18.缓存和数据库不一致的问题,怎么解决

Mybatis

#{}和${}的区别

Mybatis的优缺点

Mybatis 是一种优秀的 ORM(Object-Relational Mapping)框架,提供了一种灵活、高效、可靠的数据库访问方式。其优缺点如下:

优点:

1.SQL 控制灵活:Mybatis 遵循 SQL 分离的原则,在 XML 文件中单独配置 SQL 语句,可以灵活地控制 SQL 执行,支持编写动态 SQL。

2.轻量级框架:Mybatis 实现了 ORM 映射的基本功能,但是具有更轻量、更灵活的特点,相比于 Hibernate 等 ORM 框架而言,Mybatis 的学习成本更低。

3.性能强悍:Mybatis 框架本身非常轻量,不会对应用的性能造成很大的影响。同时,Mybatis 支持二级缓存和注解方式,也可以自定义插件对 SQL 进行拦截和增强。

4.易于集成:Mybatis 提供了非常方便的工具类和插件,可以与各种第三方框架完美集成,如 Spring、Spring Boot 等。

缺点:

1.学习成本高:Mybatis 相比于 JPA 等 ORM 框架而言,学习成本较高,需要了解 SQL 和 XML 等知识。

2.配置文件繁琐:Mybatis 需要通过 XML 配置实体对象和 SQL 语句的关系,配置文件可能会比较复杂,需要仔细校验和测试。

3.缺乏自动化功能:Mybatis 相对于 JPA 等 ORM 框架而言,缺少自动化功能,需要手动编写对应的 XML 文件进行映射,开发效率相对较低。

4.无法满足复杂业务需求:Mybatis 虽然可以自由地编写 SQL 语句,但相对于 Hibernate 等 ORM 框架而言,无法满足复杂业务需求
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

Redis

1.如何实现缓存一致性

2.Jedis和Lettuce的区别

3.redis序列化

4.RDB和AOF机制

5.redis过期键的删除策略

6.redis线程模型,单线程为什么快

7.缓存雪崩,缓存穿透,缓存击穿

8.redis主从复制核心原理

9.redis集群方案

10.redis集群扩容时数据迁移

Alt
流程说明:

  • 1)对目标节点发送cluster setslot{slot}importing{sourceNodeId}命令, 让目标节点准备导入槽的数据。
  • 2)对源节点发送cluster setslot{slot}migrating{targetNodeId}命令, 让源节点准备迁出槽的数据。
  • 3)源节点循环执行cluster getkeysinslot{slot}{count}命令, 获取count个属于槽{slot}的键。
  • 4)在源节点上执行migrate{targetIp}{targetPort}""0{timeout}keys{keys…}命令, 把获取的键通过流水线(pipeline) 机制批量迁移到目标节点, 批量迁移版本的migrate命令在Redis3.0.6以上版本提供, 之前的migrate命令只能单个键迁移。 对于大量key的场景, 批量键迁移将极大降低节点之间网络IO次数。
  • 5)重复执行步骤3) 和步骤4) 直到槽下所有的键值数据迁移到目标节点。
  • 6)向集群内所有主节点发送cluster setslot{slot}node{targetNodeId}命令, 通知槽分配给目标节点。 为了保证槽节点映射变更及时传播, 需要遍历发送给所有主节点更新被迁移的槽指向新节点。

使用伪代码模拟迁移过程如下:
Alt

11.因为 Redis 是惰性删除规则,当一个 Key 已经过期了,刚好卡在还没被删除期间,这个时候去 hasKey 会返回 true 还是 false

亲测:redis6.0.8版本
使用EXISTS myhash验证过期key,返回false
  • 1
  • 2

RabbitMQ

1.如何保证MQ消息不丢失,如何实现延时推送等

2.rocketMQ和Kafka的区别

3.一个消费者订阅多个topic,其中一个对接积压会影响消费者的性能吗 (会)

当一个消费者订阅了多个topic时,如果其中一个topic出现对接积压,会导致消费者在等待这个topic的消息时被阻塞,

从而无法处理其他topic的消息。这将导致消费者的性能下降,因为它无法及时处理来自其他topic的新消息。

此外,如果对接积压导致消息积压,会占用消费者的内存和磁盘空间,从而影响消费者的运行。

为了避免这种情况发生,需要及时发现和解决对接积压,缩短消息处理的响应时间,以保证消费者的可用性和性能。

同时,可以通过增加消费者实例,提升系统并发能力,避免对接积压对整个系统的影响。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.简述RabbitMQ的架构设计

5.RabbitMQ如何确保消息发送,消息接收?

6.RabbitMQ事务消息

7.RabbitMQ死信队列,延时队列

JVM

1.jvm类加载到执行内存变化过程,垃圾回收从哪寻根,GC Root都有哪些内容

1.加载阶段:虚拟机通过类加载器(ClassLoader)将类字节码加载到内存中。

2.链接阶段

2.1.验证阶段:验证字节码的正确性和合法性,包括语法分析、语义分析等。

2.2.准备阶段:为类的静态变量分配内存并赋初值。

2.3.解析阶段:将符号引用替换为直接引用。

3.初始化阶段:执行静态变量赋值和静态代码块,并调用类的初始化方法。

垃圾回收从哪寻根回收的:

垃圾回收器通过寻找对象之间的引用来判断哪些对象仍然在使用中,哪些对象已经可以被回收。垃圾回收的起点被称为 GC 根(GC Roots),其包括以下几种类型:

1.Java 虚拟机栈中引用的对象

2.本地方法栈中引用的对象

3.方法区中类静态属性引用的对象

4.方法区中常量引用的对象

5.JNI(Java Native Interface)中引用的对象

GC Roots 中所有对象都是不可回收的,垃圾回收器会从这些点开始遍历整个对象图,找到所有与 GC Roots 不可达的对象,并将它们标记(至少会标记两遍)为可回收的对象进行回收。

GC Roots 中的对象是垃圾回收的起点,同时也是防止 Java 对象被过早回收的保障,只要对象被 GC Roots 可达,就说明它还在使用中,不会被回收。

GC Roots 是一个相对稳定的结构,不会随着垃圾回收执行而发生变化。

GC Roots 的实现和具体的 JVM 实现有关,不同的 JVM 可能会有一些额外的类型来实现 GC Roots。例如,VM 类型的对象引用了堆中的对象,也被视为 GC Roots。

GC Roots 与具体的垃圾收集算法实现无关,不管采用的是 Mark-Sweep 还是 Copy 算法,都需要先通过 GC Roots 寻找可到达的对象进行标记。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

2.类加载机制,怎么能破坏这个机制

ava中的类加载机制是指JVM在运行时动态加载所需的类文件并生成相应的字节码,用于执行程序。
Java的类加载过程主要包括加载、连接和初始化三个阶段。

加载阶段:Java虚拟机从文件系统、ZIP包或网络中加载类的二进制字节流,
然后通过ClassLoader把字节码转化为类的实例对象。

连接阶段:连接分为验证、准备和解析三个阶段。验证阶段主要是对class文件进行验证;
准备阶段主要是为类变量分配内存并设置默认值;解析阶段主要是将常量池中的符号引用转化为直接引用。

初始化阶段:是类加载过程的最后一个阶段。当类被初始化时,静态变量被赋予正确的初值,并执行静态代码块。

要破坏类加载机制,我们需要针对这些阶段进行攻击,下面列出了一些具体的方式:

1.修改class文件内容。

2.自定义ClassLoader来绕过双亲委派机制。

3.在类的初始化过程中执行恶意代码。

4.使用Java Agent来修改字节码。

5.通过反射机制调用私有方法。

6.直接使用Unsafe来操作Java对象。

总的来说,要破坏类加载机制,我们需要深入了解它的原理,并通过各种手段来绕过它的校验和限制。
当然,这些手段都是不被推荐的,因为这可能会对程序的正常运行产生严重影响
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

3.一次完整的GC是怎么样的,从YGC到FGC到OOM

一次完整的垃圾回收(GC)过程通常由Young Generation Collection(YGC)、Full GC(FGC)
和OutOfMemoryError(OOM)组成。下面是它们的典型执行顺序和步骤:

Young Generation Collection (YGC):
1.首先,垃圾回收器会扫描Young Generation(年轻代)中的对象,标记出所有未被引用的对象作为垃圾。
  然后,垃圾回收器会使用复制算法(Copying Algorithm)将存活的对象从Young Generation中复制到另一个区域(通常是Survivor Space)。
  复制过程中,回收器会同时进行对象的年龄累加,即每次经过复制的对象年龄增加,达到一定年龄阈值的对象则会晋升到老年代。
  最后,回收器会清理并整理Young Generation中的垃圾对象,将内存空间回收释放。这样就完成了一次YGC。
  Full GC (FGC):

2.当年轻代的Survivor Space不足以存放活跃对象时,一些存活对象可能会被直接晋升到老年代,或者在长期运行时,老年代中产生了大量的垃圾对象。
  当老年代需要进行垃圾回收时,就会触发Full GC。Full GC会同时扫描和回收整个堆内存,包括Young Generation和Old Generation(老年代)。
  Full GC的执行过程可能涉及不同的垃圾回收算法,如标记-清除(Mark-Sweep)、标记-整理(Mark-Compact)等。
  Full GC通常需要较长的暂停时间,因为它需要扫描和处理整个堆内存的对象,导致应用程序的停顿。
  Out of Memory Error (OOM):

3.如果在进行垃圾回收的过程中,无法释放足够的内存来满足应用程序的需求,则可能会发生OutOfMemoryError。
  OutOfMemoryError表示堆内存已经耗尽,无法分配更多的对象。这通常是由于应用程序的资源使用超过了可用的内存限制所引起的。
  当发生OOM时,应用程序无法正常继续执行,通常会抛出异常或导致应用程序崩溃。
需要注意的是,完整的GC过程是根据具体的垃圾回收算法和垃圾回收器实现而有所变化。不同的垃圾回收器可能有自己的执行策略和步骤。上述描述仅提供了一般的情况下的执行序列。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

4.年轻代为什么用复制算法,survivor放不下怎么办,老年代也放不下呢

5.JVM调优做过吗,能调什么

6.自己手写一个虚拟机的话,主要考虑哪些结构(主要考虑:类加载器和执行引擎)

7.堆(年轻代 比例1【伊甸园区 比例8,幸存区from 比例1,幸存区to 比例1】,老年代 比例2),永久代(也就是方法区 java1.8之后是元空间)(伊甸园区 幸存区from 幸存区to 默认比例式6:1:1, 想要8:1:1要自己去手动加jvm参数设置)

8.伊甸园区满了会进行轻gc,但是幸存区from满了会进行一次重gc,重gc之后幸存区from依旧是满的,那么就会启动担保策略,从伊甸园区出去的对象会直接被分配到老年代

9.对象分配策略

在这里插入图片描述

10.内存分配策略

如果对象在Eden 出生并经过第一次MinorGC 后仍然存活,并且能被Survivor容纳的话,
将被移动到survilvor 空间中, 并将对象年龄设为1 。对象在survivor 区中每熬过一次MinorGC ,
年龄就增加1 岁,当它的年龄增加到一定程度(默认为15 岁,其实每个JVM、每个GC都有所不同)时,
就会被晋升到老年代中

针对不同年龄段的对象分配原则如下所示:
 优先分配到Eden
 大对象直接分配到老年代
  尽量避免程序中出现过多的大对象
 长期存活的对象分配到老年代
 动态对象年龄判断
  如果survivor区中相同年龄的所有对象大小的总和大于survivor空间的一半,
  年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold 中要求的年龄。
空间分配担保
-XX:HandlePromotionFailure
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

11.堆是分配对象存储的唯一选择吗?

不是唯一选择,对于那些在方法内创建的对象并且没有被外部引用,这些对象会进行栈上分配,优点:随着栈的销毁而被销毁
  • 1

12.如何解决oom

1、要解决OOM异常或heap space的异常,一般的手段是(如Eclipse Memory Analyzer)对dump 
   出来的堆转诸快照进行分析重点是确认内存中的对象是否是必要的,
   也就是要先分清楚到底是出现了内存泄漏(MemeLeak)还是内存溢出 (Memory overflow)
  • 1
  • 2
  • 3

13.方法区存储什么

它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。
1.类型信息(对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息):
    a.这个类型的完整有效名称(全名=包名类名)
    b.这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
    c.这个类型的修饰符(public,abstract, final的某个子集)
    d.这个类型直接接口的一个有序列表
2.常量池中有什么
    a.数量值
    b.字符串值
    c.类引用
    d.字段引用
    e.方法引用
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

14.方法区的演进细节

1.首先明确:只有Hotspot才有永久代。BEA JRockit、IBM J9等来说,是不存在永久代的概念的。
   原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。
2.Hotspot中方法区的变化:
    jdk1.6及之前 : 有永久代(permanent generation),永久代上;
    jdk1.7      : 有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中;
    jdk1.8及之后 : 无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

15.永久代为什么要被元空间替换

1.为永久代设置空间大小是很难确定的。
    在某些场景下,如果动态加载类过多,容易产生Perm 区的OOM。比如某个实际web工程中
    ,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。
    
    而元空间和永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存因此,
    默认情况下,元空间的大小仅受本地内存限制。
2.对永久代进行调优是很困难的。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

16.常量池为什么要移到堆里面

因为永久代的回收效率很低,在full gc的时候才会触发。
而full gc是老年代的空间不足、永久代不足时才会触发。
这就导致常量池回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,
导致永久代内存不足。放到堆里,能及时回收内存。
  • 1
  • 2
  • 3
  • 4

17.方法区的垃圾收集

1.需要同时满足下面三个条件
    a.该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
    b.加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
    c.该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 1
  • 2
  • 3
  • 4

18.java静态变量存在在堆还是在方法区?

首先肯定:静态变量是被对象共享的,随着类加载而产生(不用实例化即可访问)
jdk8之前:在方法区
jdk8以后:存放在堆中反射的class对象(即类加载后会在堆中生成一个对应的class对象)的尾部

但是!!!静态引用对应的对象实体始终都存在堆空间
(也就是 new出来之后 的对象的实体 是在堆空间保存的)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

19.对象的创建方式

1.new
2.Class的newInstance()
3.Constructor的newInstance(Xxx)
4.使用clone()
5.使用反序列化
6.第三方库Objenesis
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

20.对象的实例化

1.判断对象对应的类是否加载,链接,初始化
2.为对象分配内存
3.处理并发安全问题
4.初始化分配到的空间
5.设置对象的对象头
6.执行init方法进行初始化
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

21.对象的内存布局

1.内存布局
    a.对象头(包含两部分:1.运行时元数据,2.类型指针)说明:如果是数组,还需记录数组的长度
    b.实例数据
    c.对齐填充
   
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
在这里插入图片描述

22.JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?

对象访问方式主要有两种:
    1.句柄访问
    2.直接指针(Hotspot采用)
  • 1
  • 2
  • 3

23.执行引擎的工作过程

1.执行引擎在执行的过程中究竟需要执行什么样的字节码指令完全依赖于程序计数器。
2.每当执行完一项指令操作后PC寄存器就会更新下一条需要被执行的指令地址。
3.当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。
  • 1
  • 2
  • 3

24.JIT是什么,java已经有解释器了为什么会需要它

JIT即时编译器

当Java虚拟器启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成后再执行,
这样可以省去许多不必要的编译时间。随着时间的推移,编译器发挥作用,把越来越多的代码编译成本地代码,
获得更高的执行效率
  • 1
  • 2
  • 3
  • 4
  • 5

25.JIT的热点代码及探测方式

1.什么是热点代码
当然是否需要启动JIT编译器将字节码直接编译为对应平台的本地机器指令,
则需要根据代码被调用执行的频率而定。关于那些需要被编译为本地代码的字节码,
也被称之为“热点代码”,JIT编译器在运行时会针对那些频繁被调用的“热点代码”做出深度优化,
将其直接编译为对应平台的本地机器指令,以此提升Java程序的执行性能。

一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为“热点代码”,
因此都可以通过JIT编译器编译为本地机器指令。由于这种编译方式发生在方法的执行过程中,因此也被称之为栈上替换,或简称为OSR

2.热点代码及探测方式
目前HotSpot VM所采用的热点探测方式是基于计数器的热点探测
采用基于计数器的热点探测,HotSpot VM将会为每一个方法都建立2个不同类型的计数器,
分别为方法调用计数器 (Invocation Counter)和回边计数器(BackEdge Counter)
    a.方法调用计数器用于统计方法的调用次数
    b.回边计数器则用于统计循环体执行的循环次数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

26.方法调用计数器

当一个方法被调用时,会先检查该方法是否存在被 JIT 编译过的版本,
如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,
则将此方法的调用计数器值加 
1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的闯值。
如果已超过闯值,那么将会向即时编译器提交一个该方法的代码编译请求。

阈值: client 模式下是 1500次,在 Server 模式下是 10000次。超过这个阅值,就会触发JIT编译。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

27.热度衰减

如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,
而是一个相对的执行频率,即一段时间之内方法被调用的次数。
当超过一定的时简限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,
那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),
而这段时间就称为此方法统计的半衰周期 (Counter Half life Time)
  • 1
  • 2
  • 3
  • 4
  • 5

28.String相关面试题

package com.demo.kxf.demo05;

public class text02 {
    public static void main(String[] args) {
        /**
         * 1.字符串拼接操作不一定都是使用的事StringBuilder
         * 如果拼接符号左右两边都是字符串常量或者常量引用,则仍然使用编译器优化,即非StringBuilder的方式
         * 2.针对final修饰类,方法,基本数据类型,引用数据类型的结构时,能使用上final的时候建议使用上
         */
        final String s1 = "a";
        final String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

29.new String()到底创建了几个对象

package com.demo.kxf.demo05;

public class text02 {
    public static void main(String[] args) {
        /**
         * new String("ab")会创建几个对象?两个
         *  一个对象是:new 关键字在堆空间创建的
         *  另外一个对象是:字符串常量池中的对象
         */

        /**
         * new String("a") + new String("b")呢?
         * 对象1:new StringBuilder()
         * 对象2:new String("a")
         * 对象3:常量池中的a
         * 对象4:new String("b")
         * 对象5:常量池中的b
         * 
         * 深入剖析:StringBuilder的toString();
         * 对象6:new String("ab")
         * 强调一下,toString()的调用,在字符串常量池中,没有生产ab
         * 原因:在编译阶段,jvm根本不知道stringBuild的 value值具体是多少,
         *      所以没办法在字节码文件中将值加入到常量池中,而这也是为什么变量拼接时会创建新对象的原因,因为没有办法判断值
         */

        /**
         * String s1 = new String("ab");
         * s1.inter();
         * String s2 = "ab";
         * System.out.println(s1 == s2); //false
         * 
         * 解释:s1指向的是堆空间,堆空间又指向了常量池,s2这种声明方式是直接指向常量池的 所以s1 != s2
         */
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

30.什么是垃圾

/**
 * 1.程序运行过程中没有任何指针指向的对象,这个对象就是需要被回收的垃圾
 */
  • 1
  • 2
  • 3

31.虚拟机中的对象一般处于几种可能的状态

/**
 * 3种,
 * 1.可触及的:从根节点开始,可以达到这个对象
 * 2.可复活的:对象所有引用都被释放,但是对象有可能在finalize()中复活
 * 3.不可触及的:对象finalize()被调用过了,并且没有复活,那么就会进入不可触及状态。
 *   不可触及的对象不能被复活,因为finalize()只会被调用一次
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

32.判定一个对象objA是否可回收,至少要经历两次标记过程

/**
 * 1.如果对象objA到 GC Roots没有引用链,则进行第一次标记。
 * 2.进行筛选,判断此对象是否有必要执行finalize()方法
 * 解释:1.判断objA是否重写了finalize()方法,没有重写,则虚拟机视为“没有必要执行”,
 *      2.如果objA重写了finalize()方法,但是还未执行过,那么objA会被插入到F-Queue队列中,
 *        由一个虚拟机自动创建的,低优先级的finalizer线程触发其finalize()方法执行
 *      3.finalize()方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记
 *      如果objA在finalize()方法中与引用链上的任何一个对象建立了联系
 *      那么在第二次标记时,objA会被移出“即将回收”集合。之后,对象会再次出现没有引用存在的情况。
 *      在这个情况下,finalize方法不会被再次调用,对象会直接变成不可触及的状态,
 *      也就是说,一个对象的finalize方法只会被调用一次。
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

33.增量收集算法

/**
 * 在已有算法(标记清除,复制算法,标记压缩整理,分代算法)
 * 在垃圾回收过程中,应用软件将处于一种stop the world的状态。
 * 在stop the world 状态下,应用程序所有的线程都会挂起,暂停-切正常的工作,
 * 等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很久,
 * 将严重影响用户体验或者系统的稳定性。为了解决这个问题,
 * 即对实时垃圾收集算法的研究直接导致了增量收集 (Incremental Collecting)算注的诞生。
 * 
 * 基本思想:
 *  如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,
 *  那么就可以让垃圾收集线程和应用程序线程交替执行。
 *  每次,垃圾收集线程只收集一小片区域的内存空间,
 *  接着切换到应用程序线程。依次反复,直到垃圾收集完成。
 *  
 * 缺点:
 *  使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,
 *  所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,
 *  会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

34.内存泄漏

/**
 * 也称作“存储渗漏”。严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。
 */
  • 1
  • 2
  • 3

35.内存溢出

/**
 * 没有空闲内存了,内存塞不下了
 * 原因:
 * 1.Java虚拟机的内设不够
 * 2.代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

36.垃圾回收的并发与并行

/**
 * 1.并行《Parale1):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
 * 2.串行:相较于并行的慑念,单线程执行,如果内存不够,则程序暂停,
 *        启动JVM垃圾回收器进行垃圾回收、回收完,再启动程序的线程
 */
  • 1
  • 2
  • 3
  • 4
  • 5

37.安全点(safe Point)

/**
 * 1.如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来呢?
 *      抢先式中断:(目前没有虚拟机采用了)
 *        首先中断所有线程工如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
 *      主动式中断:
 *        设置一个中断标志,各个线程运行到safe Point的时候主动轮询这个标志如果中断标志为真,
 *        则将自己进行中断挂起。
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

38.安全区域(Safe Region)

/**
 * 介绍:
 *      safepoint 机制保证了程序执行时,在不太长的时间内就会遇到可进入 GC的 Safepoint。
 *      但是,程序“不执行”的时候呢?例如线程处于 sleep 状态或 Blocked 状态,
 *      这时候线程无法响应 JVM 的中断请求,“走”到安全点去中断挂起,
 *      JVM 也不太可能等待线程被唤醒。对于这种情况,就需要安全区域(Safe Region)来解决。
 *      
 *      安全区域是指在一段代码片段中,对象的引用关系不会发生变化,
 *      在这个区域中的任何位置开始GC都是安全的。我们也可以把 Safe Region 
 *      看做是被扩展了的 Safepoint
 * 实际执行时:
 *      1、当线程运行到Safe Region的代码时,首先标识已经进入了safe Region,
 *         如果这段时间内发生GC,JVM会忽略标识为Safe Region状态的线程;
 *      2、当线程即将离开safe Region时,会检查JVM是否已经完成GC,
 *         如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开safe Region的信号为止;
 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

39.java的各种引用

强引用(不回收)(strongReference):
    最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,
    即类似“object obj=new object()”这种引用关系。无论任何情况下只要强引用关系还存在,
    垃圾收集器就永远不会回收掉被引用的对象。
软引用(内存不足即回收) (SoftReference):
    在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。
    如果这次回收后还没有足够的内存,才会抛出内存溢出异常
弱引用(发现即回收) (weakReference): 
    被弱引用关联的对象只能生存到下一次垃圾收集之前。当垃圾收集器工作时,
    无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用(对象回收跟踪) (PhantomReference):
    一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,
    也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的
    就是能在这个对象被收集器回收时收到一个系统通知。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

40.java的垃圾收集器跳转链接

串行:Serial, Serial Old
并行:ParNew Parallel Scavenge, Parallel Old
并发:CMS, G1

Serial:串行
ParNew:并行回收
Parallel:并行回收 吞吐量优先
CMS:低延迟
G1:区域化分代式

问题:为什么cms用的算法是标记清除而不是标记整理(压缩)呢
    答:因为cms回收期间,用户线程是在工作的,如果用标记整理的话,会导致内存地址的更改,所以这是不行的
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

41.java的GC日志详解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

字节码篇

1.看一道题

package com.demo.kxf.demo06;

class Father {
    int x = 10;
     public Father() {
         this.print();
         x = 20;
         System.out.println(1321231232);
     }

    public void print() {
         System.out.println("Father.x  = " + x);
    }
}

class Son extends Father {
    int x = 30;
    public Son() {
        this.print();
        x = 40;
    }

    public void print() {
        System.out.println("Son.x  = " + x);
    }
}

public class text03 {
    public static void main(String[] args) {
        Father father = new Son();
        System.out.println(father.x);
        /**
         * 打印:
         * Son.x  = 0
         * 1321231232
         * Son.x  = 30
         * 20
         */
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

2.类的主动使用与被动使用

package com.demo.kxf.demo06;

import java.util.Random;

public class text04 {
    public static void main(String[] args) {
        /**
         * 一,主动使用
         *      1.当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
         *      2.当调用类的静态方法时,即当使用了宇节码invokestatic指令。
         *      3.当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用etstatic或者putstatic指令。(对应访问变量赋值变量操作》
         *      4.当使用java,lang,reflect包中的方法反射类的方法时,比如; Class,forName("com,atguigu.java,Test")
         *      5.当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
         *      6.如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
         *      7.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
         *      8.当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类,
         *        (涉及解析REF_getStatic、REF_putStatic、REF invokeStatic方法句柄对应的类)
         *
         *      针对5,补充说明:
         *            当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。
         *            在初始化一个类时,并不会先初始化它所实现的接口
         *            在初始化一个接口时,并不会先初始化它的父接口因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。
         *            只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化。
         * 二,被动使用
         *      解释:被动使用不会引起类的初始化。并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化。
         *      1.当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。
         *      2.当通过子类引用父类的静态变量,不会导致子类初始化
         *      3.通过数组定义类引用,不会触发此类的初始化
         *      4.引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。
         *      5.调用classLoader类的loadclass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。
         */

        text04.text01();
    }

    public static void text01() {
        //        System.out.println(User.num);
        /**
         * System.out.println(User.num);
         * 打印:
         * User类的初始化
         * 1
         */

        //        System.out.println(User.num1);
        /**
         * System.out.println(User.num);
         * 打印:
         * 1
         */

        System.out.println(User.num2);
        /**
         * System.out.println(User.num);
         * 打印:
         * User类的初始化
         * 5(随机数)
         */
    }
}


class User {
    static {
        System.out.println("User类的初始化");
    }

    public static int num = 1;
    public static final int num1 = 1;
    public static final int num2 = new Random().nextInt(10);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

在这里插入图片描述

JUC并发编程

1.多线程处理事务问题(一个线程异常 其余线程全部回滚)

package com.demo.kxf.demo01;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

public class demo01 {
    public static void main(String[] args) {
        // 每个线程处理多少条数据
        Integer count = 100;
        // 模拟数据
        ArrayList arrayList = new ArrayList<Integer>();
        // 数据处理之后放的地方(此处模拟数据库)
        ArrayList mysqlArrayList = new ArrayList<Integer>();

        // 模拟数据 1150条数据
        for (int i = 0; i < 1150; i++) {
            arrayList.add(i + 1);
        }

        // 计算多少数据
        int size = arrayList.size();
        // 根据数据量大小 使用几个线程
        int severalThreads = size / count;
        // 看看有没有余数
        int oneThreads = size % count;
        // 有余数在多加一个线程
        if(oneThreads > 0) {
            severalThreads++;
        }

        // 阻塞主线程的CountDownLatch
        CountDownLatch mainCountDownLatch = new CountDownLatch(1);
        // 阻塞子线程的CountDownLatch 
        CountDownLatch countDownLatch = new CountDownLatch(severalThreads);
        // 控制是否有错误回滚  只要有一个子线程出错  其余子线程全部回滚
        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
        // 线程池
        ExecutorService executorService = Executors.newFixedThreadPool(severalThreads);
        for (int i = 0; i < severalThreads; i++) {
            // 拿到每个线程需要处理的数据
            List collect = (List)arrayList.stream().skip(i * count).limit(count).collect(Collectors.toList());
            executorService.execute(()->{
                collect.forEach(item -> {
                    try {
                        // 同步锁
                        synchronized (mysqlArrayList) {
                            System.out.println(item + "----" + Thread.currentThread().getName());
                            mysqlArrayList.add(item);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        atomicBoolean.set(true);
                    }
                });


                try {
                    TimeUnit.SECONDS.sleep(10);
                    // 处理完数据 子线程锁就放开一个
                    countDownLatch.countDown();
                    // 在此处 根据主线的CountDownLatch 进行阻塞  等待主线程处理完毕在执行
                    mainCountDownLatch.await();
                } catch (InterruptedException e) {
                    atomicBoolean.set(true);
                    e.printStackTrace();
                }
                if(atomicBoolean.get()) {
                    System.out.println("我抛异常啦");
                }
                System.out.println(Thread.currentThread().getName() + "---" + "执行完毕");
            });
        }
        System.out.println("打印打印");
        try {
            // 根据子线程的countDownLatch进行阻塞  等待所有子线程数据处理完毕 在开始走主线程的代码
            countDownLatch.await();
            System.out.println("阻塞阻塞");
        } catch (InterruptedException e) {
            atomicBoolean.set(true);
            e.printStackTrace();
        }
        // 主线程同步锁减一
        mainCountDownLatch.countDown();
        if(atomicBoolean.get()) {
            System.out.println("我抛异常啦....");
        }
        Collections.sort(mysqlArrayList);
        System.out.println(mysqlArrayList);
        System.out.println(mysqlArrayList.size());
        // 关闭线程池
        executorService.shutdown();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

2.synchronized的可重入怎么实现

3.synchronized静态方法和普通方法锁的对象是什么

4.Java对象的锁结构放在什么地方

5.ThreadLocal的内部结构,创建的ThreadLocal的引用放在Thread里

6.线程池core=5,max=10,q=100,任务执行时间足够长,问最多可提交多少任务

7.线程池参数设计会根据什么因素考量,多线程应用场景(用户登录发权益)

线程池参数设计会根据什么因素考量

会根据cpu密集型和io密集型

需要注意的是,线程池的大小不应该超过 CPU 核数的两倍左右,因为过多的线程会增加系统上下文切换的负担,

从而导致性能降低。同时,也应该设置适当的线程保活时间,以便避免线程过多占用系统资源。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

8.线程之间的通信如何实现

1.共享内存:多个线程共享一个数据缓存区,可以互相访问和修改其中的数据。这种方式效率高,但是需要进行加锁保护,以避免竞争条件导致的数据错误。

2.信号量:信号量是一个计数器,在多个线程之间传递信号和控制执行顺序。可以使用信号量来实现同步和互斥操作,避免多个线程同时对同一资源进行访问。

3.互斥锁:互斥锁是一种用于保护共享资源的锁,可以防止多个线程同时修改同一数据。通过对共享资源进行加锁和解锁操作,可以防止数据竞争和不一致问题。

4.:条件变量可以阻塞线程执行,只有当某个条件满足时,才允许线程执行。通过使用条件变量,可以实现线程间的协调和同步。

5.消息队列:多个线程可以向同一个消息队列发送消息,消息队列则在内部维护一个消息缓存区,用于存储消息。其他线程可以从消息队列中接收消息,并进行相应的处理。

6.管道:管道是一种文件系统抽象,可以用于进程间或线程间的通信。通过管道,线程可以相互传递数据。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

9.商品库存管理如何防止超卖

1.加锁操作
当多个线程同时操作同一个商品库存时,需要对库存操作加锁,避免出现同一件商品被多人购买的情况。加锁可以使用数据库的行级锁或者分布式锁等方式来实现。

2.库存预警
当商品库存量接近下限时,应该及时进行预警,避免超卖问题。可以设置库存阈值,并在达到指定库存阈值时,及时发送短信或者邮件通知库存管理人员和运营人员。

3.延时队列
可以使用消息队列或者延时队列技术来实现操作的异步化。在商品下单时将库存变动的消息发送到消息队列或者延时队列中,库存操作变为异步处理,从而避免多个线程同时操作一个商品库存的情况。

4.库存扣减的同时,进行库存负载均衡
当有多个系统同时操作同一款商品库存,需要进行库存负载均衡,将请求分配到不同的服务器上,同时保证每个服务器上的库存的总量不能超过商品实际的库存总量。

综上所述,对于商品库存管理防止超卖,需要加锁操作,进行库存预警,使用消息队列或者延时队列技术进行异步化处理,以及进行库存负载均衡。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

9.线程池复用原理

10.线程池 7大参数

11.ConcurrentHashMap原理简述,jdk7和jdk8的区别

12.synchronized锁升级之后hashcode怎么办

1.无锁时:当对象的hashcode()方法第一次被调用时,JVM会生成对应的hashcode值并将该值存储到Mark Word中
2.偏向锁:在线程获取偏向锁时,会用Thread ID和覆盖其值,如果一个对象的hashCode()方法已经被调用过一次之后,这个对象不能被设置偏向锁,因为如果可以的话,那Mark Word中的hash code必然会被偏向线程id给覆盖,这就会造成同一个对象前后两次调用hashCode方法得到的结果不一样
3.升级为轻量级锁时:JVM会在当前线程线程的栈帧中创建一个锁记录空间,用于存储锁对象的Mark Word拷贝,该拷贝中可以包含hashCode,所以轻量级锁可以和hashCode共存,哈希码和GC年龄自然保存在此,释放锁后会将这些信息写回到对象头
4.升级为重量级锁后:Mark Word保存的重量级锁指针,代表重量级锁的ObjcetMonitor类里有字段记录非加锁状态下的Mark Word,锁释放之后也会将信息写回到对象头中。

  • 1
  • 2
  • 3
  • 4
  • 5

13.线程阻塞是否会造成CPU飙高

线程阻塞是否会造成CPU飙高,这取决于阻塞的原因。如果线程阻塞是因为等待I/O操作
(例如,等待从数据库读取数据),那么CPU使用率通常不会增加,因为线程在等待期间不会消耗CPU资源。
然而,如果线程阻塞是因为等待获取锁(例如,等待获取数据库行锁),那么可能会导致CPU使用率增加。
这是因为在等待获取锁的过程中,线程可能会不断地尝试获取锁,这会消耗CPU资源。

Sql导致性能开销突增,一般是查询条件没有触发索引,或者查询条件范围太大,查得数据太多
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其他

两个系统之间除了用 rpc 调用,还有什么其他交互方式吗

1.RESTful API
2.消息队列
3.WebSocket
4.文件传输
5.数据库复制
  • 1
  • 2
  • 3
  • 4
  • 5

Nginx

四层负载均衡和7层负载均衡的区别 (四层传输层 七层应用层 四层比七层快)

技术方案

分布式架构下,session共享有什么方案

分布式id生产方案

分布式锁解决方案

分布式事务解决方案

如何实现接口幂等性

防止重复提交怎么做

好文章转载

Java 垃圾回收最全讲解(GC过程、可达性分析、方法,7大回收器)

JVM学习之GC流程和GC策略

Java创建对象的过程

Java虚拟机(JVM)面试题(总结最全面的面试题!!!)

SpringBoot自动装配原理

【面试篇】ConcurrentHashMap1.7和1.8详解对比

Day822.Happens-Before 规则 -Java 并发编程实战

Java面试题及答案整理汇总(2023最新版)

pom.xml配置文件中所有标签及作用简单描述

Java技术之AQS详解

Java 并发高频面试题:聊聊你对 AQS 的理解?

synchronized原理剖析

AQS 详细介绍

浅析JVM的内存分区

JAVA 虚拟机

JAVA工具类

Java8 Optional

Commons IO 官方文档

docker的安装与配置

Docker的安装 与 环境配置 及 阿里云镜像仓库配置、常用命令等

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/759954
推荐阅读
相关标签
  

闽ICP备14008679号