<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>java &#8211; Lhy&#039;s blog</title>
	<atom:link href="https://blog.lhyshome.com/tag/java/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.lhyshome.com</link>
	<description>welcome</description>
	<lastBuildDate>Tue, 24 Mar 2026 08:47:49 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
<site xmlns="com-wordpress:feed-additions:1">219834889</site>	<item>
		<title>【Java面试】常用中间件</title>
		<link>https://blog.lhyshome.com/2026/03/17/511/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/511/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 10:50:13 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=511</guid>

					<description><![CDATA[Zookeeper zookeeper 是什么？ zookeeper 是一个分布式的，开放源码的分布式应用程序… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/511/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading"><strong>Zookeeper</strong></h1>



<h2 class="wp-block-heading" id="34j2n">zookeeper 是什么？</h2>



<p>zookeeper 是一个分布式的，开放源码的分布式应用程序协调服务，是 google chubby 的开源实现，是 hadoop 和 hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件，提供的功能包括：配置维护、域名服务、分布式同步、组服务等。</p>



<h2 class="wp-block-heading" id="2gvtm">zookeeper 都有哪些功能？</h2>



<ul class="wp-block-list">
<li>集群管理：监控节点存活状态、运行请求等。</li>



<li>主节点选举：主节点挂掉了之后可以从备用的节点开始新一轮选主，主节点选举说的就是这个选举的过程，使用 zookeeper 可以协助完成这个过程。</li>



<li>分布式锁：zookeeper 提供两种锁：独占锁、共享锁。独占锁即一次只能有一个线程使用资源，共享锁是读锁共享，读写互斥，即可以有多线线程同时读同一个资源，如果要使用写锁也只能有一个线程使用。zookeeper可以对分布式锁进行控制。</li>



<li>命名服务：在分布式系统中，通过使用命名服务，客户端应用能够根据指定名字来获取资源或服务的地址，提供者等信息。</li>
</ul>



<h2 class="wp-block-heading" id="6drt1">zookeeper 有几种部署模式？</h2>



<p>zookeeper 有三种部署模式：</p>



<ul class="wp-block-list">
<li>单机部署：一台集群上运行；</li>



<li>集群部署：多台集群运行；</li>



<li>伪集群部署：一台集群启动多个 zookeeper 实例运行。</li>
</ul>



<h2 class="wp-block-heading" id="euilc">zookeeper 怎么保证主从节点的状态同步？</h2>



<p>zookeeper 的核心是原子广播，这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。zab 协议有两种模式，分别是恢复模式（选主）和广播模式（同步）。当服务启动或者在领导者崩溃后，zab 就进入了恢复模式，当领导者被选举出来，且大多数 server 完成了和 leader 的状态同步以后，恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。</p>



<h2 class="wp-block-heading" id="61r4p">集群中为什么要有主节点？</h2>



<p>在分布式环境中，有些业务逻辑只需要集群中的某一台机器进行执行，其他的机器可以共享这个结果，这样可以大大减少重复计算，提高性能，所以就需要主节点。</p>



<h2 class="wp-block-heading" id="124ln">集群中有 3 台服务器，其中一个节点宕机，这个时候 zookeeper 还可以使用吗？</h2>



<p>可以继续使用，单数服务器只要没超过一半的服务器宕机就可以继续使用。</p>



<h2 class="wp-block-heading" id="6tj6a">说一下 zookeeper 的通知机制？</h2>



<p>客户端端会对某个 znode 建立一个 watcher 事件，当该 znode 发生变化时，这些客户端会收到 zookeeper 的通知，然后客户端可以根据 znode 变化来做出业务上的改变。</p>



<h1 class="wp-block-heading"><strong>Elasticsearch</strong></h1>



<h2 class="wp-block-heading">解释一下Elasticsearch中的<code>text</code>和<code>keyword</code>字段类型的区别。</h2>



<ul class="wp-block-list">
<li><strong><code>text</code></strong>：该类型会进行<strong>分词</strong>处理，将长文本拆分为多个词项，然后建立倒排索引。它主要用于<strong>全文检索</strong>场景，例如搜索文章内容、商品描述等<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong><code>keyword</code></strong>：该类型<strong>不会进行分词</strong>，会将整个字段内容作为一个完整的词项存入倒排索引。它主要用于<strong>精确匹配、聚合（Aggregation）和排序</strong>，例如订单状态、用户ID、邮箱地址等<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。实际应用中，常用<code>multi-fields</code>特性为一个字段同时定义<code>text</code>和<code>keyword</code>类型，以应对不同场景<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">Elasticsearch是如何实现Master选举的？如何处理脑裂问题？</h2>



<ul class="wp-block-list">
<li><strong>选举流程</strong>：ES的ZenDiscovery模块负责选主。当一个节点发现集群中不存在活跃主节点时，它会发起选举。所有具备主节点资格（<code>node.master: true</code>）的节点会对所有已知的有资格的节点进行排序（通常按节点ID），并倾向于选举自己知道的第一个节点。如果一个节点获得了超过<strong>半数</strong>（<code>discovery.zen.minimum_master_nodes</code>配置的值）的选票，且该节点也选举了自己，那么它就成为新的主节点<a href="https://cloud.tencent.com.cn/developer/article/2405245?from=15425" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>脑裂预防</strong>：脑裂是指集群中出现多个主节点的现象。核心解决方法是<strong>正确配置 <code>discovery.zen.minimum_master_nodes</code></strong>，其值应设置为<strong>有资格成为主节点的节点数 <code>n/2 + 1</code></strong>（即超过半数）<a href="https://cloud.tencent.com.cn/developer/article/2405245?from=15425" target="_blank" rel="noreferrer noopener"></a>。这样，任何时刻最多只有一个分区拥有足够的节点数来选举出主节点，从而避免脑裂。</li>
</ul>



<h2 class="wp-block-heading">Elasticsearch有哪些不同的查询类型？请举例说明。</h2>



<ul class="wp-block-list">
<li><strong>精确查询</strong>：如<code>term</code>、<code>terms</code>、<code>range</code>查询，不对查询词进行分析，用于精确匹配条件<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>全文检索</strong>：如<code>match</code>、<code>match_phrase</code>、<code>multi_match</code>查询，会对查询文本进行分析（分词），然后去倒排索引中查找<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>复合查询</strong>：如<code>bool</code>查询，通过<code>must</code>、<code>should</code>、<code>filter</code>、<code>must_not</code>等子句组合多个查询条件。其中<code>filter</code>子句不计算相关性得分，且结果可被缓存，性能更好<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>聚合分析</strong>：如<code>terms</code>（分组统计）、<code>avg</code>（平均值）、<code>date_histogram</code>（时间直方图）等，用于数据分析<a href="https://cloud.tencent.com.cn/developer/article/2564742" target="_blank" rel="noreferrer noopener"></a><a href="https://blog.csdn.net/qq_41377858/article/details/150592750" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/511/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">511</post-id>	</item>
		<item>
		<title>【Java面试】消息队列</title>
		<link>https://blog.lhyshome.com/2026/03/17/508/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/508/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 10:46:53 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=508</guid>

					<description><![CDATA[RabbitMQ RabbitMQ 的使用场景有哪些？ RabbitMQ 有哪些重要的角色？ RabbitMQ… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/508/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="d60ul"><strong>RabbitMQ</strong></h1>



<h2 class="wp-block-heading" id="cdseg"><strong>RabbitMQ 的使用场景有哪些？</strong></h2>



<ul class="wp-block-list">
<li>抢购活动，削峰填谷，防止系统崩塌。</li>



<li>延迟信息处理，比如 10 分钟之后给下单未付款的用户发送邮件提醒。</li>



<li>解耦系统，对于新增的功能可以单独写模块扩展，比如用户确认评价之后，新增了给用户返积分的功能，这个时候不用在业务代码里添加新增积分的功能，只需要把新增积分的接口订阅确认评价的消息队列即可，后面再添加任何功能只需要订阅对应的消息队列即可。</li>
</ul>



<h2 class="wp-block-heading" id="c9b8o"><strong>RabbitMQ 有哪些重要的角色？</strong></h2>



<p>RabbitMQ 中重要的角色有：生产者、消费者和代理：</p>



<ul class="wp-block-list">
<li>生产者：消息的创建者，负责创建和推送数据到消息服务器；</li>



<li>消费者：消息的接收方，用于处理数据和确认消息；</li>



<li>代理：就是 RabbitMQ 本身，用于扮演“快递”的角色，本身不生产消息，只是扮演“快递”的角色。</li>
</ul>



<h2 class="wp-block-heading" id="51v7u"><strong>RabbitMQ 有哪些重要的组件？</strong></h2>



<ul class="wp-block-list">
<li>ConnectionFactory（连接管理器）：应用程序与Rabbit之间建立连接的管理器，程序代码中使用。</li>



<li>Channel（信道）：消息推送使用的通道。</li>



<li>Exchange（交换器）：用于接受、分配消息。</li>



<li>Queue（队列）：用于存储生产者的消息。</li>



<li>RoutingKey（路由键）：用于把生成者的数据分配到交换器上。</li>



<li>BindingKey（绑定键）：用于把交换器的消息绑定到队列上。</li>
</ul>



<h2 class="wp-block-heading" id="7l0st"><strong>RabbitMQ 中 vhost 的作用是什么？</strong></h2>



<p>vhost：每个 RabbitMQ 都能创建很多 vhost，我们称之为虚拟主机，每个虚拟主机其实都是 mini 版的RabbitMQ，它拥有自己的队列，交换器和绑定，拥有自己的权限机制。</p>



<h2 class="wp-block-heading" id="at35u"><strong>RabbitMQ 的消息是怎么发送的？</strong></h2>



<p>首先客户端必须连接到 RabbitMQ 服务器才能发布和消费消息，客户端和 rabbit server 之间会创建一个 tcp 连接，一旦 tcp 打开并通过了认证（认证就是你发送给 rabbit 服务器的用户名和密码），你的客户端和 RabbitMQ 就创建了一条 amqp 信道（channel），信道是创建在“真实” tcp 上的虚拟连接，amqp 命令都是通过信道发送出去的，每个信道都会有一个唯一的 id，不论是发布消息，订阅队列都是通过这个信道完成的。</p>



<h2 class="wp-block-heading" id="6qvd3"><strong>RabbitMQ 怎么保证消息的稳定性？</strong></h2>



<ul class="wp-block-list">
<li>提供了事务的功能。</li>



<li>通过将 channel 设置为 confirm（确认）模式。</li>
</ul>



<h2 class="wp-block-heading" id="307js"><strong>RabbitMQ 怎么避免消息丢失？</strong></h2>



<ul class="wp-block-list">
<li>把消息持久化磁盘，保证服务器重启消息不丢失。</li>



<li>每个集群中至少有一个物理磁盘，保证消息落入磁盘。</li>
</ul>



<h2 class="wp-block-heading" id="r201"><strong>要保证消息持久化成功的条件有哪些？</strong></h2>



<ul class="wp-block-list">
<li>声明队列必须设置持久化 durable 设置为 true.</li>



<li>消息推送投递模式必须设置持久化，deliveryMode 设置为 2（持久）。</li>



<li>消息已经到达持久化交换器。</li>



<li>消息已经到达持久化队列。</li>
</ul>



<p>以上四个条件都满足才能保证消息持久化成功。</p>



<h2 class="wp-block-heading" id="cjf1a"><strong>RabbitMQ 持久化有什么缺点？</strong></h2>



<p>持久化的缺地就是降低了服务器的吞吐量，因为使用的是磁盘而非内存存储，从而降低了吞吐量。可尽量使用 ssd 硬盘来缓解吞吐量的问题。</p>



<h2 class="wp-block-heading" id="5fvcd"><strong>RabbitMQ 有几种广播类型？</strong></h2>



<ul class="wp-block-list">
<li>direct（默认方式）：最基础最简单的模式，发送方把消息发送给订阅方，如果有多个订阅者，默认采取轮询的方式进行消息发送。</li>



<li>headers：与 direct 类似，只是性能很差，此类型几乎用不到。</li>



<li>fanout：分发模式，把消费分发给所有订阅者。</li>



<li>topic：匹配订阅模式，使用正则匹配到消息队列，能匹配到的都能接收到。</li>
</ul>



<h2 class="wp-block-heading" id="fumpg"><strong>RabbitMQ 怎么实现延迟消息队列？</strong></h2>



<p>延迟队列的实现有两种方式：</p>



<ul class="wp-block-list">
<li>通过消息过期后进入死信交换器，再由交换器转发到延迟消费队列，实现延迟功能；</li>



<li>使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。</li>
</ul>



<h2 class="wp-block-heading" id="aaq26"><strong>RabbitMQ 集群有什么用？</strong></h2>



<p>集群主要有以下两个用途：</p>



<ul class="wp-block-list">
<li>高可用：某个服务器出现问题，整个 RabbitMQ 还可以继续使用；</li>



<li>高容量：集群可以承载更多的消息量。</li>
</ul>



<h2 class="wp-block-heading" id="ffcpn"><strong>RabbitMQ 节点的类型有哪些？</strong></h2>



<ul class="wp-block-list">
<li>磁盘节点：消息会存储到磁盘。</li>



<li>内存节点：消息都存储在内存中，重启服务器消息丢失，性能高于磁盘类型。</li>
</ul>



<h2 class="wp-block-heading" id="d8b52"><strong>RabbitMQ 集群搭建需要注意哪些问题？</strong></h2>



<ul class="wp-block-list">
<li>各节点之间使用“–link”连接，此属性不能忽略。</li>



<li>各节点使用的 erlang cookie 值必须相同，此值相当于“秘钥”的功能，用于各节点的认证。</li>



<li>整个集群中必须包含一个磁盘节点。</li>
</ul>



<h2 class="wp-block-heading" id="831it"><strong>RabbitMQ 每个节点是其他节点的完整拷贝吗？为什么？</strong></h2>



<p>不是，原因有以下两个：</p>



<ul class="wp-block-list">
<li>存储空间的考虑：如果每个节点都拥有所有队列的完全拷贝，这样新增节点不但没有新增存储空间，反而增加了更多的冗余数据；</li>



<li>性能的考虑：如果每条消息都需要完整拷贝到每一个集群节点，那新增节点并没有提升处理消息的能力，最多是保持和单节点相同的性能甚至是更糟。</li>
</ul>



<h2 class="wp-block-heading" id="86l6b"><strong>RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况？</strong></h2>



<p>如果唯一磁盘的磁盘节点崩溃了，不能进行以下操作：</p>



<ul class="wp-block-list">
<li>不能创建队列</li>



<li>不能创建交换器</li>



<li>不能创建绑定</li>



<li>不能添加用户</li>



<li>不能更改权限</li>



<li>不能添加和删除集群节点</li>
</ul>



<p>唯一磁盘节点崩溃了，集群是可以保持运行的，但你不能更改任何东西。</p>



<h2 class="wp-block-heading" id="2e0nq">RabbitMQ 对集群节点停止顺序有要求吗？</h2>



<p>RabbitMQ 对集群的停止的顺序是有要求的，应该先关闭内存节点，最后再关闭磁盘节点。如果顺序恰好相反的话，可能会造成消息的丢失。</p>



<h2 class="wp-block-heading">如何保证消息不丢失（可靠性）？</h2>



<p>保证消息的可靠传输需要从三个环节入手<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<ul class="wp-block-list">
<li><strong>生产者 -> RabbitMQ</strong>：开启<strong>发布确认（Publisher Confirm）机制</strong>。生产者发送消息后，RabbitMQ 会异步返回一个 <code>ack</code>（成功）或 <code>nack</code>（失败），生产者可根据结果重试<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1324637" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>RabbitMQ 自身</strong>：开启<strong>持久化</strong>。
<ol start="1" class="wp-block-list">
<li><strong>队列持久化</strong>：创建队列时设置 <code>durable = true</code><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>消息持久化</strong>：发送消息时设置 <code>deliveryMode = 2</code><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</li>
</ol>
</li>



<li><strong>RabbitMQ -> 消费者</strong>：关闭自动 <code>ack</code>，改为<strong>手动确认（Manual Ack）</strong>。消费者处理完业务逻辑后再手动确认，防止消费者宕机导致消息丢失<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">如何解决消息的重复消费问题（保证幂等性）？</h2>



<p>消息重复通常是因为网络波动，消费者已消费但确认 (<code>ack</code>) 未送达 Broker，导致消息被重新投递<a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</p>



<ul class="wp-block-list">
<li><strong>核心思路</strong>：保证<strong>幂等性</strong>，即同一条消息执行多次与执行一次的效果相同<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>解决方案</strong>：消息体中带一个全局唯一的业务 ID（如订单号、支付 ID）<a href="https://www.bjpowernode.com/middlewaremst/rabbitmq.html" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.cn/developer/article/2373992?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>。
<ul class="wp-block-list">
<li><strong>方案一（数据库）</strong>：利用数据库主键唯一约束去重。消费前先根据业务 ID 查询，若记录已存在则直接返回。</li>



<li><strong>方案二（Redis）</strong>：用业务 ID 作为 Redis 的 key，通过 <code>setNx</code> 操作来判断是否处理过。</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">什么是死信队列（DLX）？它有什么用？</h2>



<p>死信队列是一种消息补偿机制。当消息成为<strong>死信</strong>时，会被重新发送到指定的交换机（称为死信交换机 DLX），进而进入绑定的死信队列<a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a><a href="https://www.finclip.com/news/f/4164.html" target="_blank" rel="noreferrer noopener"></a>。</p>



<ul class="wp-block-list">
<li><strong>消息成为死信的条件</strong><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：
<ol start="1" class="wp-block-list">
<li>消息被消费者拒绝且未重新入队（<code>basic.reject</code> 或 <code>basic.nack</code> 且 <code>requeue = false</code>）。</li>



<li>消息<strong>TTL（存活时间）</strong> 过期。</li>



<li>队列达到最大长度，无法再添加消息。</li>
</ol>
</li>



<li><strong>作用</strong>：可以监听死信队列，对失败的消息进行<strong>延迟处理、异常监控或人工干预</strong>。</li>
</ul>



<h2 class="wp-block-heading">如何实现延迟队列？</h2>



<p>RabbitMQ 本身没有直接提供延迟队列，通常通过<strong>死信队列 + TTL</strong> 机制模拟<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/article/1961125?from=15425&amp;frompage=seopage" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li>创建一个队列，不设置消费者，并为其消息设置 <strong>TTL</strong>。</li>



<li>为该队列指定<strong>死信交换机（DLX）</strong>。</li>



<li>消息超时无人消费后，自动转发到死信交换机，再路由到绑定的死信队列。</li>



<li>消费者监听死信队列即可获取到“延迟”的消息。</li>
</ol>



<h2 class="wp-block-heading">如何处理消息堆积？</h2>



<ul class="wp-block-list">
<li><strong>临时扩容</strong>：<strong>增加消费者</strong>数量（前提是队列能支撑并行消费），或为消费者开启<strong>多线程</strong>处理。</li>



<li><strong>队列优化</strong>：对于大量消息积压，可以使用 RabbitMQ 的<strong>惰性队列</strong>，它会将消息直接存入磁盘，减少内存压力，但吞吐量会下降<a href="https://bbs.huaweicloud.com/blogs/450400" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>紧急修复</strong>：如果消费者程序有 Bug，先暂停消费者，修复 Bug 后，再启动消费能力更强的程序来追赶进度。</li>
</ul>



<h1 class="wp-block-heading" id="83idb"><strong>Kafka</strong></h1>



<h2 class="wp-block-heading" id="blsj2">kafka 可以脱离 zookeeper 单独使用吗？为什么？</h2>



<p>kafka 不能脱离 zookeeper 单独使用，因为 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器。</p>



<h2 class="wp-block-heading" id="6hmbm">kafka 有几种数据保留的策略？</h2>



<p>kafka 有两种数据保存策略：按照过期时间保留和按照存储的消息大小保留。</p>



<h2 class="wp-block-heading" id="cade3">kafka 同时设置了 7 天和 10G 清除数据，到第五天的时候消息达到了 10G，这个时候 kafka 将如何处理？</h2>



<p>这个时候 kafka 会执行数据清除工作，时间和大小不论那个满足条件，都会清空数据。</p>



<h2 class="wp-block-heading" id="37avg">什么情况会导致 kafka 运行变慢？</h2>



<ul class="wp-block-list">
<li>cpu 性能瓶颈</li>



<li>磁盘读写瓶颈</li>



<li>网络瓶颈</li>
</ul>



<h2 class="wp-block-heading" id="7r3oa">使用 kafka 集群需要注意什么？</h2>



<ul class="wp-block-list">
<li>集群的数量不是越多越好，最好不要超过 7 个，因为节点越多，消息复制需要的时间就越长，整个群组的吞吐量就越低。</li>



<li>集群数量最好是单数，因为超过一半故障集群就不能用了，设置为单数容错率更高。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/508/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">508</post-id>	</item>
		<item>
		<title>【Java面试】数据库</title>
		<link>https://blog.lhyshome.com/2026/03/17/498/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/498/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 09:21:30 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=498</guid>

					<description><![CDATA[数据库的三范式是什么？ 一张自增表里面总共有 7 条数据，删除了最后 2 条数据，重启 MySQL 数据库，又… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/498/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="aet2b">数据库的三范式是什么？</h2>



<ul class="wp-block-list">
<li>第一范式：强调的是列的原子性，即数据库表的每一列都是不可分割的原子数据项。</li>



<li>第二范式：要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。</li>



<li>第三范式：任何非主属性不依赖于其它非主属性。</li>
</ul>



<h2 class="wp-block-heading" id="52jf2">一张自增表里面总共有 7 条数据，删除了最后 2 条数据，重启 MySQL 数据库，又插入了一条数据，此时 id 是几？</h2>



<ul class="wp-block-list">
<li>表类型如果是 MyISAM ，那 id 就是 8。</li>



<li>表类型如果是 InnoDB，那 id 就是 6。</li>
</ul>



<p>InnoDB 表只会把自增主键的最大 id 记录在内存中，所以重启之后会导致最大 id 丢失。</p>



<h2 class="wp-block-heading" id="qouf">如何获取当前数据库版本？</h2>



<p>使用 select version() 获取当前 MySQL 数据库版本。</p>



<h2 class="wp-block-heading" id="332r5">说一下 ACID 是什么？</h2>



<ul class="wp-block-list">
<li>Atomicity（原子性）：一个事务（transaction）中的所有操作，或者全部完成，或者全部不完成，不会结束在中间某个环节。事务在执行过程中发生错误，会被恢复（Rollback）到事务开始前的状态，就像这个事务从来没有执行过一样。即，事务不可分割、不可约简。</li>



<li>Consistency（一致性）：在事务开始之前和事务结束以后，数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。</li>



<li>Isolation（隔离性）：数据库允许多个并发事务同时对其数据进行读写和修改的能力，隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别，包括读未提交（Read uncommitted）、读提交（read committed）、可重复读（repeatable read）和串行化（Serializable）。</li>



<li>Durability（持久性）：事务处理结束后，对数据的修改就是永久的，即便系统故障也不会丢失。</li>
</ul>



<h2 class="wp-block-heading" id="3v4hf">char 和 varchar 的区别是什么？</h2>



<ul class="wp-block-list">
<li><strong>「char(n)」</strong>&nbsp;：固定长度类型，比如订阅 char(10)，当你输入”abc”三个字符的时候，它们占的空间还是 10 个字节，其他 7 个是空字节。
<ul class="wp-block-list">
<li>chat 优点：效率高；缺点：占用空间；适用场景：存储密码的 md5 值，固定长度的，使用 char 非常合适。</li>
</ul>
</li>



<li><strong>「varchar(n)」</strong>&nbsp;：可变长度，存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。</li>
</ul>



<p>所以，从空间上考虑 varcahr 比较合适；从效率上考虑 char 比较合适，二者使用需要权衡。</p>



<h2 class="wp-block-heading" id="3tk6r">float 和 double 的区别是什么？</h2>



<ul class="wp-block-list">
<li>float 最多可以存储 8 位的十进制数，并在内存中占 4 字节。</li>



<li>double 最可可以存储 16 位的十进制数，并在内存中占 8 字节。</li>
</ul>



<h2 class="wp-block-heading" id="5sl88">MySQL 的内连接、左连接、右连接有什么区别？</h2>



<p>内连接关键字：inner join；左连接：left join；右连接：right join。</p>



<p>内连接是把匹配的关联数据显示出来；左连接是左边的表全部显示出来，右边的表显示出符合条件的数据；右连接正好相反。</p>



<h2 class="wp-block-heading" id="eb0kj">MySQL 索引是怎么实现的？</h2>



<p>索引是满足某种特定查找算法的数据结构，而这些数据结构会以某种方式指向数据，从而实现高效查找数据。</p>



<p>具体来说 MySQL 中的索引，不同的数据引擎实现有所不同，但目前主流的数据库引擎的索引都是 B+ 树实现的，B+ 树的搜索效率，可以到达二分法的性能，找到数据区域之后就找到了完整的数据结构了，所有索引的性能也是更好的。</p>



<h2 class="wp-block-heading" id="4s7et">怎么验证 MySQL 的索引是否满足需求？</h2>



<p>使用 explain 查看 SQL 是如何执行查询语句的，从而分析你的索引是否满足需求。</p>



<p>explain 语法：explain select * from table where type=1。</p>



<h2 class="wp-block-heading" id="6kk4d">说一下数据库的事务隔离？</h2>



<p>MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的，在文件的最后添加：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>❝transaction-isolation = REPEATABLE-READ ❞</p>
</blockquote>



<p>可用的配置值：READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。</p>



<ul class="wp-block-list">
<li>READ-UNCOMMITTED：未提交读，最低隔离级别、事务未提交前，就可被其他事务读取（会出现幻读、脏读、不可重复读）。</li>



<li>READ-COMMITTED：提交读，一个事务提交后才能被其他事务读取到（会造成幻读、不可重复读）。</li>



<li>REPEATABLE-READ：可重复读，默认级别，保证多次读取同一个数据时，其值都和事务开始时候的内容是一致，禁止读取到别的事务未提交的数据（会造成幻读）。</li>



<li>SERIALIZABLE：序列化，代价最高最可靠的隔离级别，该隔离级别能防止脏读、不可重复读、幻读。</li>
</ul>



<ul class="wp-block-list">
<li><strong>「脏读」</strong>&nbsp;：表示一个事务能够读取另一个事务中还未提交的数据。比如，某个事务尝试插入记录 A，此时该事务还未提交，然后另一个事务尝试读取到了记录 A。</li>



<li><strong>「不可重复读」</strong>&nbsp;：是指在一个事务内，多次读同一数据。</li>



<li><strong>「幻读」</strong>&nbsp;：指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录，但是第二次同等条件下查询却有 n+1 条记录，这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据，同一个记录的数据内容被修改了，所有数据行的记录就变多或者变少了。</li>
</ul>



<h2 class="wp-block-heading" id="b219">说一下 MySQL 常用的引擎？</h2>



<ul class="wp-block-list">
<li>InnoDB 引擎：mysql 5.1 后默认的数据库引擎，提供了对数据库 acid 事务的支持，并且还提供了行级锁和外键的约束，它的设计的目标就是处理大数据容量的数据库系统。MySQL 运行的时候，InnoDB 会在内存中建立缓冲池，用于缓冲数据和索引。但是该引擎是不支持全文搜索，同时启动也比较的慢，它是不会保存表的行数的，所以当进行 select count(*) from table 指令的时候，需要进行扫描全表。由于锁的粒度小，写操作是不会锁定全表的,所以在并发度较高的场景下使用会提升效率的。</li>



<li>MyIASM 引擎：不提供事务的支持，也不支持行级锁和外键。因此当执行插入和更新语句时，即执行写操作的时候需要锁定这个表，所以会导致效率会降低。不过和 InnoDB 不同的是，MyIASM 引擎是保存了表的行数，于是当进行 select count(*) from table 语句时，可以直接的读取已经保存的值而不需要进行扫描全表。所以，如果表的读操作远远多于写操作时，并且不需要事务的支持的，可以将 MyIASM 作为数据库引擎的首选。</li>
</ul>



<h2 class="wp-block-heading" id="16ugm">说一下 MySQL 的行锁和表锁？</h2>



<p>MyISAM 只支持表锁，InnoDB 支持表锁和行锁，默认为行锁。</p>



<ul class="wp-block-list">
<li>表级锁：开销小，加锁快，不会出现死锁。锁定粒度大，发生锁冲突的概率最高，并发量最低。</li>



<li>行级锁：开销大，加锁慢，会出现死锁。锁力度小，发生锁冲突的概率小，并发度最高。</li>
</ul>



<h2 class="wp-block-heading" id="fdfu5">说一下乐观锁和悲观锁？</h2>



<ul class="wp-block-list">
<li>乐观锁：每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。</li>



<li>悲观锁：每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻止，直到这个锁被释放。</li>
</ul>



<p>数据库的乐观锁需要自己实现，在表里面添加一个 version 字段，每次修改成功值加 1，这样每次修改的时候先对比一下，自己拥有的 version 和数据库现在的 version 是否一致，如果不一致就不修改，这样就实现了乐观锁。</p>



<h2 class="wp-block-heading" id="cevsm">MySQL 问题排查都有哪些手段？</h2>



<ul class="wp-block-list">
<li>使用 show processlist 命令查看当前所有连接信息。</li>



<li>使用 explain 命令查询 SQL 语句执行计划。</li>



<li>开启慢查询日志，查看慢查询的 SQL。</li>
</ul>



<h2 class="wp-block-heading" id="86lh0">如何做 MySQL 的性能优化？</h2>



<ul class="wp-block-list">
<li>为搜索字段创建索引。</li>



<li>避免使用 select *，列出需要查询的字段。</li>



<li>垂直分割分表。</li>



<li>选择正确的存储引擎。</li>
</ul>



<h2 class="wp-block-heading">B+树和B树的区别</h2>



<ul class="wp-block-list">
<li><strong>B树</strong>：所有节点都存数据，查找速度快（可能提前结束），但范围查询麻烦。</li>



<li><strong>B+树</strong>：只有叶子节点存数据，内部节点只存键，且叶子节点有链表连接。牺牲了“可能提前找到”的微小概率，换来了<strong>更稳定的查找性能</strong>和<strong>极致的范围查询能力</strong>，因此成为现代数据库索引的默认选择。</li>
</ul>



<h2 class="wp-block-heading">MySQL默认的事务隔离级别是？</h2>



<p>MySQL默认的事务隔离级别是&nbsp;<strong>可重复读（REPEATABLE READ）</strong>。</p>



<h2 class="wp-block-heading">什么是事务？</h2>



<p>事务是一组不可分割的数据库操作单元，这些操作要么全部成功执行，要么全部失败回滚，确保数据库从一种一致状态转换到另一种一致状态。</p>



<h2 class="wp-block-heading">事务有哪几个特性？</h2>



<p>事务具有ACID特性：</p>



<ul class="wp-block-list">
<li><strong>原子性（Atomicity）</strong>：事务中的操作要么全部完成，要么全部不执行。</li>



<li><strong>一致性（Consistency）</strong>：事务执行前后，数据库完整性约束没有被破坏。</li>



<li><strong>隔离性（Isolation）</strong>：多个事务并发执行时，相互隔离，互不干扰。</li>



<li><strong>持久性（Durability）</strong>：事务一旦提交，对数据库的改变是永久性的。</li>
</ul>



<h1 class="wp-block-heading" id="eth9v"><strong>Hibernate</strong></h1>



<h2 class="wp-block-heading" id="2tffl"><strong>为什么要使用 hibernate？</strong></h2>



<ul class="wp-block-list">
<li>hibernate 是对 jdbc 的封装，大大简化了数据访问层的繁琐的重复性代码。</li>



<li>hibernate 是一个优秀的 ORM 实现，很多程度上简化了 DAO 层的编码功能。</li>



<li>可以很方便的进行数据库的移植工作。</li>



<li>提供了缓存机制，是程序执行更改的高效。</li>
</ul>



<h2 class="wp-block-heading" id="533i1"><strong>什么是 ORM 框架？</strong></h2>



<p>ORM（Object Relation Mapping）对象关系映射，是把数据库中的关系数据映射成为程序中的对象。</p>



<p>使用 ORM 的优点：提高了开发效率降低了开发成本、开发更简单更对象化、可移植更强。</p>



<h2 class="wp-block-heading" id="al1sh"><strong>hibernate 中如何在控制台查看打印的 SQL 语句？</strong></h2>



<p>在 Config 里面把 hibernate. show_SQL 设置为 true 就可以。但不建议开启，开启之后会降低程序的运行效率。</p>



<h2 class="wp-block-heading" id="apqnc"><strong>hibernate 有几种查询方式？</strong></h2>



<p>三种：hql、原生 SQL、条件查询 Criteria。</p>



<h2 class="wp-block-heading" id="5n9bc"><strong>hibernate 实体类可以被定义为 final 吗？</strong></h2>



<p>实体类可以定义为 final 类，但这样的话就不能使用 hibernate 代理模式下的延迟关联提供性能了，所以不建议定义实体类为 final。</p>



<h2 class="wp-block-heading" id="ep062"><strong>在 hibernate 中使用 Integer 和 int 做映射有什么区别？</strong></h2>



<p>Integer 类型为对象，它的值允许为 null，而 int 属于基础数据类型，值不能为 null。</p>



<h2 class="wp-block-heading" id="43gku"><strong>hibernate 是如何工作的？</strong></h2>



<ul class="wp-block-list">
<li>读取并解析配置文件。</li>



<li>读取并解析映射文件，创建 SessionFactory。</li>



<li>打开 Session。</li>



<li>创建事务。</li>



<li>进行持久化操作。</li>



<li>提交事务。</li>



<li>关闭 Session。</li>



<li>关闭 SessionFactory。</li>
</ul>



<h2 class="wp-block-heading" id="d808u"><strong>get()和 load()的区别？</strong></h2>



<ul class="wp-block-list">
<li>数据查询时，没有 OID 指定的对象，get() 返回 null；load() 返回一个代理对象。</li>



<li>load()支持延迟加载；get() 不支持延迟加载。</li>
</ul>



<h2 class="wp-block-heading" id="rlb5"><strong>说一下 hibernate 的缓存机制？</strong></h2>



<p>hibernate 常用的缓存有一级缓存和二级缓存：</p>



<p>一级缓存：也叫 Session 缓存，只在 Session 作用范围内有效，不需要用户干涉，由 hibernate 自身维护，可以通过：evict(object)清除 object 的缓存；clear()清除一级缓存中的所有缓存；flush()刷出缓存；</p>



<p>二级缓存：应用级别的缓存，在所有 Session 中都有效，支持配置第三方的缓存，如：EhCache。</p>



<h2 class="wp-block-heading" id="cb9b1"><strong>hibernate 对象有哪些状态？</strong></h2>



<ul class="wp-block-list">
<li>临时/瞬时状态：直接 new 出来的对象，该对象还没被持久化（没保存在数据库中），不受 Session 管理。</li>



<li>持久化状态：当调用 Session 的 save/saveOrupdate/get/load/list 等方法的时候，对象就是持久化状态。</li>



<li>游离状态：Session 关闭之后对象就是游离状态。</li>
</ul>



<h2 class="wp-block-heading" id="6aqkk"><strong>在 hibernate 中 getCurrentSession 和 openSession 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>getCurrentSession 会绑定当前线程，而 openSession 则不会。</li>



<li>getCurrentSession 事务是 Spring 控制的，并且不需要手动关闭，而 openSession 需要我们自己手动开启和提交事务。</li>
</ul>



<h2 class="wp-block-heading" id="573le"><strong>hibernate 实体类必须要有无参构造函数吗？为什么？</strong></h2>



<p>hibernate 中每个实体类必须提供一个无参构造函数，因为 hibernate 框架要使用 reflection api，通过调用 ClassnewInstance() 来创建实体类的实例，如果没有无参的构造函数就会抛出异常。</p>



<h1 class="wp-block-heading" id="3o6sc"><strong>MyBatis</strong></h1>



<h2 class="wp-block-heading" id="2pdi2">MyBatis 中 #{}和 ${}的区别是什么？</h2>



<p><code>\#{}</code>是预编译处理，<code>${}</code>是字符替换。在使用&nbsp;<code>#{}</code>时，MyBatis 会将 SQL 中的&nbsp;<code>#{}</code>替换成“?”，配合 PreparedStatement 的 set 方法赋值，这样可以有效的防止 SQL 注入，保证程序的运行安全。</p>



<h2 class="wp-block-heading" id="3v5l1">MyBatis 有几种分页方式？</h2>



<p>分页方式：逻辑分页和物理分页。</p>



<p><strong>「逻辑分页：」</strong>&nbsp;使用 MyBatis 自带的 RowBounds 进行分页，它是一次性查询很多数据，然后在数据中再进行检索。</p>



<p><strong>「物理分页：」</strong>&nbsp;自己手写 SQL 分页或使用分页插件 PageHelper，去数据库查询指定条数的分页数据的形式。</p>



<h2 class="wp-block-heading" id="9bls7">RowBounds 是一次性查询全部结果吗？为什么？</h2>



<p>RowBounds 表面是在“所有”数据中检索数据，其实并非是一次性查询出所有数据，因为 MyBatis 是对 jdbc 的封装，在 jdbc 驱动中有一个 Fetch Size 的配置，它规定了每次最多从数据库查询多少条数据，假如你要查询更多数据，它会在你执行 next()的时候，去查询更多的数据。就好比你去自动取款机取 10000 元，但取款机每次最多能取 2500 元，所以你要取 4 次才能把钱取完。只是对于 jdbc 来说，当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。</p>



<h2 class="wp-block-heading" id="4ngd3">MyBatis 逻辑分页和物理分页的区别是什么？</h2>



<ul class="wp-block-list">
<li>逻辑分页是一次性查询很多数据，然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。</li>



<li>物理分页是从数据库查询指定条数的数据，弥补了一次性全部查出的所有数据的种种缺点，比如需要大量的内存，对数据库查询压力较大等问题。</li>
</ul>



<h2 class="wp-block-heading" id="ft9v2">MyBatis 是否支持延迟加载？延迟加载的原理是什么？</h2>



<p>MyBatis 支持延迟加载，设置 lazyLoadingEnabled=true 即可。</p>



<p>延迟加载的原理的是调用的时候触发加载，而不是在初始化的时候就加载信息。比如调用 a. getB(). getName()，这个时候发现 a. getB() 的值为 null，此时会单独触发事先保存好的关联 B 对象的 SQL，先查询出来 B，然后再调用 a. setB(b)，而这时候再调用 a. getB(). getName() 就有值了，这就是延迟加载的基本原理。</p>



<h2 class="wp-block-heading" id="2pcdv">说一下 MyBatis 的一级缓存和二级缓存？</h2>



<ul class="wp-block-list">
<li>一级缓存：基于 PerpetualCache 的 HashMap 本地缓存，它的声明周期是和 SQLSession 一致的，有多个 SQLSession 或者分布式的环境中数据库操作，可能会出现脏数据。当 Session flush 或 close 之后，该 Session 中的所有 Cache 就将清空，默认一级缓存是开启的。</li>



<li>二级缓存：也是基于 PerpetualCache 的 HashMap 本地缓存，不同在于其存储作用域为 Mapper 级别的，如果多个SQLSession之间需要共享缓存，则需要使用到二级缓存，并且二级缓存可自定义存储源，如 Ehcache。默认不打开二级缓存，要开启二级缓存，使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。</li>
</ul>



<p>开启二级缓存数据查询流程：二级缓存 -&gt; 一级缓存 -&gt; 数据库。</p>



<p>缓存更新机制：当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后，默认该作用域下所有 select 中的缓存将被 clear。</p>



<h2 class="wp-block-heading" id="eakhh">MyBatis 和 hibernate 的区别有哪些？</h2>



<ul class="wp-block-list">
<li>灵活性：MyBatis 更加灵活，自己可以写 SQL 语句，使用起来比较方便。</li>



<li>可移植性：MyBatis 有很多自己写的 SQL，因为每个数据库的 SQL 可以不相同，所以可移植性比较差。</li>



<li>学习和使用门槛：MyBatis 入门比较简单，使用门槛也更低。</li>



<li>二级缓存：hibernate 拥有更好的二级缓存，它的二级缓存可以自行更换为第三方的二级缓存。</li>
</ul>



<h2 class="wp-block-heading" id="6o86">MyBatis 有哪些执行器（Executor）？</h2>



<p>MyBatis 有三种基本的Executor执行器：</p>



<ul class="wp-block-list">
<li>SimpleExecutor：每执行一次 update 或 select 就开启一个 Statement 对象，用完立刻关闭 Statement 对象；</li>



<li>ReuseExecutor：执行 update 或 select，以 SQL 作为 key 查找 Statement 对象，存在就使用，不存在就创建，用完后不关闭 Statement 对象，而是放置于 Map 内供下一次使用。简言之，就是重复使用 Statement 对象；</li>



<li>BatchExecutor：执行 update（没有 select，jdbc 批处理不支持 select），将所有 SQL 都添加到批处理中（addBatch()），等待统一执行（executeBatch()），它缓存了多个 Statement 对象，每个 Statement 对象都是 addBatch()完毕后，等待逐一执行 executeBatch()批处理，与 jdbc 批处理相同。</li>
</ul>



<h2 class="wp-block-heading" id="2738q">MyBatis 分页插件的实现原理是什么？</h2>



<p>分页插件的基本原理是使用 MyBatis 提供的插件接口，实现自定义插件，在插件的拦截方法内拦截待执行的 SQL，然后重写 SQL，根据 dialect 方言，添加对应的物理分页语句和物理分页参数。</p>



<h2 class="wp-block-heading" id="5m133">MyBatis 如何编写一个自定义插件？</h2>



<p><strong>「自定义插件实现原理」</strong></p>



<p>MyBatis 自定义插件针对 MyBatis 四大对象（Executor、StatementHandler、ParameterHandler、ResultSetHandler）进行拦截：</p>



<ul class="wp-block-list">
<li>Executor：拦截内部执行器，它负责调用 StatementHandler 操作数据库，并把结果集通过 ResultSetHandler 进行自动映射，另外它还处理了二级缓存的操作；</li>



<li>StatementHandler：拦截 SQL 语法构建的处理，它是 MyBatis 直接和数据库执行 SQL 脚本的对象，另外它也实现了 MyBatis 的一级缓存；</li>



<li>ParameterHandler：拦截参数的处理；</li>



<li>ResultSetHandler：拦截结果集的处理。</li>
</ul>



<p><strong>「自定义插件实现关键」</strong></p>



<p>MyBatis 插件要实现 Interceptor 接口，接口包含的方法，如下：</p>



<p>代码语言：javascript</p>



<p>代码运行次数：0</p>



<p>运行</p>



<p>AI代码解释</p>



<pre class="wp-block-code"><code>public interface Interceptor{   
   Object intercept(Invocation invocation)throws Throwable;       
   Object plugin(Object target);    
   void setProperties(Properties properties);
}</code></pre>



<ul class="wp-block-list">
<li>setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性，即：接口实现对象的参数配置；</li>



<li>plugin 方法是插件用于封装目标对象的，通过该方法我们可以返回目标对象本身，也可以返回一个它的代理，可以决定是否要进行拦截进而决定要返回一个什么样的目标对象，官方提供了示例：return Plugin. wrap(target, this)；</li>



<li>intercept 方法就是要进行拦截的时候要执行的方法。</li>
</ul>



<p><strong>「自定义插件实现示例」</strong></p>



<p>官方插件实现：</p>



<pre class="wp-block-code"><code>@Intercepts({@Signature(type = Executor. class, method= "query",
        args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
public class TestInterceptorimplementsInterceptor{
   public Object intercept(Invocation invocation)throws Throwable {
     Object target = invocation. getTarget(); //被代理对象
     Method method = invocation. getMethod(); //代理方法
     Object&#91;] args = invocation. getArgs(); //方法参数
     // do something . . . . . .  方法拦截前执行代码块
     Object result = invocation. proceed();
     // do something . . . . . . . 方法拦截后执行代码块
     return result;
   }
   public Object plugin(Object target){
     return Plugin. wrap(target, this);
   }
}</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/498/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">498</post-id>	</item>
		<item>
		<title>【Java面试】Spring</title>
		<link>https://blog.lhyshome.com/2026/03/17/456/</link>
					<comments>https://blog.lhyshome.com/2026/03/17/456/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 03:22:55 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=456</guid>

					<description><![CDATA[Spring/Spring MVC 为什么要使用 spring？ 解释一下什么是AOP?意义是什么?实现机制是… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/17/456/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="aiqcf"><strong>Spring/Spring MVC</strong></h1>



<h2 class="wp-block-heading" id="4ovq1">为什么要使用 spring？</h2>



<ul class="wp-block-list">
<li>spring 提供 ioc 技术，容器会帮你管理依赖的对象，从而不需要自己创建和管理依赖对象了，更轻松的实现了程序的解耦。</li>



<li>spring 提供了事务支持，使得事务操作变的更加方便。</li>



<li>spring 提供了面向切片编程，这样可以更方便的处理某一类的问题。</li>



<li>更方便的框架集成，spring 可以很方便的集成其他框架，比如 MyBatis、hibernate 等。</li>
</ul>



<h2 class="wp-block-heading"><strong>解释一下什么是AOP?意义是什么?实现机制是什么</strong></h2>



<ol class="wp-block-list">
<li><strong>什么是AOP&#8211;面向切面编程</strong>
<ul class="wp-block-list">
<li>用于将<strong>与业务无关的</strong>，<strong>但却对多个对象产生影响</strong>的<strong>公共行为和逻辑</strong>，使用<strong>动态代理的方式</strong>封装为一个<strong>统一的可重用的模块</strong>，<strong>这个模块被命名为“切面”（Aspect）</strong>，具有<strong>无侵入性</strong>的特点,可以<strong>减少</strong><strong>重复代码</strong>.如用于鉴权、日志、事务、运行监控等</li>
</ul>
</li>



<li><strong>实现机制</strong>
<ul class="wp-block-list">
<li>Spring AOP<strong>基于动态代理实现</strong>.使用<strong>JDK动态代理</strong>或<strong>CGLIB</strong>.JDK动态代理支持接口的代理,不支持类的代理,当无法使用JDK动态代理时,Spring AOP才会使用CGLIB</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading"><strong>静态代理和动态代理的区别是什么</strong></h2>



<p>代理主要分为<strong>静态代理</strong>和<strong>动态代理</strong>。静态代理的代表为<strong>AspectJ</strong>；动态代理的代表为<strong>Spring AOP</strong></p>



<p><strong>生成代理对象的时机不同</strong></p>



<ol class="wp-block-list">
<li>静态代理：由程序员<strong>主动创建</strong>或由<strong>特定工具自动生成</strong>源代码，再对其编译,在程序<strong>运行前代理类的.class文件就已经存在</strong>了。</li>



<li>动态代理：<strong>在程序运行时用反射机制在内存中临时生成代理对象,</strong>该代理对象<strong>包含了目标对象的全部方法</strong>，并且<strong>在特定的切点做了增强处理</strong>，<strong>并回调原对象的方法</strong></li>
</ol>



<p><strong>AspectJ的静态代理</strong>具有<strong>更好的性能</strong>，但<strong>AspectJ需要特定的编译器</strong>进行处理，而<strong>Spring AOP则无需特定的编译器</strong>处理</p>



<h2 class="wp-block-heading" id="5dpo0">解释一下什么是 ioc？</h2>



<p>ioc：Inversionof Control（中文：控制反转）是 spring 的核心，对于 spring 框架来说，就是由 spring 来负责控制对象的生命周期和对象间的关系。</p>



<p>简单来说，控制指的是当前对象对内部成员的控制权；控制反转指的是，这种控制权不由当前对象管理了，由其他（类,第三方容器）来管理。</p>



<h2 class="wp-block-heading" id="a7dg7">spring 有哪些主要模块？</h2>



<ul class="wp-block-list">
<li>spring core：框架的最基础部分，提供 ioc 和依赖注入特性。</li>



<li>spring context：构建于 core 封装包基础上的 context 封装包，提供了一种框架式的对象访问方法。</li>



<li>spring dao：Data Access Object 提供了JDBC的抽象层。</li>



<li>spring aop：提供了面向切面的编程实现，让你可以自定义拦截器、切点等。</li>



<li>spring Web：提供了针对 Web 开发的集成特性，例如文件上传，利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。</li>



<li>spring Web mvc：spring 中的 mvc 封装包提供了 Web 应用的 Model-View-Controller（MVC）的实现。</li>
</ul>



<h2 class="wp-block-heading" id="8oiaf">spring 常用的注入方式有哪些？</h2>



<ul class="wp-block-list">
<li>setter 属性注入</li>



<li>构造方法注入</li>



<li>注解方式注入</li>
</ul>



<h2 class="wp-block-heading" id="77t2b">spring 中的 bean 是线程安全的吗？</h2>



<p>spring 中的 bean 默认是单例模式，spring 框架并没有对单例 bean 进行多线程的封装处理。</p>



<p>实际上大部分时候 spring bean 无状态的（比如 dao 类），所有某种程度上来说 bean 也是安全的，但如果 bean 有状态的话（比如 view model 对象），那就要开发者自己去保证线程安全了，最简单的就是改变 bean 的作用域，把“singleton”变更为“prototype”，这样请求 bean 相当于 new Bean()了，所以就可以保证线程安全了。</p>



<ul class="wp-block-list">
<li>有状态就是有数据存储功能。</li>



<li>无状态就是不会保存数据。</li>
</ul>



<h2 class="wp-block-heading" id="1ptt4">spring 支持几种 bean 的作用域？</h2>



<p>spring 支持 5 种作用域，如下：</p>



<ul class="wp-block-list">
<li>singleton：spring ioc 容器中只存在一个 bean 实例，bean 以单例模式存在，是系统默认值；</li>



<li>prototype：每次从容器调用 bean 时都会创建一个新的示例，既每次 getBean()相当于执行 new Bean()操作；</li>



<li>Web 环境下的作用域：</li>



<li>request：每次 http 请求都会创建一个 bean；</li>



<li>session：同一个 http session 共享一个 bean 实例；</li>



<li>global-session：用于 portlet 容器，因为每个 portlet 有单独的 session，globalsession 提供一个全局性的 http session。</li>
</ul>



<p><strong>「注意：」</strong>&nbsp;使用 prototype 作用域需要慎重的思考，因为频繁创建和销毁 bean 会带来很大的性能开销。</p>



<h2 class="wp-block-heading" id="82ru0">spring 自动装配 bean 有哪些方式？</h2>



<ul class="wp-block-list">
<li>no：默认值，表示没有自动装配，应使用显式 bean 引用进行装配。</li>



<li>byName：它根据 bean 的名称注入对象依赖项。</li>



<li>byType：它根据类型注入对象依赖项。</li>



<li>构造函数：通过构造函数来注入依赖项，需要设置大量的参数。</li>



<li>autodetect：容器首先通过构造函数使用 autowire 装配，如果不能，则通过 byType 自动装配。</li>
</ul>



<h2 class="wp-block-heading" id="c860l">spring 事务实现方式有哪些？</h2>



<ul class="wp-block-list">
<li>声明式事务：声明式事务也有两种实现方式，基于 xml 配置文件的方式和注解方式（在类上添加 @Transaction 注解）。</li>



<li>编码方式：提供编码的形式管理和维护事务。</li>
</ul>



<h2 class="wp-block-heading" id="5bprv">说一下 spring 的事务隔离？</h2>



<p>spring 有五大隔离级别，默认值为 ISOLATION_DEFAULT（使用数据库的设置），其他四个隔离级别和数据库的隔离级别一致：</p>



<ul class="wp-block-list">
<li>ISOLATION_DEFAULT：用底层数据库的设置隔离级别，数据库设置的是什么我就用什么；</li>



<li>ISOLATION<strong>READ</strong>UNCOMMITTED：未提交读，最低隔离级别、事务未提交前，就可被其他事务读取（会出现幻读、脏读、不可重复读）；</li>



<li>ISOLATION<strong>READ</strong>COMMITTED：提交读，一个事务提交后才能被其他事务读取到（会造成幻读、不可重复读），SQL server 的默认级别；</li>



<li>ISOLATION<strong>REPEATABLE</strong>READ：可重复读，保证多次读取同一个数据时，其值都和事务开始时候的内容是一致，禁止读取到别的事务未提交的数据（会造成幻读），MySQL 的默认级别；</li>



<li>ISOLATION_SERIALIZABLE：序列化，代价最高最可靠的隔离级别，该隔离级别能防止脏读、不可重复读、幻读。</li>
</ul>



<ul class="wp-block-list">
<li><strong>「脏读」</strong>&nbsp;：表示一个事务能够读取另一个事务中还未提交的数据。比如，某个事务尝试插入记录 A，此时该事务还未提交，然后另一个事务尝试读取到了记录 A。</li>



<li><strong>「不可重复读」</strong>&nbsp;：是指在一个事务内，多次读同一数据。</li>



<li><strong>「幻读」</strong>&nbsp;：指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录，但是第二次同等条件下查询却有 n+1 条记录，这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据，同一个记录的数据内容被修改了，所有数据行的记录就变多或者变少了。</li>
</ul>



<h2 class="wp-block-heading" id="fmae2">说一下 spring mvc 运行流程？</h2>



<ul class="wp-block-list">
<li>spring mvc 先将请求发送给 DispatcherServlet。</li>



<li>DispatcherServlet 查询一个或多个 HandlerMapping，找到处理请求的 Controller。</li>



<li>DispatcherServlet 再把请求提交到对应的 Controller。</li>



<li>Controller 进行业务逻辑处理后，会返回一个ModelAndView。</li>



<li>Dispathcher 查询一个或多个 ViewResolver 视图解析器，找到 ModelAndView 对象指定的视图对象。</li>



<li>视图对象负责渲染返回给客户端。</li>
</ul>



<h2 class="wp-block-heading" id="ks5q">spring mvc 有哪些组件？</h2>



<ul class="wp-block-list">
<li>前置控制器 DispatcherServlet。</li>



<li>映射控制器 HandlerMapping。</li>



<li>处理器 Controller。</li>



<li>模型和视图 ModelAndView。</li>



<li>视图解析器 ViewResolver。</li>
</ul>



<h2 class="wp-block-heading" id="5hev0">@RequestMapping 的作用是什么？</h2>



<p>将 http 请求映射到相应的类/方法上。</p>



<h2 class="wp-block-heading" id="52hra">@Autowired 的作用是什么？</h2>



<p>@Autowired 它可以对类成员变量、方法及构造函数进行标注，完成自动装配的工作，通过@Autowired 的使用来消除 set/get 方法。</p>



<h2 class="wp-block-heading"><strong>@Autowired和@Resource注解的区别</strong></h2>



<p>@Autowired与@Resource都可以用来装配bean，都可以写在<strong>属性</strong>或<strong>set</strong>方法上,<strong>@Autowired还可以写在构造方法上,@Resource不可以</strong></p>



<p><strong>1、@Autowired</strong></p>



<p>由Spring提供，只<strong>按照类型</strong>自动注入</p>



<p><strong>2、@Resource</strong></p>



<p>由J2EE提供，默认<strong>按照名称</strong>自动注入</p>



<p>@Resource有两个属性：<strong>name和type</strong></p>



<p>@Resource装配顺序：</p>



<ol class="wp-block-list">
<li>如果同时指定了<strong>name和type</strong>，则从Spring上下文中找到<strong>唯一匹配</strong>的bean进行装配，<strong>找不到则抛出异常</strong></li>



<li>如果指定了<strong>name</strong>，则从Spring上下文中查找<strong>名称匹配</strong>的bean进行装配，<strong>找不到则抛出异常</strong></li>



<li>如果指定了<strong>type</strong>，则从Spring上下文中找到<strong>类型匹配</strong>的bean进行装配，<strong>找不到或找到多个，都抛出异常</strong></li>
</ol>



<h1 class="wp-block-heading" id="aqd0p"><strong>Spring Boot/Spring Cloud</strong></h1>



<h2 class="wp-block-heading" id="at6tk">什么是 spring boot？</h2>



<p>spring boot 是为 spring 服务的，是用来简化新 spring 应用的初始搭建以及开发过程的。</p>



<h2 class="wp-block-heading" id="120er">为什么要用 spring boot？</h2>



<ul class="wp-block-list">
<li>配置简单</li>



<li>独立运行</li>



<li>自动装配</li>



<li>无代码生成和 xml 配置</li>



<li>提供应用监控</li>



<li>易上手</li>



<li>提升开发效率</li>
</ul>



<h2 class="wp-block-heading" id="531nh">spring boot 核心配置文件是什么？</h2>



<p>spring boot 核心的两个配置文件：</p>



<ul class="wp-block-list">
<li>bootstrap (. yml 或者 . properties)：boostrap 由父 ApplicationContext 加载的，比 applicaton 优先加载，且 boostrap 里面的属性不能被覆盖；</li>



<li>application (. yml 或者 . properties)：用于 spring boot 项目的自动化配置。</li>
</ul>



<h2 class="wp-block-heading" id="aair3">spring boot 配置文件有哪几种类型？它们有什么区别？</h2>



<p>配置文件有 . properties 格式和 . yml 格式，它们主要的区别是书法风格不同。</p>



<p>. properties 配置如下：</p>



<pre class="wp-block-code"><code>spring.RabbitMQ.port=5672</code></pre>



<p>. yml 配置如下：</p>



<pre class="wp-block-code"><code>spring:
    RabbitMQ:
        port: 5672</code></pre>



<p>yml 格式不支持 @PropertySource 注解导入。</p>



<h2 class="wp-block-heading" id="59tdl">spring boot 有哪些方式可以实现热部署？</h2>



<ul class="wp-block-list">
<li>使用 devtools 启动热部署，添加 devtools 库，在配置文件中把 spring. devtools. restart. enabled 设置为 true；</li>



<li>使用 Intellij Idea 编辑器，勾上自动编译或手动重新编译。</li>
</ul>



<h2 class="wp-block-heading" id="80mgm">jpa 和 hibernate 有什么区别？</h2>



<p>jpa 全称 Java Persistence API，是 Java 持久化接口规范，hibernate 属于 jpa 的具体实现。</p>



<h2 class="wp-block-heading" id="esrjj">什么是 spring cloud？</h2>



<p>spring cloud 是一系列框架的有序集合。它利用 spring boot 的开发便利性巧妙地简化了分布式系统基础设施的开发，如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等，都可以用 spring boot 的开发风格做到一键启动和部署。</p>



<h2 class="wp-block-heading" id="64h4e">spring cloud 断路器的作用是什么？</h2>



<p>在分布式架构中，断路器模式的作用也是类似的，当某个服务单元发生故障（类似用电器发生短路）之后，通过断路器的故障监控（类似熔断保险丝），向调用方返回一个错误响应，而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放，避免了故障在分布式系统中的蔓延。</p>



<h2 class="wp-block-heading" id="bnlcl">spring cloud 的核心组件有哪些？</h2>



<ul class="wp-block-list">
<li>Eureka：服务注册于发现。</li>



<li>Feign：基于动态代理机制，根据注解和选择的机器，拼接请求 url 地址，发起请求。</li>



<li>Ribbon：实现负载均衡，从一个服务的多台机器中选择一台。</li>



<li>Hystrix：提供线程池，不同的服务走不同的线程池，实现了不同服务调用的隔离，避免了服务雪崩的问题。</li>



<li>Zuul：网关管理，由 Zuul 网关转发请求给对应的服务。</li>
</ul>



<h2 class="wp-block-heading">Spring循环依赖怎么解决的？</h2>



<p>Spring通过三级缓存解决单例Bean的循环依赖（主要是构造器注入无法解决）：</p>



<ul class="wp-block-list">
<li><strong>一级缓存</strong>：singletonObjects，存放完全初始化好的Bean。</li>



<li><strong>二级缓存</strong>：earlySingletonObjects，存放提前暴露的早期对象（尚未填充属性）。</li>



<li><strong>三级缓存</strong>：singletonFactories，存放Bean工厂，用于生成早期对象的引用。</li>



<li>过程：A依赖B，B依赖A。创建A时，将A的工厂放入三级缓存，然后填充属性时发现依赖B，去创建B；B创建时依赖A，从三级缓存获取A的工厂生成早期A（放入二级缓存），B完成注入后创建完毕，然后A从二级缓存拿到B的引用，完成后续初始化。最终A放入一级缓存。</li>
</ul>



<p><strong>详细说明：</strong></p>



<p>Spring 框架为了解决单例作用域下的循环依赖问题，设计了一套基于&nbsp;<strong>三级缓存</strong>&nbsp;的机制。下面将详细解释其原理、步骤以及为什么需要三级缓存。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">1. 什么是循环依赖</h3>



<p>循环依赖是指两个或多个 Bean 之间互相持有对方的引用，形成一个闭环。例如：</p>



<ul class="wp-block-list">
<li>A 依赖 B，B 依赖 A（最简单的循环依赖）。</li>



<li>A 依赖 B，B 依赖 C，C 依赖 A（更复杂的循环）。</li>
</ul>



<p>在创建 Bean 时，如果容器无法打破这个环，就会陷入无限递归，最终导致&nbsp;<code>BeanCurrentlyInCreationException</code>。</p>



<h3 class="wp-block-heading">2. Spring 能解决哪些循环依赖</h3>



<p>Spring&nbsp;<strong>仅能解决</strong>&nbsp;以下条件的循环依赖：</p>



<ul class="wp-block-list">
<li>Bean 的作用域是&nbsp;<strong>单例（singleton）</strong></li>



<li>依赖注入的方式为&nbsp;<strong>setter 注入</strong>&nbsp;或&nbsp;<strong>字段注入（@Autowired）</strong><br>（因为这两种方式可以在对象实例化后，再设置属性）</li>



<li>不能解决&nbsp;<strong>构造函数注入</strong>&nbsp;的循环依赖（因为构造函数必须在实例化时完成所有参数的注入，此时对象尚未创建，无法提前暴露）。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 三级缓存的内部结构</h3>



<p>Spring 容器内部维护了三个缓存，用于存放不同阶段的 Bean 实例：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left">缓存名称</th><th class="has-text-align-left" data-align="left">作用</th></tr></thead><tbody><tr><td><strong>一级缓存</strong>&nbsp;<code>singletonObjects</code></td><td>存放完全初始化好的单例 Bean（成品）</td></tr><tr><td><strong>二级缓存</strong>&nbsp;<code>earlySingletonObjects</code></td><td>存放提前暴露的 Bean 实例（半成品），即对象已经创建，但尚未完成属性填充和初始化</td></tr><tr><td><strong>三级缓存</strong>&nbsp;<code>singletonFactories</code></td><td>存放生成 Bean 实例的&nbsp;<code>ObjectFactory</code>&nbsp;工厂对象（用于生成代理对象等）</td></tr></tbody></table></figure>



<p>这三个缓存对应 Spring 的&nbsp;<code>DefaultSingletonBeanRegistry</code>&nbsp;类中的三个成员变量：</p>



<pre class="wp-block-code"><code><em>// 一级</em>
private final Map&lt;String, Object&gt; <strong>singletonObjects</strong> = new ConcurrentHashMap&lt;&gt;(256);          
<em>// 二级</em>
private final Map&lt;String, Object&gt; <strong>earlySingletonObjects</strong> = new ConcurrentHashMap&lt;&gt;(16);     
<em>// 三级</em>
private final Map&lt;String, ObjectFactory&lt;?&gt;&gt; <strong>singletonFactories</strong> = new HashMap&lt;&gt;(16);        </code></pre>



<h3 class="wp-block-heading">4. 解决循环依赖的流程（以 A 依赖 B，B 依赖 A 为例）</h3>



<p>假设两个 Bean 都是通过 setter 注入，且作用域为单例。</p>



<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-8cf370e7 wp-block-group-is-layout-flex">
<h4 class="wp-block-heading">步骤 1：开始创建 A</h4>



<ul class="wp-block-list">
<li><code>getBean("a")</code>&nbsp;发现一级缓存中没有 A。</li>



<li>实例化 A（调用构造函数），得到一个原始对象&nbsp;<code>aInstance</code>。</li>



<li><strong>提前暴露 A</strong>：将&nbsp;<code>aInstance</code>&nbsp;封装为一个&nbsp;<code>ObjectFactory</code>&nbsp;放入<strong>三级缓存</strong><code>singletonFactories</code>&nbsp;中（key = &#8220;a&#8221;）。
<ul class="wp-block-list">
<li>这一步实际上执行的是&nbsp;<code>addSingletonFactory("a", () -&gt; getEarlyBeanReference("a", mbd, aInstance))</code>，其中&nbsp;<code>getEarlyBeanReference</code>&nbsp;可能会返回原始对象或代理对象（如果有 AOP 增强）。</li>
</ul>
</li>
</ul>



<h4 class="wp-block-heading">步骤 2：填充 A 的属性</h4>



<ul class="wp-block-list">
<li>发现 A 需要注入 B，因此调用&nbsp;<code>getBean("b")</code>&nbsp;去创建或获取 B。</li>
</ul>



<h4 class="wp-block-heading">步骤 3：创建 B（同样的流程）</h4>



<ul class="wp-block-list">
<li><code>getBean("b")</code>&nbsp;一级缓存无 B。</li>



<li>实例化 B，得到原始对象&nbsp;<code>bInstance</code>。</li>



<li>将 B 的工厂放入三级缓存（key = &#8220;b&#8221;）。</li>
</ul>



<h4 class="wp-block-heading">步骤 4：填充 B 的属性</h4>



<ul class="wp-block-list">
<li>发现 B 需要注入 A，调用&nbsp;<code>getBean("a")</code>。</li>
</ul>



<h4 class="wp-block-heading">步骤 5：获取 A 的“提前引用”</h4>



<ul class="wp-block-list">
<li>此时 A 正在创建中，一级缓存没有 A。</li>



<li>查看二级缓存&nbsp;<code>earlySingletonObjects</code>，也没有 A。</li>



<li>查看三级缓存&nbsp;<code>singletonFactories</code>，发现有 A 的工厂。</li>



<li><strong>执行工厂</strong>，得到提前暴露的 A 的引用（可能是原始对象，也可能是 AOP 代理对象）。</li>



<li>将得到的对象放入<strong>二级缓存</strong>&nbsp;<code>earlySingletonObjects</code>（key = &#8220;a&#8221;），并从三级缓存中移除该工厂。</li>
</ul>



<h4 class="wp-block-heading">步骤 6：B 获得 A 的引用</h4>



<ul class="wp-block-list">
<li>B 成功拿到了 A 的引用（此时 A 尚未完成属性填充，但对象已存在）。</li>



<li>继续完成 B 的剩余属性填充和初始化。</li>



<li>B 初始化完成后，将自己放入<strong>一级缓存</strong>&nbsp;<code>singletonObjects</code>（此时 B 是成品），并清理二、三级缓存中的 B。</li>
</ul>



<h4 class="wp-block-heading">步骤 7：回到 A</h4>



<ul class="wp-block-list">
<li>A 从&nbsp;<code>getBean("b")</code>&nbsp;调用返回，拿到了初始化好的 B 的完整引用。</li>



<li>将 B 注入到 A 中（此时 A 的属性填充完成）。</li>



<li>执行 A 的初始化方法（如&nbsp;<code>afterPropertiesSet</code>、init-method）。</li>



<li>将 A 放入<strong>一级缓存</strong>&nbsp;<code>singletonObjects</code>，并清理二、三级缓存中的 A。</li>
</ul>
</div>



<p>至此，循环依赖被完美解决，两个 Bean 都已完成创建并存于一级缓存。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">5. 为什么需要三级缓存，而不是两级？</h3>



<div class="wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-8cf370e7 wp-block-group-is-layout-flex">
<p>有些人可能会问：二级缓存（提前暴露的对象）加上一级缓存（成品）不就够了吗？为什么还需要第三级缓存（工厂）？</p>



<p>关键在于&nbsp;<strong>AOP 代理对象的创建时机</strong>。</p>



<ul class="wp-block-list">
<li>如果 Bean&nbsp;<strong>不需要</strong>&nbsp;AOP 代理，那么提前暴露的对象就是原始对象，没有区别。</li>



<li>如果 Bean&nbsp;<strong>需要</strong>&nbsp;AOP 代理（例如被&nbsp;<code>@Transactional</code>&nbsp;注解），那么代理对象的创建通常是在 Bean 初始化之后（通过&nbsp;<code>BeanPostProcessor</code>&nbsp;的&nbsp;<code>postProcessAfterInitialization</code>）。<br>但是，在解决循环依赖时，其他 Bean 可能在初始化之前就需要引用这个 Bean。如果等到初始化完成才创建代理，那么其他 Bean 拿到的就是原始对象，而非代理对象，这会导致 AOP 功能失效（例如事务不起作用）。</li>
</ul>



<p>因此，Spring 通过三级缓存中的&nbsp;<code>ObjectFactory</code>&nbsp;来&nbsp;<strong>延迟创建代理对象</strong>：</p>



<ul class="wp-block-list">
<li>工厂方法&nbsp;<code>getEarlyBeanReference</code>&nbsp;允许在<strong>提前暴露</strong>阶段就生成代理对象（如果 Bean 需要被代理）。</li>



<li>这样，当 B 通过工厂获取 A 的引用时，拿到的可能是一个尚未完全初始化的 A 的代理对象。之后 A 继续完成初始化，但 B 持有的已经是代理对象，保证了 AOP 的正确性。</li>
</ul>



<p>如果只有二级缓存（提前暴露原始对象），就无法在必要的时候提前创建代理，导致 AOP 失效。</p>



<h4 class="wp-block-heading">举例说明：</h4>



<ul class="wp-block-list">
<li>A 和 B 互相依赖，且 A 需要被事务代理。</li>



<li>如果没有三级缓存：
<ul class="wp-block-list">
<li>A 提前暴露原始对象，B 拿到原始 A 并完成创建。</li>



<li>之后 A 初始化完成，生成代理对象。</li>



<li>但 B 中注入的仍是原始 A，事务注解无效。</li>
</ul>
</li>



<li>有了三级缓存：
<ul class="wp-block-list">
<li>B 通过工厂获取 A 时，工厂返回一个<strong>代理对象</strong>（尚未完全初始化，但已经是代理）。</li>



<li>B 持有代理 A。</li>



<li>A 后续初始化完成后，容器中保存的也是同一个代理对象（或者代理对象内部包装了原始对象），因此 B 使用的代理正确。</li>
</ul>
</li>
</ul>
</div>



<h3 class="wp-block-heading">6. 哪些循环依赖 Spring 无法解决？</h3>



<ul class="wp-block-list">
<li><strong>构造函数注入</strong>：因为构造函数必须在实例化时完成，无法提前暴露对象，无法打破循环。</li>



<li><strong>非单例作用域</strong>（prototype）：Spring 不缓存 prototype 的 Bean，每次请求都新建，因此无法提前暴露，也无法解决循环依赖。</li>



<li><strong>多例混合</strong>：比如 singleton 依赖 prototype，但反过来则无法解决。</li>
</ul>



<p>对于构造函数注入的循环依赖，可以改用 setter 注入，或者使用&nbsp;<code>@Lazy</code>&nbsp;注解延迟加载其中一个依赖。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">7. 源码简要追溯</h3>



<p>主要逻辑集中在&nbsp;<code>AbstractBeanFactory.doGetBean()</code>&nbsp;和&nbsp;<code>DefaultSingletonBeanRegistry.getSingleton()</code>&nbsp;方法中。</p>



<p><code>getSingleton(String beanName, boolean allowEarlyReference)</code>&nbsp;方法体现了三级缓存的查找顺序：</p>



<pre class="wp-block-code"><code>protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null &amp;&amp; isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null &amp;&amp; allowEarlyReference) {
                ObjectFactory&lt;?&gt; singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}</code></pre>



<p>当创建一个 Bean 时，在&nbsp;<code>doCreateBean</code>&nbsp;中实例化后，会调用&nbsp;<code>addSingletonFactory</code>&nbsp;将工厂放入三级缓存。</p>



<h3 class="wp-block-heading">总结</h3>



<p>Spring 通过&nbsp;<strong>三级缓存</strong>&nbsp;巧妙地解决了单例 setter 注入的循环依赖问题：</p>



<ul class="wp-block-list">
<li>一级缓存存放完整 Bean。</li>



<li>二级缓存存放提前暴露的 Bean（半成品）。</li>



<li>三级缓存存放生产 Bean 的工厂，用于在必要时提前生成代理对象，避免 AOP 失效。</li>
</ul>



<p>这种设计既保证了循环依赖的解决，又兼顾了 AOP 等增强功能的正确性。</p>



<h2 class="wp-block-heading">@Lazy延迟加载</h2>



<p><code>@Lazy</code>&nbsp;是 Spring 提供的一个灵活控制 Bean 生命周期的注解，主要用途包括：</p>



<ul class="wp-block-list">
<li><strong>性能优化</strong>：延迟非必要 Bean 的初始化，加快启动速度。</li>



<li><strong>打破循环依赖</strong>：特别是解决构造器注入导致的循环依赖。</li>



<li><strong>按需加载</strong>：让资源消耗较大的 Bean 在真正需要时才创建。</li>
</ul>



<h3 class="wp-block-heading">工作原理</h3>



<p>Spring 容器在启动时，对于延迟加载的 Bean，并不会立即调用其构造方法和初始化逻辑，而是：</p>



<ul class="wp-block-list">
<li><strong>对于&nbsp;<code>@Lazy</code>&nbsp;标注的类或&nbsp;<code>@Bean</code>&nbsp;方法</strong>：容器会将这些 Bean 的创建推迟到第一次调用&nbsp;<code>getBean()</code>&nbsp;获取它们时。但在容器启动阶段，它们会被记录下来，但不会实例化。</li>



<li><strong>对于注入点上使用&nbsp;<code>@Lazy</code></strong>：Spring 会生成一个<strong>代理对象</strong>（基于 CGLIB 或 JDK 动态代理）并注入到目标 Bean 中。该代理对象会拦截所有方法调用，在首次调用时触发目标 Bean 的创建和初始化，然后将调用委派给真实的 Bean 实例。</li>
</ul>



<p>正是通过这种代理机制，<code>@Lazy</code>&nbsp;才能在不破坏依赖关系的情况下实现延迟加载。</p>



<h2 class="wp-block-heading">CGLIB 与 JDK 动态代理</h2>



<ul class="wp-block-list">
<li><strong>JDK 动态代理</strong>&nbsp;是 Java 原生提供的，基于接口的代理，适合接口设计良好的场景。</li>



<li><strong>CGLIB 代理</strong>&nbsp;通过生成子类实现代理，更加灵活，适合没有接口的类或需要更细粒度控制的场景。</li>



<li>Spring 根据目标类是否有接口自动选择，也允许强制使用 CGLIB。</li>



<li>在实际开发中，如果使用 Spring Boot 2.x，<strong>默认 CGLIB </strong>模式可以避免接口定义，使代码更简洁。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot 的核心注解是哪个？</h2>



<p>核心注解是&nbsp;<strong><code>@SpringBootApplication</code></strong>&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。通常标注在项目的主启动类上。</p>



<h2 class="wp-block-heading">SpringBootApplication注解包含哪几个注解？</h2>



<p>它是一个组合注解，主要包含以下三个核心注解&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li><strong><code>@SpringBootConfiguration</code></strong>：继承自 <code>@Configuration</code>，表示该类是一个配置类。</li>



<li><strong><code>@EnableAutoConfiguration</code></strong>：开启自动配置功能。</li>



<li><strong><code>@ComponentScan</code></strong>：启用组件扫描，默认扫描主启动类所在包及其子包。</li>
</ol>



<h2 class="wp-block-heading">SpringBoot最核心的注解有哪些？</h2>



<p>除了上面的&nbsp;<code>@SpringBootApplication</code>&nbsp;组合注解外，还有：</p>



<ul class="wp-block-list">
<li><strong><code>@EnableAutoConfiguration</code></strong>：自动配置的核心。</li>



<li><strong><code>@Conditional</code> 系列注解</strong>：如 <code>@ConditionalOnClass</code>、<code>@ConditionalOnMissingBean</code>，用于控制自动配置在满足特定条件时才生效 <a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/information/%e5%bd%93%e6%9c%89%e4%b8%80%e4%ba%9b%e7%b1%bb%e5%8f%af%e8%83%bd%e4%b8%8d%e5%ad%98%e5%9c%a8%e6%97%b6%ef%bc%8c%e6%88%91%e5%ba%94%e8%af%a5%e5%a6%82%e4%bd%95%e5%9c%a8XxxAutoConfiguration%e4%b8%ad%e7%ae%a1%e7%90%86%e5%ae%83%e4%bb%ac%ef%bc%9f" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong><code>@ConfigurationProperties</code></strong>：用于将配置文件（如 <code>application.yml</code>）中的属性绑定到 Java Bean 上 <a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot自动配置原理是什么？</h2>



<p>原理主要基于 <code>@EnableAutoConfiguration</code> 注解 <a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。它会通过 <code>SpringFactoriesLoader</code> 机制，从 classpath 下所有 JAR 包中的 <code>META-INF/spring.factories</code> 文件里，加载 <code>EnableAutoConfiguration</code> 键对应的自动配置类（如 <code>XxxAutoConfiguration</code>）<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。这些配置类上通常标有 <code>@Conditional</code> 条件注解，只有满足特定条件（如存在相关类、配置了相关属性）时，这些自动配置类才会生效，实例化相应的 Bean。</p>



<h2 class="wp-block-heading">SpringBoot开启自动配置的原理是什么？</h2>



<p>只需要在配置类上使用&nbsp;<strong><code>@EnableAutoConfiguration</code></strong>&nbsp;注解即可。由于&nbsp;<code>@SpringBootApplication</code>&nbsp;已经组合了该注解，所以只要在启动类上标记&nbsp;<code>@SpringBootApplication</code>，就相当于开启了自动配置&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a><a href="https://developer.aliyun.com/article/1700264" target="_blank" rel="noreferrer noopener"></a>。</p>



<h2 class="wp-block-heading">SpringBoot自动配置的类型在哪注册？</h2>



<p>自动配置类（类型）的注册信息存放在所有 JAR 包中的&nbsp;<strong><code>META-INF/spring.factories</code></strong>&nbsp;文件中&nbsp;<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a>。Spring Boot 启动时会扫描该文件，获取所有注册的自动配置类的全限定名。</p>



<h2 class="wp-block-heading">SpringBoot自动配置报告怎么查看？</h2>



<p>开启&nbsp;<strong>debug 模式</strong>。在配置文件（<code>application.properties</code>&nbsp;或&nbsp;<code>application.yml</code>）中设置&nbsp;<code>debug: true</code>。启动应用后，控制台会打印&nbsp;<strong>Positive matches</strong>（生效的自动配置）和&nbsp;<strong>Negative matches</strong>（未生效的自动配置）报告，方便开发者了解哪些自动配置类被应用了。</p>



<h2 class="wp-block-heading">SpringBoot怎么排除某些自动配置？</h2>



<p>有多种方式可以排除自动配置类&nbsp;<a href="https://m.yisu.com/zixun/363917.html" target="_blank" rel="noreferrer noopener"></a><a href="https://cloud.tencent.com.cn/developer/information/%e5%bd%93%e6%9c%89%e4%b8%80%e4%ba%9b%e7%b1%bb%e5%8f%af%e8%83%bd%e4%b8%8d%e5%ad%98%e5%9c%a8%e6%97%b6%ef%bc%8c%e6%88%91%e5%ba%94%e8%af%a5%e5%a6%82%e4%bd%95%e5%9c%a8XxxAutoConfiguration%e4%b8%ad%e7%ae%a1%e7%90%86%e5%ae%83%e4%bb%ac%ef%bc%9f" target="_blank" rel="noreferrer noopener"></a>：</p>



<ol start="1" class="wp-block-list">
<li>在 <code>@SpringBootApplication</code> 或 <code>@EnableAutoConfiguration</code> 注解中使用 <code>exclude</code> 属性：<code>@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})</code></li>



<li>在配置文件中使用 <code>spring.autoconfigure.exclude</code> 属性：<code>spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration</code></li>
</ol>



<h2 class="wp-block-heading">SpringBoot怎么开启和关闭自动配置？</h2>



<ul class="wp-block-list">
<li><strong>开启</strong>：使用 <code>@EnableAutoConfiguration</code> 注解（通常通过 <code>@SpringBootApplication</code> 间接使用）<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a>。</li>



<li><strong>关闭</strong>：一般不建议完全关闭自动配置。如果想禁用特定的自动配置，可以使用排除（exclude）机制。如果不想使用整个 Spring Boot 的自动配置特性，可以不使用 <code>@EnableAutoConfiguration</code> 注解，但这会丧失 Spring Boot 的核心优势。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot的目录结构是怎样的？</h2>



<p>一个典型的 Spring Boot 项目目录结构遵循一定的约定&nbsp;<a href="https://cloud.tencent.cn/developer/article/1079825" target="_blank" rel="noreferrer noopener"></a>：</p>



<pre class="wp-block-preformatted">com.example.project
├── Application.java (主启动类，位于根包下)
├── domain/ (或 model) - 实体类
├── repository/ (或 dao) - 数据访问层
├── service/ - 业务逻辑层
│   └── impl/ - 业务实现类
└── web/ (或 controller) - 控制器层</pre>



<p>此外，资源文件位于&nbsp;<code>src/main/resources</code>&nbsp;目录下，包含：</p>



<ul class="wp-block-list">
<li><code>application.properties</code> 或 <code>application.yml</code>：配置文件。</li>



<li><code>static/</code>：存放静态资源（如 CSS, JS, 图片）。</li>



<li><code>templates/</code>：存放模板文件（如 Thymeleaf）。</li>
</ul>



<h2 class="wp-block-heading">SpringBoot中的Starters是什么？</h2>



<p>Starters 是一组方便的<strong>依赖描述符</strong>&nbsp;<a href="https://cloud.baidu.com/article/3716859" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。它相当于一个“一站式”的依赖集合。例如，引入&nbsp;<code>spring-boot-starter-web</code>&nbsp;依赖，它就会自动传递引入构建 Web 应用所需的所有依赖（如 Spring MVC、内嵌 Tomcat、Jackson 等），避免了开发者手动寻找和配置一个个兼容版本的依赖，极大地简化了项目的依赖管理&nbsp;<a href="https://www.finclip.com/news/f/23737.html" target="_blank" rel="noreferrer noopener"></a><a href="https://bbs.huaweicloud.com/blogs/402045" target="_blank" rel="noreferrer noopener"></a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/17/456/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">456</post-id>	</item>
		<item>
		<title>【Java面试】redis</title>
		<link>https://blog.lhyshome.com/2026/03/04/353/</link>
					<comments>https://blog.lhyshome.com/2026/03/04/353/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Wed, 04 Mar 2026 10:07:52 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=353</guid>

					<description><![CDATA[Redis 是什么？都有哪些使用场景？ Redis 是一个使用 C 语言开发的高速缓存数据库。 Redis 使… <span class="read-more"><a href="https://blog.lhyshome.com/2026/03/04/353/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="9ld6q">Redis 是什么？都有哪些使用场景？</h2>



<p>Redis 是一个使用 C 语言开发的高速缓存数据库。</p>



<p>Redis 使用场景：</p>



<ul class="wp-block-list">
<li>记录帖子点赞数、点击数、评论数；</li>



<li>缓存近期热帖；</li>



<li>缓存文章详情信息；</li>



<li>记录用户会话信息。</li>
</ul>



<h2 class="wp-block-heading" id="cmmhk">Redis 有哪些功能？</h2>



<ul class="wp-block-list">
<li>数据缓存功能</li>



<li>分布式锁的功能</li>



<li>支持数据持久化</li>



<li>支持事务</li>



<li>支持消息队列</li>
</ul>



<h2 class="wp-block-heading" id="f5r6k">Redis 和 memcache 有什么区别？</h2>



<ul class="wp-block-list">
<li>存储方式不同：memcache 把数据全部存在内存之中，断电后会挂掉，数据不能超过内存大小；Redis 有部份存在硬盘上，这样能保证数据的持久性。</li>



<li>数据支持类型：memcache 对数据类型支持相对简单；Redis 有复杂的数据类型。</li>



<li>使用底层模型不同：它们之间底层实现方式，以及与客户端之间通信的应用协议不一样，Redis 自己构建了 vm 机制，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求。</li>



<li>value 值大小不同：Redis 最大可以达到 512mb；memcache 只有 1mb。</li>
</ul>



<h2 class="wp-block-heading" id="3o5b2">Redis 为什么是单线程的？</h2>



<p>因为 cpu 不是 Redis 的瓶颈，Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现，而且 cpu 又不会成为瓶颈，那就顺理成章地采用单线程的方案了。</p>



<p>关于 Redis 的性能，官方网站也有，普通笔记本轻松处理每秒几十万的请求。</p>



<p>而且单线程并不代表就慢 nginx 和 nodejs 也都是高性能单线程的代表。</p>



<h2 class="wp-block-heading" id="f516k">什么是缓存穿透？怎么解决？</h2>



<p>缓存穿透：指查询一个一定不存在的数据，由于缓存是不命中时需要从数据库查询，查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到数据库去查询，造成缓存穿透。</p>



<p>解决方案：最简单粗暴的方法如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们就把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。</p>



<h2 class="wp-block-heading" id="4g04h">Redis 支持的数据类型有哪些？</h2>



<p>Redis 支持的数据类型：string（字符串）、list（列表）、hash（字典）、set（集合）、zset（有序集合）。</p>



<h2 class="wp-block-heading" id="9tb8a">Redis 支持的 Java 客户端都有哪些？</h2>



<p>支持的 Java 客户端有 Redisson、jedis、lettuce 等。</p>



<h2 class="wp-block-heading" id="vdag">jedis 和 Redisson 有哪些区别？</h2>



<ul class="wp-block-list">
<li>jedis：提供了比较全面的 Redis 命令的支持。</li>



<li>Redisson：实现了分布式和可扩展的 Java 数据结构，与 jedis 相比 Redisson 的功能相对简单，不支持排序、事务、管道、分区等 Redis 特性。</li>
</ul>



<h2 class="wp-block-heading" id="5d4f7">怎么保证缓存和数据库数据的一致性？</h2>



<ul class="wp-block-list">
<li>合理设置缓存的过期时间。</li>



<li>新增、更改、删除数据库操作时同步更新 Redis，可以使用事物机制来保证数据的一致性。</li>
</ul>



<h2 class="wp-block-heading" id="2g5n8">Redis 持久化有几种方式？</h2>



<p>Redis 的持久化有两种方式，或者说有两种策略：</p>



<ul class="wp-block-list">
<li>RDB（Redis Database）：指定的时间间隔能对你的数据进行快照存储。</li>



<li>AOF（Append Only File）：每一个收到的写命令都通过write函数追加到文件中。</li>
</ul>



<h2 class="wp-block-heading" id="476uu">Redis 怎么实现分布式锁？</h2>



<p>Redis 分布式锁其实就是在系统里面占一个“坑”，其他程序也要占“坑”的时候，占用成功了就可以继续执行，失败了就只能放弃或稍后重试。</p>



<p>占坑一般使用 setnx(set if not exists)指令，只允许被一个程序占有，使用完调用 del 释放锁。</p>



<h2 class="wp-block-heading" id="9uhri">Redis 分布式锁有什么缺陷？</h2>



<p>Redis 分布式锁不能解决超时的问题，分布式锁有一个超时时间，程序的执行如果超出了锁的超时时间就会出现问题。</p>



<h2 class="wp-block-heading" id="cadob">Redis 如何做内存优化？</h2>



<p>尽量使用 Redis 的散列表，把相关的信息放到散列表里面存储，而不是把每个字段单独存储，这样可以有效的减少内存使用。比如将 Web 系统的用户对象，应该放到散列表里面再整体存储到 Redis，而不是把用户的姓名、年龄、密码、邮箱等字段分别设置 key 进行存储。</p>



<h2 class="wp-block-heading" id="efamv">Redis 淘汰策略有哪些？</h2>



<ul class="wp-block-list">
<li>volatile-lru：从已设置过期时间的数据集（server. db[i]. expires）中挑选最近最少使用的数据淘汰。</li>



<li>volatile-ttl：从已设置过期时间的数据集（server. db[i]. expires）中挑选将要过期的数据淘汰。</li>



<li>volatile-random：从已设置过期时间的数据集（server. db[i]. expires）中任意选择数据淘汰。</li>



<li>allkeys-lru：从数据集（server. db[i]. dict）中挑选最近最少使用的数据淘汰。</li>



<li>allkeys-random：从数据集（server. db[i]. dict）中任意选择数据淘汰。</li>



<li>no-enviction（驱逐）：禁止驱逐数据。</li>
</ul>



<h2 class="wp-block-heading" id="1hd4h">Redis 常见的性能问题有哪些？该如何解决？</h2>



<ul class="wp-block-list">
<li>主服务器写内存快照，会阻塞主线程的工作，当快照比较大时对性能影响是非常大的，会间断性暂停服务，所以主服务器最好不要写内存快照。</li>



<li>Redis 主从复制的性能问题，为了主从复制的速度和连接的稳定性，主从库最好在同一个局域网内。</li>
</ul>



<h2 class="wp-block-heading">什么是Redis</h2>



<ul class="wp-block-list">
<li>Redis(Remote Dictionary Server) 是一个使用 C 语言编写的，开源的（BSD许 可）高性能非关系型（NoSQL）的键值对数据库。</li>



<li>Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串，值 支持五种数据类型：字符串、列表、集合、散列表、有序集合。</li>



<li>与传统数据库不同的是 Redis 的数据是存在内存中的，所以读写速度非常快， 因此 redis 被广泛应用于缓存方向，每秒可以处理超过 10万次读写操作，是已知性能快的Key-Value DB。</li>



<li>另外，Redis 也经常用来做分布式 锁。除此之 外，Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。</li>
</ul>



<h2 class="wp-block-heading">Redis有哪些优缺点</h2>



<ul class="wp-block-list">
<li>优点
<ul class="wp-block-list">
<li>读写性能优异， Redis能读的速度是110000次/s，写的速度是81000次/s。</li>



<li>支持数据持久化，支持AOF和RDB两种持久化方式。</li>



<li>AOF：写日志的方式，记录每次对服务器写的操作, 当服务器重启的时候会重新执行这些命令来恢复原始的数据。例如 Mysql binlog，Hbase HLog。</li>



<li>RDB：这是一种快照的方式，它将 Redis 某时间点的数据都进行快照存储。比如 Mysql Dump 也是这种方式。</li>



<li>支持事务，Redis的所有操作都是原子性的，同时Redis还支持对几个操作合并 后的原子性执行。</li>



<li>数据结构丰富，除了支持string类型的value外还支持hash、set、zset、list等 数据结构。</li>



<li>支持主从复制，主机会自动将数据同步到从机，可以进行读写分离。</li>
</ul>
</li>



<li>缺点
<ul class="wp-block-list">
<li>数据库容量受到物理内存的限制，不能用作海量数据的高性能读写，因此Redis 适合的场景主要局限在较小数据量的高性能操作和运算上。</li>



<li>Redis 不具备自动容错和恢复功能，主机从机的宕机都会导致前端部分读写请求 失败，需要等待机器重启或者手动切换前端的IP才能恢复。</li>



<li>主机宕机，宕机前有部分数据未能及时同步到从机，切换IP后还会引入数据不一 致的问题，降低了系统的可用性。</li>



<li>Redis 较难支持在线扩容，在集群容量达到上限时在线扩容会变得很复杂。为避 免这一问题，运维人员在系统上线时必须确保有足够的空间，这对资源造成了很大的浪费。</li>
</ul>
</li>
</ul>



<h2 class="wp-block-heading">为什么要用 Redis/缓存</h2>



<p>主要从“高性能”和“高并发”这两点来看待这个问题。<br><br><strong>高性能：</strong><br>假如用户第一次访问数据库中的某些数据。这个过程会比较慢，因为是从硬盘上 读取的。将该用户访问的数据存在数缓存中，这样下一次再访问这些数据的时候 就可以直接从缓存中获取了。操作缓存就是直接操作内存，所以速度相当快。如 果数据库中的对应数据改变的之后，同步改变缓存中相应的数据即可！</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="553" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1024x553.png" alt="" class="wp-image-355" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1024x553.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-300x162.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-768x415.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-660x356.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-500x270.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-800x432.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image.png 1213w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>高并发：</strong><br>直接操作缓存能够承受的请求是远远大于直接访问数据库的，所以我们可以考虑 把数据库中的部分数据转移到缓存中去，这样用户的一部分请求会直接到缓存这 里而不用经过数据库。</p>



<figure class="wp-block-image size-full"><img decoding="async" width="762" height="651" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1.png" alt="" class="wp-image-357" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1.png 762w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-300x256.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-660x564.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-1-500x427.png 500w" sizes="(max-width: 762px) 100vw, 762px" /></figure>



<h2 class="wp-block-heading">为什么要用Redis 而不用 map/guava 做缓存?</h2>



<p>缓存分为本地缓存和分布式缓存。以 Java 为例，使用自带的 map 或者 guava 实现的是本地缓存，主要的特点是轻量以及快速，生命周期随着 jvm 的销毁 而结束，并且在多实例的情况下，每个实例都需要各自保存一份缓存，缓存不具有一致性。<br>使用 redis 或 memcached 之类的称为分布式缓存，在多实例的情况下，各实 例共用一份缓存数据，缓存具有一致性。缺点是需要保持 redis 或 memcached 服务的高可用，整个程序架构上较为复杂。</p>



<h2 class="wp-block-heading">Redis为什么这么快</h2>



<p>完全基于内存，绝大部分请求是纯粹的内存操作，非常快速。数据存在内存 中，类似于 HashMap，HashMap 的优势就是查找和操作的时间复杂度都是 O(1)；<br>数据结构简单，对数据操作也简单，Redis 中的数据结构是专门进行设计 的；<br>采用单线程，避免了不必要的上下文切换和竞争条件，也不存在多进程或者多线程导致的切换而消耗CPU，不用去考虑各种锁的问题，不存在加锁释放锁操作，没有因为可能出现死锁而导致的性能消耗；<br>使用多路 I/O 复用模型，非阻塞 IO；<br>使用底层模型不同，它们之间底层实现方式以及与客户端之间通信的应用协议不一样，Redis直接自己构建了 VM 机制 ，因为一般的系统调用系统函数的话，会浪费一定的时间去移动和请求；</p>



<h2 class="wp-block-heading">Redis有哪些数据类型</h2>



<p>Redis主要有5种数据类型，包括String，List，Set，Zset，Hash，满足大部分 的使用要求 数据类型 可以存储 的值 操作 应用场景</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>数据类型</strong></td><td><strong>可以存储的值</strong></td><td><strong>操作</strong></td><td><strong>应用场景</strong></td></tr><tr><td>STRING</td><td>字符串、 整数或者 浮点数</td><td>对整个字 符串或者 字符串的 其中一部 分执行操 作 对整数和 浮点数执 行自增或者自减操作</td><td>做简单的 键值对缓存</td></tr><tr><td>LIST</td><td>列表</td><td>从两端压 入或者弹 出元素 对单个或 者多个元 素进行修 剪， 只保留一 个范围内 的元素</td><td>存储一些 列表型的 数据结 构，类似 粉丝列 表、文章 的评论列 表之类的 数据</td></tr><tr><td>SET</td><td>无序集合</td><td>添加、获 取、移除 单个元素 检查一个 元素是否 存在于集 合中</td><td>交集、并 集、差集 的操作， 比如交 集，可以 把两个人 的粉丝列</td></tr><tr><td>HASH</td><td>包含键值 对的无序 散列表</td><td>添加、获 取、移除 单个键值 对 获取所有 键值对 检查某个 键是否存 在</td><td>结构化的 数据，比 如一个对 象</td></tr><tr><td>ZSET</td><td>有序集合</td><td>添加、获 取、删除 元素 根据分值 范围或者 成员来获 取元素 计算一个 键的排名</td><td>去重但可 以排序， 如获取排 名前几名 的用户</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Redis的应用场景</h2>



<p><strong>7.1 总结一</strong><br><strong>计数器</strong><br>可以对 String 进行自增自减运算，从而实现计数器功能。Redis 这种内存型数 据库的读写性能非常高，很适合存储频繁读写的计数量。<br><strong>缓存</strong><br>将热点数据放到内存中，设置内存的大使用量以及淘汰策略来保证缓存的命中率。<br><strong>会话缓存</strong><br>可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存 储用户的会话信息，也就不再具有状态，一个用户可以请求任意一个应用服务 器，从而更容易实现高可用性以及可伸缩性。<br><strong>全页缓存（FPC）</strong><br>除基本的会话token之外，Redis还提供很简便的FPC平台。以Magento为例， Magento提供一个插件来使用Redis作为全页缓存后端。此外，对WordPress的用户来说，Pantheon有一个非常好的插件 wp-redis，这个插件能帮助你以快速度加载你曾浏览过的页面。<br><strong>查找表</strong><br>例如 DNS 记录就很适合使用 Redis 进行存储。查找表和缓存类似，也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效，而缓存的内容可以失效， 因为缓存不作为可靠的数据来源。<br><strong>消息队列(发布/订阅功能)</strong><br>List 是一个双向链表，可以通过 lpush 和 rpop 写入和读取消息。不过好使用Kafka、RabbitMQ等消息中间件。<br><strong>分布式锁实现</strong><br>在分布式场景下，无法使用单机环境下的锁来对多个节点上的进程进行同步。可 以使用 Redis 自带的 SETNX 命令实现分布式锁，除此之外，还可以使用官方提 供的 RedLock 分布式锁实现。<br><strong>其它</strong><br>Set 可以实现交集、并集等操作，从而实现共同好友等功能。ZSet 可以实现有 序性操作，从而实现排行榜等功能。</p>



<p><strong>7.2 总结二</strong><br>Redis相比其他缓存，有一个非常大的优势，就是支持多种数据类型。<br>数据类型说明string字符串，简单的k-v存储hashhash格式，value为field和 value，适合ID-Detail这样的场景。list简单的list，顺序列表，支持首位或者末 尾插入数据set无序list，查找速度快，适合交集、并集、差集处理sorted set有 序的set<br>其实，通过上面的数据类型的特性，基本就能想到合适的应用场景了。</p>



<ul class="wp-block-list">
<li><strong>string：</strong>适合简单的k-v存储，类似于memcached的存储结构，短信验证 码，配置信息等，就用这种类型来存储。</li>



<li><strong>hash：</strong>一般key为ID或者唯一标示，value对应的就是详情了。如商品详情， 个人信息详情，新闻详情等。</li>



<li><strong>list：</strong>因为list是有序的，比较适合存储一些有序且数据相对固定的数据。如省 市区表、字典表等。因为list是有序的，适合根据写入的时间来排序，如：新的***，消息队列等。</li>



<li><strong>set：</strong>可以简单的理解为ID-List的模式，如微博中一个人有哪些好友，set 牛的地方在于，可以对两个set提供交集、并集、差集操作。例如：查找两个人 共同的好友等。</li>



<li><strong>Sorted Set：</strong>是set的增强版本，增加了一个score参数，自动会根据score的 值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据。 如上所述，虽然Redis不像关系数据库那么复杂的数据结构，但是，也能适合很 多场景，比一般的缓存数据结构要多。了解每种数据结构适合的业务场景，不仅 有利于提升开发效率，也能有效利用Redis的性能。</li>
</ul>



<h2 class="wp-block-heading">Redis 的持久化机制是什么？各自的优缺点</h2>



<p>持久化就是把内存的数据写到磁盘中去，防止服务宕机了内存数据丢失。<br>Redis 提供两种持久化机制 RDB（默认） 和 AOF 机制:<br><strong>RDB：是Redis DataBase缩写快照</strong><br>RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中，对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="196" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1024x196.png" alt="" class="wp-image-358" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1024x196.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-300x57.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-768x147.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-660x126.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-500x96.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-800x153.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2-1280x245.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-2.png 1402w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>优点：</strong></p>



<ol class="wp-block-list">
<li>只有一个文件dump.rdb，方便持久化。</li>



<li>容灾性好，一个文件可以保存到安全的磁盘。</li>



<li>性能大化，fork子进程来完成写操作，让主进程继续处理命令，所以是 IO 大化。使用单独子进程来进行持久化，主进程不会进行任何 IO 操作，保证了 redis 的高性能</li>



<li>相对于数据集大时，比 AOF 的启动效率更高。</li>
</ol>



<p><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>数据安全性低。RDB是间隔一段时间进行持久化，如果持久化之间redis发生故障，会发生数据丢失。所以这种方式更适合数据要求不那么严谨的时候。</li>



<li>AOF（Append-only file)持久化方式： 是指所有的命令行记录以 redis 命 令请 求协议的格式完全持久化存储)保存为 aof 文件。</li>
</ol>



<p><strong>AOF：持久化</strong></p>



<p>AOF持久化(即Append Only File持久化)，则是将Redis执行的每次写命令记录到单独的日志文件中，当重启Redis会重新将持久化的日志中文件恢复数据。 当两种方式同时开启时，数据恢复Redis会优先选择AOF恢复。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="179" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1024x179.png" alt="" class="wp-image-359" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1024x179.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-300x53.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-768x134.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-660x116.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-500x88.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-800x140.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3-1280x224.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-3.png 1377w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>优点：</strong></p>



<ol class="wp-block-list">
<li>数据安全，aof 持久化可以配置 appendfsync 属性，有 always，每进行一 次命令操作就记录到 aof 文件中一次。</li>



<li>通过 append 模式写文件，即使中途服务器宕机，可以通过 redis-checkaof 工具解决数据一致性问题。</li>



<li>AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前（文件过大时会对命 令 进行合并重写），可以删除其中的某些命令（比如误操作的 flushall）。</li>
</ol>



<p><strong>缺点：</strong></p>



<ol class="wp-block-list">
<li>AOF 文件比 RDB 文件大，且恢复速度慢。</li>



<li>数据集大的时候，比 rdb 启动效率低。</li>
</ol>



<p><strong>优缺点是什么？</strong></p>



<ol class="wp-block-list">
<li>AOF文件比RDB更新频率高，优先使用AOF还原数据。</li>



<li>AOF比RDB更安全也更大</li>



<li>RDB性能比AOF好</li>



<li>如果两个都配了优先加载AOF</li>
</ol>



<h2 class="wp-block-heading">如何选择合适的持久化方式</h2>



<ul class="wp-block-list">
<li>一般来说， 如果想达到足以媲美PostgreSQL的数据安全性，你应该 同时使用两种持久化功能。在这种情况下，当 Redis 重启的时候会优先载 入AOF文件来恢复原始的数据，因为在通常情况下AOF文件保存的数据集 要比RDB文件保存的数据集要完整。</li>



<li>如果你非常关心你的数据， 但仍然可以承受数分钟以内的数据丢失， 那么你可以只使用RDB持久化。</li>



<li>有很多用户都只使用AOF持久化，但并不推荐这种方式，因为定时生 成RDB快照（snapshot）非常便于进行数据库备份， 并且 RDB 恢复数据 集的速度也要比AOF恢复的速度要快，除此之外，使用RDB还可以避免 AOF程序的bug。</li>



<li>如果你只希望你的数据在服务器运行的时候存在，你也可以不使用任何持久化方式。</li>
</ul>



<h2 class="wp-block-heading">Redis的过期键的删除策略（持久化）</h2>



<p>我们都知道，Redis是key-value数据库，我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了，Redis如何处理。<br>过期策略通常有以下三种：</p>



<ul class="wp-block-list">
<li>定时过期：每个设置过期时间的key都需要创建一个定时器，到过期时间就会立即清除。该策略可以立即清除过期的数据，对内存很友好；但是会占用大量的CPU资源去处理过期的数据，从而影响缓存的响应时间和吞吐量。</li>



<li>惰性过期：只有当访问一个key时，才会判断该key是否已过期，过期则清除。该策略可以最大化地节省CPU资源，却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问，从而不会被清除，占用大量内存。</li>



<li>定期过期：每隔一定的时间，会扫描一定数量的数据库的expires字典中一定数量的key，并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时，可以在不同情况下使得CPU和内存资源达到优的平衡效果。<br>(expires字典会保存所有设置了过期时间的key的过期时间数据，其中，key是指向键空间中的某个键的指针，value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)</li>
</ul>



<p>Redis中同时使用了惰性过期和定期过期两种过期策略。</p>



<h2 class="wp-block-heading">我们知道通过expire来设置key 的过期时间，那么对过期的数据怎么处理呢?（针对持久化的数据）</h2>



<p>除了缓存服务器自带的缓存失效策略之外（Redis默认的有6中策略可供选择），我们还可以根据具体的业务需求进行自定义的缓存淘汰，常见的策略有两种：</p>



<ol class="wp-block-list">
<li>定时去清理过期的缓存；</li>



<li>当有用户请求过来时，再判断这个请求所用到的缓存是否过期，过期的话就去底层系统得到新数据并更新缓存。</li>
</ol>



<p>两者各有优劣，第一种的缺点是维护大量缓存的key是比较麻烦的，第二种的缺点就是每次用户请求过来都要判断缓存失效，逻辑相对比较复杂！具体用哪种方案，大家可以根据自己的应用场景来权衡。</p>



<h2 class="wp-block-heading">redis内存淘汰策略是什么</h2>



<p>redis之所以快，因为他是依赖于内存的，但内存大小非常有限，如果超过就会报错。<br>所以当redis内存数据集大小上升到一定大小的时候，就会施行数据淘汰策略。</p>



<h2 class="wp-block-heading">Redis的内存淘汰策略有哪些</h2>



<p>Redis的内存淘汰策略是指在Redis用于缓存的内存不足时，怎么处理需要新写入且需要申请额外空间的数据。<br>对全局的键空间选择性移除<br>当内存不足以容纳新写入数据时，有一下几种方式操作：</p>



<ul class="wp-block-list">
<li>noeviction：，新写入操作会报错。</li>



<li>allkeys-lru：在键空间中，移除最近最少使用的key。（这个是最常用的）</li>



<li>allkeys-random：在键空间中，随机移除某个key。</li>
</ul>



<p>对设置过期时间的键空间选择性移除</p>



<ul class="wp-block-list">
<li>volatile-lru：在设置了过期时间的键空间中，移除最近最少使用的key。</li>



<li>volatile-random：在设置了过期时间的键空间中，随机移除某个key。</li>



<li>volatile-ttl：在设置了过期时间的键空间中，有更早过期时间的key优先移除。</li>
</ul>



<p><strong>总结</strong></p>



<p>Redis的内存淘汰策略的选取并不会影响过期的key的处理。<br>内存淘汰策略用于处理内存不足时的需要申请额外空间的数据（针对于内存）<br>过期策略用于处理过期的缓存数据（针对于数据库）</p>



<h2 class="wp-block-heading">Redis的内存用完了会发生什么？</h2>



<p>如果达到设置的上限，Redis的写命令会返回错误信息（但是读命令还可以正常返回）或者你可以配置内存淘汰机制，当Redis达到内存上限时会冲刷掉旧的内容。</p>



<h2 class="wp-block-heading">Redis如何做内存优化？</h2>



<p>可以好好利用Hash,list,sorted set,set等集合类型数据，因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表（hashes），散列表（是说散列表里面存储的数少）使用的内存非常小，所以你应该尽可能的将你的数据模型抽象到一个散列表里面（一个对象里面）。</p>



<p>例如：你的web系统中有一个用户对象，不要为这个用户的名称，姓氏，邮箱，密码设置单独的key，而是应该把这个用户的所有信息存储到一张散列表里面线程模型。</p>



<h2 class="wp-block-heading">什么是事务？</h2>



<p>事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</p>



<p>事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行。</p>



<h2 class="wp-block-heading">Redis事务的概念</h2>



<p>Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令，一个事务中所有命令都会被序列化。在事务执行过程，会按照顺序串行化执行队列中的命令，其他客户端 提交的命令请求不会插入到事务执行命令序列中。</p>



<p>总结说：redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。</p>



<h2 class="wp-block-heading">事务的基本操作</h2>



<p>开启事务</p>



<p>multi<br>AI生成项目<br>shell<br>1<br>作用：设定事务的开启位置，此指令执行后，后续的所有指令均加入到事务中</p>



<p>执行事务</p>



<p>exec<br>AI生成项目<br>shell<br>1<br>作用：设定事务的结束位置，同时执行事务。与multi成对出现，成对使用</p>



<p>注意：加入事务的命令暂时进入到任务队列中，并没有立即执行，只有执行exec命令才开始执行</p>



<p>但是你在命令入队列的时候，发现你有些命令指令输入错误，你不想执行了，这个时候，你不想提交事务了</p>



<p>取消事务</p>



<p>discard<br>AI生成项目<br>shell<br>1<br>作用：终止当前事务的定义，发生在multi之后，exec之前</p>



<p>总结：遇到multi指令，创建队列，遇到discard指令销毁队列</p>



<p>事务执行过程中，如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求，将会把请求放入队列中排队(需要研究)</p>



<h2 class="wp-block-heading">Redis事务相关命令</h2>



<p>Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的</p>



<p>Redis会将一个事务中的所有命令序列化，然后按顺序执行。</p>



<p>redis 不支持回滚，“Redis 在事务失败时不进行回滚，而是继续执行余下的命令”， 所以 Redis 的内部可以保持简单且快速。</p>



<p>如果在一个事务中的命令出现错误，那么所有的命令都不会执行；</p>



<p>如果在一个事务中出现运行错误，那么正确的命令会被执行。</p>



<p>WATCH命令是一个乐观锁，可以为 Redis 事务提供 check-and-set （CAS）行为。 可以监控一个或多个键，一旦其中有一个键被修改（或删除），之后的事务就不会执行，监控一直持续到EXEC命令。</p>



<p>MULTI命令用于开启一个事务，它总是返回OK。 MULTI执行之后，客户端可以继续向服务器发送任意多条命令，这些命令不会立即被执行，而是被放到一个队列中，当EXEC命令被调用时，所有队列中的命令才会被执行。</p>



<p>EXEC：执行所有事务块内的命令。返回事务块内所有命令的返回值，按命令执行的先后顺序排列。 当操作被打断时，返回空值 nil 。</p>



<p>通过调用DISCARD，客户端可以清空事务队列，并放弃执行事务， 并且客户端会从事务状态中退出。</p>



<p>UNWATCH命令可以取消watch对所有key的监控。</p>



<h2 class="wp-block-heading">Redis事务支持隔离性吗</h2>



<p>Redis 是单进程程序，并且它保证在执行事务时，不会对事务进行中断，事务可以运行直到执行完所有事务队列中的命令为止。因此，Redis 的事务是总是带有隔离性的。</p>



<h2 class="wp-block-heading">Redis事务保证原子性吗，支持回滚吗</h2>



<p>Redis中，单条命令是原子性执行的，但事务不保证原子性，且没有回滚。事务中任意命令执行失败，其余的命令仍会被执行。</p>



<h2 class="wp-block-heading">Redis事务其他实现</h2>



<p>基于Lua脚本，Redis可以保证脚本内的命令一次性、按顺序地执行，其同时也不提供事务运行错误的回滚，执行过程中如果部分命令运行错误，剩下的命令还是会继续运行完</p>



<p>基于中间标记变量，通过另外的标记变量来标识事务是否执行完成，读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现，比较繁琐集群方案哨兵模式</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="698" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-1024x698.png" alt="" class="wp-image-360" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-1024x698.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-300x204.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-768x524.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-660x450.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-500x341.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4-800x545.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-4.png 1178w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>哨兵的介绍</p>



<p>sentinel，中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件，主要有以下功能：</p>



<ul class="wp-block-list">
<li><strong>集群监控：</strong>负责监控 redis master 和 slave 进程是否正常工作。</li>



<li><strong>消息通知：</strong>如果某个 redis 实例有故障，那么哨兵负责发送消息作为报警通知给管理员。</li>



<li><strong>故障转移：</strong>如果 master node 挂掉了，会自动转移到 slave node 上。</li>



<li><strong>配置中心：</strong>如果故障转移发生了，通知 client 客户端新的 master 地址。</li>
</ul>



<p>哨兵用于实现 redis 集群的高可用，本身也是分布式的，作为一个哨兵集群去运行，互相协同工作。<br>故障转移时，判断一个 master node 是否宕机了，需要大部分的哨兵都同意才行，涉及到了分布式选举的问题。<br>即使部分哨兵节点挂掉了，哨兵集群还是能正常工作的，因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的，那就很坑爹了。</p>



<p>哨兵的核心知识</p>



<ul class="wp-block-list">
<li>哨兵至少需要 3 个实例，来保证自己的健壮性。</li>



<li>哨兵 + redis 主从的部署架构，是不保证数据零丢失的，只能保证 redis 集群的高可用性。</li>



<li>对于哨兵 + redis 主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练。</li>
</ul>



<p><strong>官方Redis Cluster 方案(服务端路由查询)</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="426" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1024x426.png" alt="" class="wp-image-361" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1024x426.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-300x125.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-768x319.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1536x638.png 1536w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-660x274.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-500x208.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-800x333.png 800w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5-1280x532.png 1280w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-5.png 1612w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>redis 集群模式的工作原理能说一下么？在集群模式下，redis 的 key 是如何寻址的？分布式寻址都有哪些算法？了解一致性 hash 算法吗？简介</p>



<p>Redis Cluster是一种服务端Sharding技术，3.0版本开始正式提供。Redis Cluster并没有使用一致性hash，而是采用slot(槽)的概念，一共分成16384个槽。将请求发送到任意节点，接收到请求的节点会将查询请求发送到正确的节点上执行。16384个槽数默认值是固定的。</p>



<p><strong>方案说明</strong></p>



<ol class="wp-block-list">
<li>通过哈希的方式，将数据分片，每个节点均分存储一定哈希槽(哈希值) 区间的数据，默认分配了16384 个槽位</li>



<li>每份数据分片会存储在多个互为主从的多节点上</li>



<li>数据写入先写主节点，再同步到从节点(支持配置为阻塞同步)</li>



<li>同一分片多个节点间的数据不保持一致性</li>



<li>读取数据时，当客户端操作的key没有分配在该节点上时，redis会返回转向指令，指向正确的节点</li>



<li>扩容时需要把旧节点的数据迁移一部分到新节点</li>
</ol>



<p>在 redis cluster 架构下，每个 redis 要放开两个端口号，一个用来对外供客户端连接，另一个用来节点间互相通信。</p>



<p>比如一个是 6379，另外一个就是 加1w 的端口号，比如 16379。</p>



<p>16379 端口号是用来进行节点间通信的，也就是 cluster bus 的东西，cluster bus 的通信，用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议，gossip 协议，用于节点间进行高效的数据交换，占用更少的网络带宽和处理时间。</p>



<p>节点间的内部通信机制</p>



<p>基本通信原理</p>



<p>集群元数据的维护有两种方式：集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。</p>



<h2 class="wp-block-heading">Redis主从架构</h2>



<p>单机的 redis，能够承载的 QPS 大概就在上万到几万不等。对于缓存来说，一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构，一主多从，主负责写，并且将数据复制到其它的 slave 节点，从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="594" height="422" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6.png" alt="" class="wp-image-362" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6.png 594w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6-300x213.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-6-500x355.png 500w" sizes="auto, (max-width: 594px) 100vw, 594px" /></figure>



<p>redis 采用异步方式复制数据到 slave 节点，不过 redis2.8 开始，slave node 会周期性地确认自己每次复制的数据量；</p>



<p>一个 master node 是可以配置多个 slave node 的； slave node 也可以连接其他的 slave node；</p>



<p>slave node 做复制的时候，不会（阻碍）block master node 的正常工作；</p>



<p>slave node 在做复制的时候，也不会 block 对自己的查询操作，它会用旧的数据集来提供服务；但是复制完成的时候，需要删除旧数据集，加载新数据集，这个时候就会暂停对外服务了；</p>



<p>slave node 主要用来进行横向扩容，做读写分离，扩容的 slave node 可以提高读的吞吐量。</p>



<p>注意：如果采用了主从架构，那么建议必须开启 master node 的持久化，不建议用 slave node 作为 master node 的数据热备，因为那样的话，如果你关掉 master 的持久化，可能在 master 宕机重启的时候数据是空的，然后可能一经过复制， slave node 的数据也丢了。</p>



<p>另外，master 的各种备份方案，也需要做。万一本地的所有文件丢失了，从备份中挑选一份 rdb 去恢复 master，这样才能确保启动的时候，是有数据的，即使采用了后续讲解的高可用机制，slave node 可以自动接管 master node，但也可能 sentinel 还没检测到 master failure，master node 就自动重启了，还是可能导致上面所有的 slave node 数据被清空。</p>



<p>redis 主从复制的核心原理当启动一个 slave node 的时候，它会发送一个 PSYNC 命令给 master node。</p>



<p>如果这是 slave node 初次连接到 master node，那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程，开始生成一份 RDB 快照文件，同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后， master 会将这个 RDB 发送给 slave，slave 会先写入本地磁盘，然后再从本地磁盘加载到内存中，接着 master 会将内存中缓存的写命令发送到 slave，slave 也会同步这些数据。</p>



<p>slave node 如果跟 master node 有网络故障，断开了连接，会自动重连，连接之后 master node 仅会复制给 slave 部分缺少的数据。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="916" height="327" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7.png" alt="" class="wp-image-363" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7.png 916w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-300x107.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-768x274.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-660x236.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-500x178.png 500w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-7-800x286.png 800w" sizes="auto, (max-width: 916px) 100vw, 916px" /></figure>



<ol class="wp-block-list">
<li>当从库和主库建立MS关系后，会向主数据库发送SYNC命令</li>



<li>主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程)，并将期间接收到的写命令存起来（都是主节点在做）</li>



<li>当快照完成后，主Redis会将快照文件和所有缓存的写命令发送给从Redis</li>



<li>从Redis接收到后，会载入快照文件并且执行收到的缓存的命令</li>



<li>之后，主Redis每当接收到写命令时就会将命令发送从Redis，从而保证数据的一致</li>
</ol>



<p>缺点</p>



<p>所有的slave节点数据的复制和同步都由master节点来处理，会照成master节点压力太大，使用主从从结构来解决</p>



<h2 class="wp-block-heading">Redis集群的主从复制模型是怎样的？</h2>



<p>为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用，所以集群使用了主从复制模型，每个节点都会有N-1个复制品生产环境中的 redis 是怎么部署的？</p>



<p>redis cluster，10 台机器，5 台机器部署了 redis 主实例，另外 5 台机器部署了 redis 的从实例，每个主实例挂了一个从实例，5 个节点对外提供读写服务，每个节点的读写高峰qps可能可以达到每秒 5 万，5 台机器最多是 25 万读写请求/s。</p>



<p>机器是什么配置？32G 内存+ 8 核 CPU + 1T 磁盘，但是分配给 redis 进程的是10g内存，一般线上生产环境，redis 的内存尽量不要超过 10g，超过 10g 可能会有问题。</p>



<p>5 台机器对外提供读写，一共有 50g 内存。</p>



<p>因为每个主实例都挂了一个从实例，所以是高可用的，任何一个主实例宕机，都会自动故障迁移，redis 从实例会自动变成主实例继续提供读写服务。</p>



<p>你往内存里写的是什么数据？每条数据的大小是多少？商品数据，每条数据是 10kb。100 条数据是 1mb，10 万条数据是 1g。常驻内存的是 200 万条商品数据，占用内存是 20g，仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。</p>



<p>其实大型的公司，会有基础架构的 team 负责缓存集群的运维。</p>



<h2 class="wp-block-heading">说说Redis哈希槽的概念？</h2>



<p>Redis集群没有使用一致性hash,而是引入了哈希槽的概念，Redis集群有16384个哈希槽，每个key通过CRC16校验后对16384取模来决定放置哪个槽，集群的每个节点负责一部分hash槽。</p>



<p>Redis 的哈希槽（Hash Slot）是 Redis 集群中的一个概念，用于将数据分布到不同的节点上。Redis 集群中使用哈希槽来实现数据的分片和负载均衡，将大量的数据分散到多个节点上，以提高系统的可扩展性和性能。</p>



<p>具体来说，Redis 会将所有的哈希槽均分到不同的节点上，每个节点负责处理一部分哈希槽的数据。</p>



<p>例如：一个 Redis 集群有 3 个节点，每个节点负责处理 16384 个哈希槽，那么每个节点负责处理的哈希槽范围为：<br>第一个节点：0-5460<br>第二个节点：5461-10922<br>第三个节点：10923-16383<br>当客户端需要访问某个键值对时，Redis 集群会根据键名计算出哈希值，并将该哈希值映射到一个哈希槽上。然后，Redis 集群会根据哈希槽的分布情况，将该键值对传递到对应的节点上进行处理。</p>



<p>在 Redis 集群中，哈希槽的默认数量是固定的，且不可更改。默认情况下，Redis 集群中有 16384 个哈希槽，可以通过 cluster slots 命令来设置哈希槽数量。当某个节点失效或者新增节点时，Redis 集群会重新计算哈希槽的分布情况，将负载均衡到其他节点上。<br>在 Redis 集群中，哈希槽的分布情况是通过哈希函数计算得出的。Redis 默认使用的哈希函数是 MurmurHash2，该函数可以将任意长度的数据映射成一个 32 位的二进制数，并保证均匀分布。除了 MurmurHash2，Redis 还支持其他的哈希函数，例如 CRC16 和 CRC32 等。<br>总的来说，Redis 的哈希槽是 Redis 集群中的一个重要概念，用于将数据分布到不同的节点上。Redis 集群通过哈希槽的分配和负载均衡，实现了高可用性、可扩展性和高性能的分布式存储服务。</p>



<h2 class="wp-block-heading">Redis集群会有写操作丢失吗？为什么？</h2>



<p>Redis并不能保证数据的强一致性，这意味这在实际中集群在特定的条件下可能会丢失写操作。</p>



<p>Redis集群之间是如何复制的？</p>



<p>异步复制</p>



<p>Redis集群最大节点个数是多少？</p>



<p>16384个，因为哈希槽最多16384个</p>



<p>Redis集群如何选择数据库？</p>



<p>Redis集群目前无法做数据库选择，默认在0数据库。</p>



<h2 class="wp-block-heading">Redis是单线程的，如何提高多核CPU的利用率？</h2>



<p>可以在同一个服务器部署多个Redis的实例，并把他们当作不同的服务器来使用，在某些时候，无论如何一个服务器是不够的， 所以，如果你想使用多个CPU，你可以考虑一下分片（shard）。</p>



<h2 class="wp-block-heading">为什么要做Redis分区？</h2>



<p>分区可以让Redis管理更大的内存，Redis将可以使用所有机器的内存。如果没有分区，你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升，Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。</p>



<h2 class="wp-block-heading">你知道有哪些Redis分区实现方案？</h2>



<ul class="wp-block-list">
<li>客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个 redis节点读取。大多数客户端已经实现了客户端分区。</li>



<li>代理分区意味着客户端将请求发送给代理，然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例，然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy</li>



<li>查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例，然后由Redis将请求转发给正确的Redis节点。Redis Cluster（redis集群）实现了一种混合形式的查询路由，但并不是直接将请求从一个redis节点转发到另一个redis节点，而是在客户端的帮助下直接redirected到正确的redis节点。</li>
</ul>



<h2 class="wp-block-heading">Redis分区有什么缺点？</h2>



<ul class="wp-block-list">
<li>涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集，因为他们可能被存储到不同的Redis实例（实际上这种情况也有办法，但是不能直接使用交集指令）。</li>



<li>同时操作多个key,则不能使用Redis事务。</li>



<li>分区使用的粒度是key，不能使用一个非常长的排序key存储一个数据集（The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set）</li>



<li>当使用分区的时候，数据处理会非常复杂，例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。</li>



<li>分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点，能做到最大程度对用户透明地数据再平衡，但其他一些客户端分区或者代理分区方法则不支持这种特性。然而，有一种预分片的技术也可以较好的解决这个问题。</li>
</ul>



<h2 class="wp-block-heading">Redis实现分布式锁</h2>



<p>具体实现可参考：基于redis实现分布式锁</p>



<p>Redis为单进程单线程模式，采用队列模式将并发访问变成串行访问，且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。</p>



<p>当且仅当 key 不存在，将 key 的值设为 value。 若给定的 key 已经存在，则SETNX 不做任何动作</p>



<p>SETNX 是『SET if Not eXists』(如果不存在，则 SET)的简写。</p>



<p>返回值：设置成功，返回 1 。设置失败，返回 0 。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="790" height="254" src="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8.png" alt="" class="wp-image-364" srcset="https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8.png 790w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-300x96.png 300w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-768x247.png 768w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-660x212.png 660w, https://blog.lhyshome.com/wp-content/uploads/2026/03/image-8-500x161.png 500w" sizes="auto, (max-width: 790px) 100vw, 790px" /></figure>



<p>使用SETNX完成同步锁的流程及事项如下(img)：使用SETNX命令获取锁，若返回0（key已存在，锁已存在）则获取失败，反之获取成功为了防止获取锁后程序出现异常，导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态，需要为该key设置一个“合理”的过期时间释放锁，使用DEL命令将锁数据删除</p>



<h2 class="wp-block-heading">分布式Redis是前期做还是后期规模上来了再做好？为什么？</h2>



<p>既然Redis是如此的轻量（单实例只使用1M内存），为防止以后的扩容， 好的办法就是一开始就启动较多实例。即便你只有一台服务器，你也可以一开始就让Redis以分布式的方式运行，使用分区，在同一台服务器上启动多个实例。</p>



<p>一开始就多设置几个Redis实例，例如32或者64个实例，对大多数用户来说这操作起来可能比较麻烦，但是从长久来看做这点牺牲是值得的。</p>



<p>这样的话，当你的数据不断增长，需要更多的Redis服务器时，你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已（而不用考虑重新分区的问题）。一旦你添加了另一台服务器，你需要将你一半的Redis实例从第一台机器迁移到第二台机器。</p>



<h2 class="wp-block-heading">缓存雪崩</h2>



<p>缓存雪崩是指缓存同一时间大面积的失效，所以，后面的请求都会落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p>解决方案</p>



<ol class="wp-block-list">
<li>缓存数据的过期时间设置随机，防止同一时间大量数据过期现象发生。</li>



<li>一般并发量不是特别多的时候，使用最多的解决方案是加锁排队。</li>



<li>给每一个缓存数据增加相应的缓存标记，记录缓存的是否失效，如果缓存标记失效，则更新数据缓存。</li>
</ol>



<h2 class="wp-block-heading">缓存穿透</h2>



<p>缓存穿透是指缓存和数据库中都没有的数据，导致所有的请求都落到数据库上，造成数据库短时间内承受大量请求而崩掉。</p>



<p>解决方案</p>



<ol class="wp-block-list">
<li>接口层增加校验，如用户鉴权校验，id做基础校验，id&lt;=0的直接拦截；</li>



<li>从缓存取不到的数据，在数据库中也没有取到，这时也可以将key-value对写为key-null，缓存有效时间可以设置短点，如30秒（设置太长会导致正常情况也没法使用）。这样可以防止攻击用户反复用同一个id暴力攻击</li>



<li>采用布隆过滤器，将所有可能存在的数据哈希到一个足够大的 bitmap 中，一个一定不存在的数据会被这个 bitmap 拦截掉，从而避免了对底层存储系统的查询压力附加</li>
</ol>



<p>Bitmap： 典型的就是哈希表缺点是，Bitmap对于每个元素只能记录1bit信息，如果还想完成额外的功能，恐怕只能靠牺牲更多的空间、时间来完成了。</p>



<p>布隆过滤器（推荐）</p>



<p>就是引入了k(k&gt;1)k(k&gt;1)个相互独立的哈希函数，保证在给定的空间、误判率下，完成元素判重的过程。</p>



<p>它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。</p>



<p>Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。</p>



<p>Hash存在一个冲突（碰撞）的问题，用同一个Hash得到的两个URL的值有可能相同。为了减少冲突，我们可以多引入几个Hash，如果通过其中的一个Hash值我们得出某元素不在集合中，那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时，才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。</p>



<p>Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。</p>



<h2 class="wp-block-heading">缓存击穿</h2>



<p>缓存击穿是指缓存中没有但数据库中有的数据（一般是缓存时间到期），这时由于并发用户特别多<strong>，同时读缓存没读到数据，又同时去数据库去取数据</strong>，引起数据库压力瞬间增大，造成过大压力。</p>



<p>和缓存雪崩不同的是，缓存击穿指并发查同一条数据，缓存雪崩是不同数据都过期了，很多数据都查不到从而查数据库。</p>



<p>解决方案</p>



<ol class="wp-block-list">
<li>设置热点数据永远不过期。</li>



<li>加互斥锁，互斥锁缓存预热</li>
</ol>



<p>缓存预热就是系统上线后，将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候，先查询数据库，然后再将数据缓存的问题！用户直接查询事先被预热的缓存数据！解决方案</p>



<ol class="wp-block-list">
<li>直接写个缓存刷新页面，上线时手工操作一下；</li>



<li>数据量不大，可以在项目启动的时候自动进行加载；</li>



<li>定时刷新缓存；</li>
</ol>



<h2 class="wp-block-heading">缓存降级</h2>



<p>当访问量剧增、服务出现问题（如响应时间慢或不响应）或非核心服务影响到核心流程的性能时，仍然需要保证服务还是可用的，即使是有损服务。系统可以根据一些关键数据进行自动降级，也可以配置开关实现人工降级。</p>



<p>缓存降级的最终目的是保证核心服务可用，即使是有损的。而且有些服务是无法降级的（如加入购物车、结算）。</p>



<p>在进行降级之前要对系统进行梳理，看看系统是不是可以丢卒保帅；从而梳理出哪些必须誓死保护，哪些可降级；比如可以参考日志级别设置预案：</p>



<ol class="wp-block-list">
<li>一般：比如有些服务偶尔因为网络抖动或者服务正在上线而超时，可以自动降级；</li>



<li>警告：有些服务在一段时间内成功率有波动（如在95~100%之间），可以自动降级或人工降级，并发送告警；</li>



<li>错误：比如可用率低于90%，或者数据库连接池被打爆了，或者访问量突然猛增到系统能承受的最大阀值，此时可以根据情况自动降级或者人工降级；</li>



<li>严重错误：比如因为特殊原因数据错误了，此时需要紧急人工降级。</li>
</ol>



<p>服务降级的目的，是为了防止Redis服务故障，导致数据库跟着一起发生雪崩问题。因此，对于不重要的缓存数据，可以采取服务降级策略，例如一个比较常见的做法就是，Redis出现问题，不去数据库查询，而是直接返回默认值给用户。</p>



<h2 class="wp-block-heading">降级的方案有哪些？（*）</h2>



<h2 class="wp-block-heading">热点数据和冷数据</h2>



<p>热点数据，缓存才有价值，对于冷数据而言，大部分数据可能还没有再次访问到就已经被挤出内存，不仅占用内存，而且价值不大。频繁修改的数据，看情况考虑使用缓存对于热点数据，比如我们的某IM产品，生日祝福模块，当天的寿星列表，缓存以后可能读取数十万次。再举个例子，某导航产品，我们将导航信息，缓存以后可能读取数百万次。<br>数据更新前至少读取两次，缓存才有意义。这个是最基本的策略，如果缓存还没有起作用就失效了，那就没有太大价值了。<br>那存不存在，修改频率很高，但是又不得不考虑缓存的场景呢？<br>有！比如，这个读取接口对数据库的压力很大，但是又是热点数据，这个时候就需要考虑通过缓存手段，减少数据库的压力，比如我们的某助手产品的，点赞数，收藏数，分享数等是非常典型的热点数据，但是又不断变化，此时就需要将数据同步保存到Redis缓存，减少数据库压力。</p>



<h2 class="wp-block-heading">缓存热点key</h2>



<p>缓存中的一个Key(比如一个促销商品)，在某个时间点过期的时候，恰好在这个时间点对这个Key有大量的并发请求过来，这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端DB压垮。</p>



<p>解决方案对缓存查询加锁，如果KEY不存在，就加锁，然后查DB入缓存，然后解锁；其他进程如果发现有锁就等待，然后等解锁后返回数据或者进入DB查询常用工具</p>



<p>Redis支持的Java客户端都有哪些？官方推荐用哪个？</p>



<p>Redisson、Jedis、lettuce等等，官方推荐使用Redisson。</p>



<h2 class="wp-block-heading">Redis和Redisson有什么关系？</h2>



<p>Redisson是一个高级的分布式协调Redis客服端，能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap,ScoredSortedSet, SortedSet, Map, ConcurrentMap, List,ListMultimap,Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock,ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe,HyperLogLog)。</p>



<h2 class="wp-block-heading">Jedis与Redisson对比有什么优缺点？</h2>



<p>Jedis是Redis的Java实现的客户端，其API提供了比较全面的Redis命令的支持；Redisson实现了分布式和可扩展的Java数据结构，和Jedis相比，功能较为简单，不支持字符串操作，不支持排序、事务、管道、分区等Redis特性。</p>



<p>Redisson的宗旨是促进使用者对Redis的关注分离，从而让使用者能够将精力更集中地放在处理业务逻辑上。</p>



<h2 class="wp-block-heading">Redis与Memcached的区别</h2>



<p>两者都是非关系型内存键值数据库，现在公司一般都是用 Redis 来实现缓存，而且 Redis 自身也越来越强大了！Redis 与 Memcached 主要有以下不同：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>对比参数</strong></td><td><strong>Redis</strong></td><td><strong>Memcac hed</strong></td></tr><tr><td>类型</td><td>1. 支持内存 2. 非关系型数据库</td><td>1. 支持内存 2. 键值对形式 3. 缓存形式</td></tr><tr><td>数据存储类型</td><td>1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗称 ZSet】</td><td>1. 文本型 2. 二进制类型</td></tr><tr><td>查询【操作】类型</td><td>1. 批量操作 2. 事务支持 3. 每个类型不同的 CRUD</td><td>1.常用的 CRUD 2. 少量的其他命令</td></tr><tr><td>附加功能</td><td>1. 发布/订阅模式 2. 主从分区 3. 序列化 支持 4. 脚本支持 【Lua脚本】</td><td>1. 多线程服务支持</td></tr><tr><td>网络IO模型</td><td>1. 单线程的多路 IO 复用模型</td><td>1. 多线程，非阻塞IO模式</td></tr><tr><td>事件库</td><td>自封转简易事件库 AeEvent</td><td>贵族血统的 LibEvent 事件库</td></tr><tr><td><strong>持久化支持</strong></td><td><strong>1. RDB 2. AOF</strong></td><td><strong>不支持</strong></td></tr><tr><td><strong>集群模式</strong></td><td><strong>原生支持 cluster 模式，可以实现主从复制，读写分离</strong></td><td>没有原生的集群模式，需要依靠客户端来实现往集群中分片写入数据</td></tr><tr><td>内存管理机制</td><td>在 Redis 中，并不是所有数据都一直存储在内存中，可以将一些很久没用 的 value 交换到磁盘</td><td>Memcach ed 的数据则会一直在内存中， Memcach ed 将内存分割成特定长度的块来存储数据，以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高，例如块的大小为 128 bytes，只存储 100 bytes 的数据，那么剩下的 28 bytes 就浪费掉了。</td></tr><tr><td>适用场景</td><td>复杂数据结构，有持久化，高可用需求，value 存储内容较大</td><td>纯key- value，数据量非常大，并发量非常大的业务</td></tr></tbody></table></figure>



<p><br>(1) memcached所有的值均是简单的字符串，redis作为其替代者，支持更为丰富的数据类型</p>



<p>(2) redis的速度比memcached快很多</p>



<p>(3) redis可以持久化其数据</p>



<p>如何保证缓存与数据库双写时的数据一致性？<br>你只要用缓存，就可能会涉及到缓存与数据库双存储双写，你只要是双写，就一定会有数据一致性的问题，那么你如何解决一致性问题？一般来说，就是如果你的系统不是严格要求缓存+数据库必须一致性的话，缓存可以稍微的跟数据库偶尔有不一致的情况，最好不要做这个方案，读请求和写请求串行化，串到一个内存队列里去，这样就可以保证一定不会出现不一致的情况串行化之后，就会导致系统的吞吐量会大幅度的降低，用比正常情况下多几倍的机器去支撑线上的一个请求。</p>



<p>还有一种方式就是可能会暂时产生不一致的情况，但是发生的几率特别小，就是先更新数据库，然后再删除缓存。</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>问题场景</strong></td><td><strong>描述</strong></td><td><strong>解决</strong></td></tr><tr><td>先写缓存，再写数据库，缓存写成功，数据库写失败</td><td>缓存写成功，但写数据库失败或者响应延迟，则下次读取（并发读）缓存时，就出现脏读</td><td>这个写缓存的方式，本身就是错误的，需要改为先写数据库，把旧缓存置为失效；读取数据的时候，如果缓存不存在，则读取数据库再写缓存</td></tr><tr><td>先写数据库，再写缓存，数据库写成功，缓存写失败</td><td>写数据库成功，但写缓存失败，则下次读取（并发读）缓存时，则读不到数据</td><td>缓存使用时，假如读缓存失败，先读数据库，再回写缓存的方式实现</td></tr><tr><td>需要缓存异步刷新</td><td>指数据库操作和写缓存不在一个操作步骤中，比如在分布式场景下，无法做到同时写缓存或需要异步刷新（补救措施）时候</td><td>确定哪些数据适合此类场景，根据经验值确定合理的数据不一致时间，用户数据刷新的时间间隔</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Redis常见性能问题和解决方案？</h2>



<ul class="wp-block-list">
<li>Master最好不要做任何持久化工作，包括内存快照和AOF日志文件，特别是不要启用内存快照做持久化。</li>



<li>如果数据比较关键，某个Slave开启AOF备份数据，策略为每秒同步一次。</li>



<li>为了主从复制的速度和连接的稳定性，Slave和Master 好在同一个局域网内。</li>



<li>尽量避免在压力较大的主库上增加从库</li>



<li>Master调用BGREWRITEAOF重写AOF文件，AOF在重写的时候会占大量的CPU和内存资源，导致服务load过高，出现短暂服务暂停现象。</li>



<li>为了Master的稳定性，主从复制不要用图状结构，用单向链表结构更稳定，即主从关系为：Master&lt;–Slave1&lt;–Slave2&lt;–Slave3…，这样的结构也方便解决单点故障问题，实现Slave对Master的替换，也即，如果Master挂了，可以立马启用Slave1做Master，其他不变。</li>
</ul>



<h2 class="wp-block-heading">Redis官方为什么不提供Windows版本？</h2>



<p>因为目前Linux版本已经相当稳定，而且用户量很大，无需开发windows版本，反而会带来兼容性等问题。</p>



<h2 class="wp-block-heading">一个字符串类型的值能存储最大容量是多少？</h2>



<p>512M</p>



<p>Redis如何做大量数据插入？ Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。</p>



<h2 class="wp-block-heading">假如Redis里面有1亿个key，其中有10w个key是以某个固定的已知的前缀开头的，如果将它们全部找出来？</h2>



<p>使用keys指令可以扫出指定模式的key列表。</p>



<p>对方接着追问：如果这个redis正在给线上的业务提供服务，那使用keys指令会有什么问题？这个时候你要回答redis关键的一个特性：redis的单线程的。keys指令会导致线程阻塞一段时间，线上服务会停顿，直到指令执行完毕，服务才能恢复。</p>



<p>这个时候可以使用scan指令，scan指令可以无阻塞的提取出指定模式的key列表，但是会有一定的重复概率，在客户端做一次去重就可以了，但是整体所花费的时间会比直接用keys指令长。</p>



<h2 class="wp-block-heading">Redis如何实现延时队列</h2>



<p>使用sortedset，使用时间戳做score, 消息内容作为key,调用zadd来生产消息，消费者使用zrangbyscore获取n秒之前的数据做轮询处理。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2026/03/04/353/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">353</post-id>	</item>
		<item>
		<title>【Java面试】基础</title>
		<link>https://blog.lhyshome.com/2025/07/07/316/</link>
					<comments>https://blog.lhyshome.com/2025/07/07/316/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Mon, 07 Jul 2025 08:57:54 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=316</guid>

					<description><![CDATA[Java 基础 1. JDK 和 JRE 有什么区别？ 具体来说 JDK 其实包含了 JRE，同时还包含了编译… <span class="read-more"><a href="https://blog.lhyshome.com/2025/07/07/316/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading" id="ec6e2"><strong>Java 基础</strong></h1>



<h2 class="wp-block-heading" id="aes7e"><strong>1. JDK 和 JRE 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>JDK：Java Development Kit 的简称，Java 开发工具包，提供了 Java 的开发环境和运行环境。</li>



<li>JRE：Java Runtime Environment 的简称，Java 运行环境，为 Java 的运行提供了所需环境。</li>
</ul>



<p>具体来说 JDK 其实包含了 JRE，同时还包含了编译 Java 源码的编译器 Javac，还包含了很多 Java 程序调试和分析的工具。简单来说：如果你需要运行 Java 程序，只需安装 JRE 就可以了，如果你需要编写 Java 程序，需要安装 JDK。</p>



<h2 class="wp-block-heading" id="chqqj"><strong>2. == 和 equals 的区别是什么？</strong></h2>



<p><strong>「== 解读」</strong></p>



<p>对于基本类型和引用类型 == 的作用效果是不同的，如下所示：</p>



<ul class="wp-block-list">
<li>基本类型：比较的是值是否相同；</li>



<li>引用类型：比较的是引用是否相同；</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true</code></pre>



<p>代码解读：因为 x 和 y 指向的是同一个引用，所以 == 也是 true，而 new String()方法则重写开辟了内存空间，所以 == 结果为 false，而 equals 比较的一直是值，所以结果都为 true。</p>



<p><strong>「equals 解读」</strong></p>



<p>equals 本质上就是 ==，只不过 String 和 Integer 等重写了 equals 方法，把它变成了值比较。看下面的代码就明白了。</p>



<p>首先来看默认情况下 equals 比较一个有相同值的对象，代码如下：</p>



<pre class="wp-block-code"><code>classCat{
    publicCat(String name){
        this.name = name;
    }

    private String name;

    public String getName(){
        return name;
    }

    publicvoidsetName(String name){
        this.name = name;
    }
}

Cat c1 = new Cat("王磊");
Cat c2 = new Cat("王磊");
System.out.println(c1.equals(c2)); // false</code></pre>



<p>输出结果出乎我们的意料，竟然是 false？这是怎么回事，看了 equals 源码就知道了，源码如下：</p>



<pre class="wp-block-code"><code>publicbooleanequals(Object obj){
        return (this == obj);
}</code></pre>



<p>原来 equals 本质上就是 ==。</p>



<p>那问题来了，两个相同值的 String 对象，为什么返回的是 true？代码如下：</p>



<pre class="wp-block-code"><code>String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true</code></pre>



<p>同样的，当我们进入 String 的 equals 方法，找到了答案，代码如下：</p>



<pre class="wp-block-code"><code>publicbooleanequals(Object anObject){
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1&#91;] = value;
            char v2&#91;] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1&#91;i] != v2&#91;i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}</code></pre>



<p>原来是 String 重写了 Object 的 equals 方法，把引用比较改成了值比较。</p>



<p><strong>「总结」</strong> ：== 对于基本类型来说是值比较，对于引用类型来说是比较的是引用；而 equals 默认情况下是引用比较，只是很多类重新了 equals 方法，比如 String、Integer 等把它变成了值比较，所以一般情况下 equals 比较的是值是否相等。</p>



<h2 class="wp-block-heading" id="ff96r"><strong>3. 两个对象的 hashCode() 相同，则 equals() 也一定为 true，对吗？</strong></h2>



<p>不对，两个对象的 hashCode() 相同，equals() 不一定 true。</p>



<p>代码示例：</p>



<pre class="wp-block-code"><code>String str1 = "通话";
String str2 = "重地";
System. out. println(String. format("str1：%d | str2：%d",  str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));</code></pre>



<p>执行的结果：</p>



<pre class="wp-block-code"><code>str1：1179395 | str2：1179395

false</code></pre>



<p>代码解读：很显然“通话”和“重地”的 hashCode() 相同，然而 equals() 则为 false，因为在散列表中，hashCode() 相等即两个键值对的哈希值相等，然而哈希值相等，并不一定能得出键值对相等。</p>



<h2 class="wp-block-heading" id="3l7rh"><strong>4. final 在 Java 中有什么作用？</strong></h2>



<ul class="wp-block-list">
<li>final 修饰的类叫最终类，该类不能被继承。</li>



<li>final 修饰的方法不能被重写。</li>



<li>final 修饰的变量叫常量，常量必须初始化，初始化之后值就不能被修改。</li>
</ul>



<h2 class="wp-block-heading" id="ds89n"><strong>5. Java 中的 Math. round(-1. 5) 等于多少？</strong></h2>



<p>等于 -1，因为在数轴上取值时，中间值（0.5）向右取整，所以正 0.5 是往上取整，负 0.5 是直接舍弃。</p>



<h2 class="wp-block-heading" id="da68n"><strong>6. String 属于基础的数据类型吗？</strong></h2>



<p>String 不属于基础类型，基础类型有 8 种：byte、boolean、char、short、int、float、long、double，而 String 属于对象。</p>



<h2 class="wp-block-heading" id="70io6">7. Java 中操作字符串都有哪些类？它们之间有什么区别？</h2>



<p>操作字符串的类有：String、StringBuffer、StringBuilder。</p>



<p>String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象，每次操作都会生成新的 String 对象，然后将指针指向新的 String 对象，而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作，所以在经常改变字符串内容的情况下最好不要使用 String。</p>



<p>StringBuffer 和 StringBuilder 最大的区别在于，StringBuffer 是线程安全的，而 StringBuilder 是非线程安全的，但 StringBuilder 的性能却高于 StringBuffer，所以在单线程环境下推荐使用 StringBuilder，多线程环境下推荐使用 StringBuffer。</p>



<h2 class="wp-block-heading" id="25eoj"><strong>8. String str=”i”与 String str=new String(“i”)一样吗？</strong></h2>



<p>不一样，因为内存的分配方式不一样。String str=”i”的方式，Java 虚拟机会将其分配到常量池中；而 String str=new String(“i”) 则会被分到堆内存中。</p>



<h2 class="wp-block-heading" id="7k2lg"><strong>9. 如何将字符串反转？</strong></h2>



<p>使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。</p>



<p>示例代码：</p>



<pre class="wp-block-code"><code>// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba</code></pre>



<h2 class="wp-block-heading" id="bi58l"><strong>10. String 类的常用方法都有那些？</strong></h2>



<ul class="wp-block-list">
<li>indexOf()：返回指定字符的索引。</li>



<li>charAt()：返回指定索引处的字符。</li>



<li>replace()：字符串替换。</li>



<li>trim()：去除字符串两端空白。</li>



<li>split()：分割字符串，返回一个分割后的字符串数组。</li>



<li>getBytes()：返回字符串的 byte 类型数组。</li>



<li>length()：返回字符串长度。</li>



<li>toLowerCase()：将字符串转成小写字母。</li>



<li>toUpperCase()：将字符串转成大写字符。</li>



<li>substring()：截取字符串。</li>



<li>equals()：字符串比较。</li>
</ul>



<h2 class="wp-block-heading" id="8io15"><strong>11. 抽象类必须要有抽象方法吗？</strong></h2>



<p>不需要，抽象类不一定非要有抽象方法。</p>



<p>示例代码：</p>



<pre class="wp-block-code"><code>abstract class Cat{
    public static void sayHi(){
        System.out.println("hi~");
    }
}</code></pre>



<p>上面代码，抽象类并没有抽象方法但完全可以正常运行。</p>



<h2 class="wp-block-heading" id="cadrv"><strong>12. 普通类和抽象类有哪些区别？</strong></h2>



<ul class="wp-block-list">
<li>普通类不能包含抽象方法，抽象类可以包含抽象方法。</li>



<li>抽象类不能直接实例化，普通类可以直接实例化。</li>
</ul>



<h2 class="wp-block-heading" id="cmlug"><strong>13. 抽象类能使用 final 修饰吗？</strong></h2>



<p>不能，定义抽象类就是让其他类继承的，如果定义为 final 该类就不能被继承，这样彼此就会产生矛盾，所以 final 不能修饰抽象类，如下图所示，编辑器也会提示错误信息：</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="350" height="125" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image.png" alt="" class="wp-image-317" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image.png 350w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-300x107.png 300w" sizes="auto, (max-width: 350px) 100vw, 350px" /></figure>



<h2 class="wp-block-heading" id="2n09q"><strong>14. 接口和抽象类有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>实现：抽象类的子类使用 extends 来继承；接口必须使用 implements 来实现接口。</li>



<li>构造函数：抽象类可以有构造函数；接口不能有。</li>



<li>实现数量：类可以实现很多个接口；但是只能继承一个抽象类。</li>



<li>访问修饰符：接口中的方法默认使用 public 修饰；抽象类中的方法可以是任意访问修饰符。</li>
</ul>



<h2 class="wp-block-heading" id="8dlcf"><strong>15. Java 中 IO 流分为几种？</strong></h2>



<p>按功能来分：输入流（input）、输出流（output）。</p>



<p>按类型来分：字节流和字符流。</p>



<p>字节流和字符流的区别是：字节流按 8 位传输以字节为单位输入输出数据，字符流按 16 位传输以字符为单位输入输出数据。</p>



<h2 class="wp-block-heading" id="80rmf"><strong>16. BIO、NIO、AIO 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>BIO：Block IO 同步阻塞式 IO，就是我们平常使用的传统 IO，它的特点是模式简单使用方便，并发处理能力低。</li>



<li>NIO：Non IO 同步非阻塞 IO，是传统 IO 的升级，客户端和服务器端通过 Channel（通道）通讯，实现了多路复用。</li>



<li>AIO：Asynchronous IO 是 NIO 的升级，也叫 NIO2，实现了异步非堵塞 IO ，异步 IO 的操作基于事件和回调机制。</li>
</ul>



<h2 class="wp-block-heading" id="9q5um"><strong>17. Files的常用方法都有哪些？</strong></h2>



<ul class="wp-block-list">
<li>Files. exists()：检测文件路径是否存在。</li>



<li>Files. createFile()：创建文件。</li>



<li>Files. createDirectory()：创建文件夹。</li>



<li>Files. delete()：删除一个文件或目录。</li>



<li>Files. copy()：复制文件。</li>



<li>Files. move()：移动文件。</li>



<li>Files. size()：查看文件个数。</li>



<li>Files. read()：读取文件。</li>



<li>Files. write()：写入文件。</li>
</ul>



<h2 class="wp-block-heading" id="351s2"><strong>18. Java 容器都有哪些？</strong></h2>



<p>Java 容器分为 Collection 和 Map 两大类，其下又有很多子类，如下所示：</p>



<ul class="wp-block-list">
<li>Collection</li>



<li>List
<ul class="wp-block-list">
<li>ArrayList</li>



<li>LinkedList</li>



<li>Vector</li>



<li>Stack</li>
</ul>
</li>



<li>Set
<ul class="wp-block-list">
<li>HashSet</li>



<li>LinkedHashSet</li>



<li>TreeSet</li>
</ul>
</li>



<li>Map</li>



<li>HashMap
<ul class="wp-block-list">
<li>LinkedHashMap</li>
</ul>
</li>



<li>TreeMap</li>



<li>ConcurrentHashMap</li>



<li>Hashtable</li>
</ul>



<h2 class="wp-block-heading" id="1vklq"><strong>19. Collection 和 Collections 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>Collection 是一个集合接口，它提供了对集合对象进行基本操作的通用接口方法，所有集合都是它的子类，比如 List、Set 等。</li>



<li>Collections 是一个包装类，包含了很多静态方法，不能被实例化，就像一个工具类，比如提供的排序方法：Collections. sort(list)。</li>
</ul>



<h2 class="wp-block-heading" id="2o4dd"><strong>20. List、Set、Map 之间的区别是什么？</strong></h2>



<p>List、Set、Map 的区别主要体现在两个方面：元素是否有序、是否允许元素重复。</p>



<p>三者之间的区别，如下表：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="326" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-1024x326.png" alt="" class="wp-image-319" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-1024x326.png 1024w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-300x96.png 300w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-768x245.png 768w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-660x210.png 660w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-500x159.png 500w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2-800x255.png 800w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-2.png 1165w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="uqlc"><strong>21. HashMap 和 Hashtable 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>存储：HashMap 运行 key 和 value 为 null，而 Hashtable 不允许。</li>



<li>线程安全：Hashtable 是线程安全的，而 HashMap 是非线程安全的。</li>



<li>推荐使用：在 Hashtable 的类注释可以看到，Hashtable 是保留类不建议使用，推荐在单线程环境下使用 HashMap 替代，如果需要多线程使用则用 ConcurrentHashMap 替代。</li>
</ul>



<h2 class="wp-block-heading" id="8fisa"><strong>22. 如何决定使用 HashMap 还是 TreeMap？</strong></h2>



<p>对于在 Map 中插入、删除、定位一个元素这类操作，HashMap 是最好的选择，因为相对而言 HashMap 的插入会更快，但如果你要对一个 key 集合进行有序的遍历，那 TreeMap 是更好的选择。</p>



<h2 class="wp-block-heading" id="dfivu"><strong>23. 说一下 HashMap 的实现原理？</strong></h2>



<p>HashMap 基于 Hash 算法实现的，我们通过 put(key,value)存储，get(key)来获取。当传入 key 时，HashMap 会根据 key. hashCode() 计算出 hash 值，根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时，我们称之为 hash 冲突，HashMap 的做法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时，使用链表否则使用红黑树。</p>



<h2 class="wp-block-heading" id="k934"><strong>24. 说一下 HashSet 的实现原理？</strong></h2>



<p>HashSet 是基于 HashMap 实现的，HashSet 底层使用 HashMap 来保存所有元素，因此 HashSet 的实现比较简单，相关 HashSet 的操作，基本上都是直接调用底层 HashMap 的相关方法来完成，HashSet 不允许重复的值。</p>



<h2 class="wp-block-heading" id="5nfao"><strong>25. ArrayList 和 LinkedList 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>数据结构实现：ArrayList 是动态数组的数据结构实现，而 LinkedList 是双向链表的数据结构实现。</li>



<li>随机访问效率：ArrayList 比 LinkedList 在随机访问的时候效率要高，因为 LinkedList 是线性的数据存储方式，所以需要移动指针从前往后依次查找。</li>



<li>增加和删除效率：在非首尾的增加和删除操作，LinkedList 要比 ArrayList 效率要高，因为 ArrayList 增删操作要影响数组内的其他数据的下标。</li>
</ul>



<p>综合来说，在需要频繁读取集合中的元素时，更推荐使用 ArrayList，而在插入和删除操作较多时，更推荐使用 LinkedList。</p>



<h2 class="wp-block-heading" id="ab8cu"><strong>26. 如何实现数组和 List 之间的转换？</strong></h2>



<ul class="wp-block-list">
<li>数组转 List：使用 Arrays. asList(array) 进行转换。</li>



<li>List 转数组：使用 List 自带的 toArray() 方法。</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>// list to array
List&lt;String&gt; list = new ArrayList&lt;String&gt;();
list. add("王磊");
list. add("的博客");
list. toArray();
// array to list
String&#91;] array = new String&#91;]{"王磊","的博客"};
Arrays. asList(array);</code></pre>



<h2 class="wp-block-heading" id="gtki"><strong>27. ArrayList 和 Vector 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>线程安全：Vector 使用了 Synchronized 来实现线程同步，是线程安全的，而 ArrayList 是非线程安全的。</li>



<li>性能：ArrayList 在性能方面要优于 Vector。</li>



<li>扩容：ArrayList 和 Vector 都会根据实际的需要动态的调整容量，只不过在 Vector 扩容每次会增加 1 倍，而 ArrayList 只会增加 50%。</li>
</ul>



<h2 class="wp-block-heading" id="2e32l"><strong>28. Array 和 ArrayList 有何区别？</strong></h2>



<ul class="wp-block-list">
<li>Array 可以存储基本数据类型和对象，ArrayList 只能存储对象。</li>



<li>Array 是指定固定大小的，而 ArrayList 大小是自动扩展的。</li>



<li>Array 内置方法没有 ArrayList 多，比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。</li>
</ul>



<h2 class="wp-block-heading" id="43im8"><strong>29. 在 Queue 中 poll()和 remove()有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>相同点：都是返回第一个元素，并在队列中删除返回的对象。</li>



<li>不同点：如果没有元素 poll()会返回 null，而 remove()会直接抛出 NoSuchElementException 异常。</li>
</ul>



<p>代码示例：</p>



<pre class="wp-block-code"><code>Queue&lt;String&gt; queue = new LinkedList&lt;String&gt;();
queue. offer("string"); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());</code></pre>



<h2 class="wp-block-heading" id="9c3t8"><strong>30. 哪些集合类是线程安全的？</strong></h2>



<p>Vector、Hashtable、Stack 都是线程安全的，而像 HashMap 则是非线程安全的，不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现，它们也有了自己对应的线程安全类，比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。</p>



<h2 class="wp-block-heading" id="c0k5a"><strong>31. 迭代器 Iterator 是什么？</strong></h2>



<p>Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration，迭代器允许调用者在迭代过程中移除元素。</p>



<h2 class="wp-block-heading" id="efniq"><strong>32. Iterator 怎么使用？有什么特点？</strong></h2>



<p>Iterator 使用代码如下：</p>



<pre class="wp-block-code"><code>List&lt;String&gt; list = new ArrayList&lt;&gt;();
Iterator&lt;String&gt; it = list. iterator();
while(it. hasNext()){
  String obj = it. next();
  System. out. println(obj);
}</code></pre>



<p>Iterator 的特点是更加安全，因为它可以确保，在当前遍历的集合元素被更改的时候，就会抛出 ConcurrentModificationException 异常。</p>



<h2 class="wp-block-heading" id="91gaf"><strong>33. Iterator 和 ListIterator 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>Iterator 可以遍历 Set 和 List 集合，而 ListIterator 只能遍历 List。</li>



<li>Iterator 只能单向遍历，而 ListIterator 可以双向遍历（向前/后遍历）。</li>



<li>ListIterator 从 Iterator 接口继承，然后添加了一些额外的功能，比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。</li>
</ul>



<h2 class="wp-block-heading" id="83j4p"><strong>34. 怎么确保一个集合不能被修改？</strong></h2>



<p>可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合，这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。</p>



<p>示例代码如下：</p>



<pre class="wp-block-code"><code>List&lt;String&gt; list = new ArrayList&lt;&gt;();
list. add("x");
Collection&lt;String&gt; clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());</code></pre>



<h1 class="wp-block-heading" id="eobkt"><strong>多线程</strong></h1>



<h2 class="wp-block-heading" id="edb48"><strong>35. 并行和并发有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>并行：多个处理器或多核处理器同时处理多个任务。</li>



<li>并发：多个任务在同一个 CPU 核上，按细分的时间片轮流(交替)执行，从逻辑上来看那些任务是同时执行。</li>
</ul>



<p>如下图：</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="877" height="662" src="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3.png" alt="" class="wp-image-320" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3.png 877w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-300x226.png 300w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-768x580.png 768w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-660x498.png 660w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-500x377.png 500w, https://blog.lhyshome.com/wp-content/uploads/2025/07/image-3-800x604.png 800w" sizes="auto, (max-width: 877px) 100vw, 877px" /></figure>



<p>并发 = 两个队列和一台咖啡机。</p>



<p>并行 = 两个队列和两台咖啡机。</p>



<h2 class="wp-block-heading" id="9g1bh"><strong>36. 线程和进程的区别？</strong></h2>



<p>一个程序下至少有一个进程，一个进程下至少有一个线程，一个进程下也可以有多个线程来增加程序的执行速度。</p>



<h2 class="wp-block-heading" id="ejjmq"><strong>37. 守护线程是什么？</strong></h2>



<p>守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。</p>



<h2 class="wp-block-heading" id="29b3k"><strong>38. 创建线程有哪几种方式？</strong></h2>



<p>创建线程有三种方式：</p>



<ul class="wp-block-list">
<li>继承 Thread 重写 run 方法；</li>



<li>实现 Runnable 接口；</li>



<li>实现 Callable 接口。</li>
</ul>



<h2 class="wp-block-heading" id="6di6b"><strong>39. 说一下 runnable 和 callable 有什么区别？</strong></h2>



<p>runnable 没有返回值，callable 可以拿到有返回值，callable 可以看作是 runnable 的补充。</p>



<h2 class="wp-block-heading" id="4l8mo"><strong>40. 线程有哪些状态？</strong></h2>



<p>线程的状态：</p>



<ul class="wp-block-list">
<li>NEW 尚未启动</li>



<li>RUNNABLE 正在执行中</li>



<li>BLOCKED 阻塞的（被同步锁或者IO锁阻塞）</li>



<li>WAITING 永久等待状态</li>



<li>TIMED_WAITING 等待指定的时间重新被唤醒的状态</li>



<li>TERMINATED 执行完成</li>
</ul>



<h2 class="wp-block-heading" id="2h6cb"><strong>41. sleep() 和 wait() 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>类的不同：sleep() 来自 Thread，wait() 来自 Object。</li>



<li>释放锁：sleep() 不释放锁；wait() 释放锁。</li>



<li>用法不同：sleep() 时间到会自动恢复；wait() 可以使用 notify()/notifyAll()直接唤醒。</li>
</ul>



<h2 class="wp-block-heading" id="1gbva"><strong>42. notify()和 notifyAll()有什么区别？</strong></h2>



<p>notifyAll()会唤醒所有的线程，notify()之后唤醒一个线程。notifyAll() 调用后，会将全部线程由等待池移到锁池，然后参与锁的竞争，竞争成功则继续执行，如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程，具体唤醒哪一个线程由虚拟机控制。</p>



<h2 class="wp-block-heading" id="2tfuc"><strong>43. 线程的 run() 和 start() 有什么区别？</strong></h2>



<p>start() 方法用于启动线程，run() 方法用于执行线程的运行时代码。run() 可以重复调用，而 start() 只能调用一次。</p>



<h2 class="wp-block-heading" id="3kgbu"><strong>44. 创建线程池有哪几种方式？</strong></h2>



<p>线程池创建有七种方式，最核心的是最后一种：</p>



<ul class="wp-block-list">
<li>newSingleThreadExecutor()：它的特点在于工作线程数目被限制为 1，操作一个无界的工作队列，所以它保证了所有任务的都是被顺序执行，最多会有一个任务处于活动状态，并且不允许使用者改动线程池实例，因此可以避免其改变线程数目；</li>



<li>newCachedThreadPool()：它是一种用来处理大量短时间工作任务的线程池，具有几个鲜明特点：它会试图缓存线程并重用，当无缓存线程可用时，就会创建新的工作线程；如果线程闲置的时间超过 60 秒，则被终止并移出缓存；长时间闲置时，这种线程池，不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列；</li>



<li>newFixedThreadPool(int nThreads)：重用指定数目（nThreads）的线程，其背后使用的是无界的工作队列，任何时候最多有 nThreads 个工作线程是活动的。这意味着，如果任务数量超过了活动队列数目，将在工作队列中等待空闲线程出现；如果有工作线程退出，将会有新的工作线程被创建，以补足指定的数目 nThreads；</li>



<li>newSingleThreadScheduledExecutor()：创建单线程池，返回 ScheduledExecutorService，可以进行定时或周期性的工作调度；</li>



<li>newScheduledThreadPool(int corePoolSize)：和newSingleThreadScheduledExecutor()类似，创建的是个 ScheduledExecutorService，可以进行定时或周期性的工作调度，区别在于单一工作线程还是多个工作线程；</li>



<li>newWorkStealingPool(int parallelism)：这是一个经常被人忽略的线程池，Java 8 才加入这个创建方法，其内部会构建ForkJoinPool，利用Work-Stealing算法，并行地处理任务，不保证处理顺序；</li>



<li>ThreadPoolExecutor()：是最原始的线程池创建，上面1-3创建方式都是对ThreadPoolExecutor的封装。</li>
</ul>



<h2 class="wp-block-heading" id="1fdgd"><strong>45. 线程池都有哪些状态？</strong></h2>



<ul class="wp-block-list">
<li>RUNNING：这是最正常的状态，接受新的任务，处理等待队列中的任务。</li>



<li>SHUTDOWN：不接受新的任务提交，但是会继续处理等待队列中的任务。</li>



<li>STOP：不接受新的任务提交，不再处理等待队列中的任务，中断正在执行任务的线程。</li>



<li>TIDYING：所有的任务都销毁了，workCount 为 0，线程池的状态在转换为 TIDYING 状态时，会执行钩子方法 terminated()。</li>



<li>TERMINATED：terminated()方法结束后，线程池的状态就会变成这个。</li>
</ul>



<h2 class="wp-block-heading" id="c53r5"><strong>46. 线程池中 submit() 和 execute() 方法有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>execute()：只能执行 Runnable 类型的任务。</li>



<li>submit()：可以执行 Runnable 和 Callable 类型的任务。</li>
</ul>



<p>Callable 类型的任务可以获取执行的返回值，而 Runnable 执行无返回值。</p>



<h2 class="wp-block-heading" id="6apde"><strong>47. 在 Java 程序中怎么保证多线程的运行安全？</strong></h2>



<ul class="wp-block-list">
<li>方法一：使用安全类，比如 Java. util. concurrent 下的类。</li>



<li>方法二：使用自动锁 synchronized。</li>



<li>方法三：使用手动锁 Lock。</li>
</ul>



<p>手动锁 Java 示例代码如下：</p>



<pre class="wp-block-code"><code>Lock lock = new ReentrantLock();
lock. lock();
try {
    System. out. println("获得锁");
} catch (Exception e) {
    // TODO: handle exception
} finally {
    System. out. println("释放锁");
    lock. unlock();
}</code></pre>



<h2 class="wp-block-heading" id="avaeq"><strong>48. 多线程中 synchronized 锁升级的原理是什么？</strong></h2>



<p>synchronized 锁升级原理：在锁对象的对象头里面有一个 threadid 字段，在第一次访问的时候 threadid 为空，jvm 让其持有偏向锁，并将 threadid 设置为其线程 id，再次进入的时候会先判断 threadid 是否与其线程 id 一致，如果一致则可以直接使用此对象，如果不一致，则升级偏向锁为轻量级锁，通过自旋循环一定次数来获取锁，执行一定次数之后，如果还没有正常获取到要使用的对象，此时就会把锁从轻量级升级为重量级锁，此过程就构成了 synchronized 锁的升级。</p>



<p>锁的升级的目的：锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式，使用了偏向锁升级为轻量级锁再升级到重量级锁的方式，从而减低了锁带来的性能消耗。</p>



<h2 class="wp-block-heading" id="d60mn"><strong>49. 什么是死锁？</strong></h2>



<p>当线程 A 持有独占锁a，并尝试去获取独占锁 b 的同时，线程 B 持有独占锁 b，并尝试获取独占锁 a 的情况下，就会发生 AB 两个线程由于互相持有对方需要的锁，而发生的阻塞现象，我们称为死锁。</p>



<h2 class="wp-block-heading" id="bfapg"><strong>50. 怎么防止死锁？</strong></h2>



<ul class="wp-block-list">
<li>尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock)，设置超时时间，超时可以退出防止死锁。</li>



<li>尽量使用 Java. util. concurrent 并发类代替自己手写锁。</li>



<li>尽量降低锁的使用粒度，尽量不要几个功能用同一把锁。</li>



<li>尽量减少同步的代码块。</li>
</ul>



<h2 class="wp-block-heading" id="8ms48"><strong>51. ThreadLocal 是什么？有哪些使用场景？</strong></h2>



<p>ThreadLocal 为每个使用该变量的线程提供独立的变量副本，所以每一个线程都可以独立地改变自己的副本，而不会影响其它线程所对应的副本。</p>



<p>ThreadLocal 的经典使用场景是数据库连接和 session 管理等。</p>



<h2 class="wp-block-heading" id="2pog2"><strong>52. 说一下 synchronized 底层实现原理？</strong></h2>



<p>synchronized 是由一对 monitorenter/monitorexit 指令实现的，monitor 对象是同步的基本实现单元。在 Java 6 之前，monitor 的实现完全是依靠操作系统内部的互斥锁，因为需要进行用户态到内核态的切换，所以同步操作是一个无差别的重量级操作，性能也很低。但在 Java 6 的时候，Java 虚拟机 对此进行了大刀阔斧地改进，提供了三种不同的 monitor 实现，也就是常说的三种不同的锁：偏向锁（Biased Locking）、轻量级锁和重量级锁，大大改进了其性能。</p>



<h2 class="wp-block-heading" id="7vgs8"><strong>53. synchronized 和 volatile 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>volatile 是变量修饰符；synchronized 是修饰类、方法、代码段。</li>



<li>volatile 仅能实现变量的修改可见性，不能保证原子性；而 synchronized 则可以保证变量的修改可见性和原子性。</li>



<li>volatile 不会造成线程的阻塞；synchronized 可能会造成线程的阻塞。</li>
</ul>



<h2 class="wp-block-heading" id="a7324"><strong>54. synchronized 和 Lock 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>synchronized 可以给类、方法、代码块加锁；而 lock 只能给代码块加锁。</li>



<li>synchronized 不需要手动获取锁和释放锁，使用简单，发生异常会自动释放锁，不会造成死锁；而 lock 需要自己加锁和释放锁，如果使用不当没有 unLock()去释放锁就会造成死锁。</li>



<li>通过 Lock 可以知道有没有成功获取锁，而 synchronized 却无法办到。</li>
</ul>



<h2 class="wp-block-heading" id="b7j3u"><strong>55. synchronized 和 ReentrantLock 区别是什么？</strong></h2>



<p>synchronized 早期的实现比较低效，对比 ReentrantLock，大多数场景性能都相差较大，但是在 Java 6 中对 synchronized 进行了非常多的改进。</p>



<p>主要区别如下：</p>



<ul class="wp-block-list">
<li>ReentrantLock 使用起来比较灵活，但是必须有释放锁的配合动作；</li>



<li>ReentrantLock 必须手动获取与释放锁，而 synchronized 不需要手动释放和开启锁；</li>



<li>ReentrantLock 只适用于代码块锁，而 synchronized 可用于修饰方法、代码块等。</li>
</ul>



<h2 class="wp-block-heading" id="4shpd"><strong>56. 说一下 atomic 的原理？</strong></h2>



<p>atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。</p>



<h1 class="wp-block-heading" id="fsmil"><strong>反射</strong></h1>



<h2 class="wp-block-heading" id="4cvsb"><strong>57. 什么是反射？</strong></h2>



<p>反射是在运行状态中，对于任意一个类，都能够知道这个类的所有属性和方法；对于任意一个对象，都能够调用它的任意一个方法和属性；这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。</p>



<h2 class="wp-block-heading" id="8obi5"><strong>58. 什么是 Java 序列化？什么情况下需要序列化？</strong></h2>



<p>Java 序列化是为了保存各种对象在内存中的状态，并且可以把保存的对象状态再读出来。</p>



<p>以下情况需要使用 Java 序列化：</p>



<ul class="wp-block-list">
<li>想把的内存中的对象状态保存到一个文件中或者数据库中时候；</li>



<li>想用套接字在网络上传送对象的时候；</li>



<li>想通过RMI（远程方法调用）传输对象的时候。</li>
</ul>



<h2 class="wp-block-heading" id="f64rm"><strong>59. 动态代理是什么？有哪些应用？</strong></h2>



<p>动态代理是运行时动态生成代理类。</p>



<p>动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc，Java注解对象获取等。</p>



<h2 class="wp-block-heading" id="5ch7r"><strong>60. 怎么实现动态代理？</strong></h2>



<p>JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的，而 cglib 是基于继承当前类的子类实现的。</p>



<h1 class="wp-block-heading" id="7fg0d"><strong>对象拷贝</strong></h1>



<h2 class="wp-block-heading" id="7j8u5"><strong>61. 为什么要使用克隆？</strong></h2>



<p>克隆的对象可能包含一些已经修改过的属性，而 new 出来的对象的属性都还是初始化时候的值，所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。</p>



<h2 class="wp-block-heading" id="8r4ca"><strong>62. 如何实现对象克隆？</strong></h2>



<ul class="wp-block-list">
<li>实现 Cloneable 接口并重写 Object 类中的 clone() 方法。</li>



<li>实现 Serializable 接口，通过对象的序列化和反序列化实现克隆，可以实现真正的深度克隆。</li>
</ul>



<h2 class="wp-block-heading" id="ito0"><strong>63. 深拷贝和浅拷贝区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>浅克隆：当对象被复制时只复制它本身和其中包含的值类型的成员变量，而引用类型的成员对象并没有复制。</li>



<li>深克隆：除了对象本身被复制外，对象所包含的所有成员变量也将复制。</li>
</ul>



<h2 class="wp-block-heading" id="8vl6v"><strong>Java Web</strong></h2>



<h2 class="wp-block-heading" id="brsa7"><strong>64. JSP 和 servlet 有什么区别？</strong></h2>



<p>JSP 是 servlet 技术的扩展，本质上就是 servlet 的简易方式。servlet 和 JSP 最主要的不同点在于，servlet 的应用逻辑是在 Java 文件中，并且完全从表示层中的 html 里分离开来，而 JSP 的情况是 Java 和 html 可以组合成一个扩展名为 JSP 的文件。JSP 侧重于视图，servlet 主要用于控制逻辑。</p>



<h2 class="wp-block-heading" id="5u3id"><strong>65. JSP 有哪些内置对象？作用分别是什么？</strong></h2>



<p>JSP 有 9 大内置对象：</p>



<ul class="wp-block-list">
<li>request：封装客户端的请求，其中包含来自 get 或 post 请求的参数；</li>



<li>response：封装服务器对客户端的响应；</li>



<li>pageContext：通过该对象可以获取其他对象；</li>



<li>session：封装用户会话的对象；</li>



<li>application：封装服务器运行环境的对象；</li>



<li>out：输出服务器响应的输出流对象；</li>



<li>config：Web 应用的配置对象；</li>



<li>page：JSP 页面本身（相当于 Java 程序中的 this）；</li>



<li>exception：封装页面抛出异常的对象。</li>
</ul>



<h2 class="wp-block-heading" id="7r5ae"><strong>66. 说一下 JSP 的 4 种作用域？</strong></h2>



<ul class="wp-block-list">
<li>page：代表与一个页面相关的对象和属性。</li>



<li>request：代表与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面，涉及多个 Web 组件；需要在页面显示的临时数据可以置于此作用域。</li>



<li>session：代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。</li>



<li>application：代表与整个 Web 应用程序相关的对象和属性，它实质上是跨越整个 Web 应用程序，包括多个页面、请求和会话的一个全局作用域。</li>
</ul>



<h2 class="wp-block-heading" id="1j8re"><strong>67. session 和 cookie 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>存储位置不同：session 存储在服务器端；cookie 存储在浏览器端。</li>



<li>安全性不同：cookie 安全性一般，在浏览器存储，可以被伪造和修改。</li>



<li>容量和个数限制：cookie 有容量限制，每个站点下的 cookie 也有个数限制。</li>



<li>存储的多样性：session 可以存储在 Redis 中、数据库中、应用程序中；而 cookie 只能存储在浏览器中。</li>
</ul>



<h2 class="wp-block-heading" id="e1dkt"><strong>68. 说一下 session 的工作原理？</strong></h2>



<p>session 的工作原理是客户端登录完成之后，服务器会创建对应的 session，session 创建完之后，会把 session 的 id 发送给客户端，客户端再存储到浏览器中。这样客户端每次访问服务器时，都会带着 sessionid，服务器拿到 sessionid 之后，在内存找到与之对应的 session 这样就可以正常工作了。</p>



<h2 class="wp-block-heading" id="ckf7j"><strong>69. 如果客户端禁止 cookie 能实现 session 还能用吗？</strong></h2>



<p>可以用，session 只是依赖 cookie 存储 sessionid，如果 cookie 被禁用了，可以使用 url 中添加 sessionid 的方式保证 session 能正常使用。</p>



<h2 class="wp-block-heading" id="7vace"><strong>70. spring mvc 和 struts 的区别是什么？</strong></h2>



<ul class="wp-block-list">
<li>拦截级别：struts2 是类级别的拦截；spring mvc 是方法级别的拦截。</li>



<li>数据独立性：spring mvc 的方法之间基本上独立的，独享 request 和 response 数据，请求数据通过参数获取，处理结果通过 ModelMap 交回给框架，方法之间不共享变量；而 struts2 虽然方法之间也是独立的，但其所有 action 变量是共享的，这不会影响程序运行，却给我们编码和读程序时带来了一定的麻烦。</li>



<li>拦截机制：struts2 有以自己的 interceptor 机制，spring mvc 用的是独立的 aop 方式，这样导致struts2 的配置文件量比 spring mvc 大。</li>



<li>对 ajax 的支持：spring mvc 集成了ajax，所有 ajax 使用很方便，只需要一个注解 @ResponseBody 就可以实现了；而 struts2 一般需要安装插件或者自己写代码才行。</li>
</ul>



<h2 class="wp-block-heading" id="1hnfi"><strong>71. 如何避免 SQL 注入？</strong></h2>



<ul class="wp-block-list">
<li>使用预处理 PreparedStatement。</li>



<li>使用正则表达式过滤掉字符中的特殊字符。</li>
</ul>



<h2 class="wp-block-heading" id="8ldgu"><strong>72. 什么是 XSS 攻击，如何避免？</strong></h2>



<p>XSS 攻击：即跨站脚本攻击，它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码（css 代码、Javascript 代码等），当用户浏览该页面时，嵌入其中的脚本代码会被执行，从而达到恶意攻击用户的目的，如盗取用户 cookie、破坏页面结构、重定向到其他网站等。</p>



<p>预防 XSS 的核心是必须对输入的数据做过滤处理。</p>



<h2 class="wp-block-heading" id="espi7"><strong>73. 什么是 CSRF 攻击，如何避免？</strong></h2>



<p>CSRF：Cross-Site Request Forgery（中文：跨站请求伪造），可以理解为攻击者盗用了你的身份，以你的名义发送恶意请求，比如：以你名义发送邮件、发消息、购买商品，虚拟货币转账等。</p>



<p>防御手段：</p>



<ul class="wp-block-list">
<li>验证请求来源地址；</li>



<li>关键操作添加验证码；</li>



<li>在请求地址添加 token 并验证。</li>
</ul>



<h2 class="wp-block-heading" id="ammj6"><strong>异常</strong></h2>



<h2 class="wp-block-heading" id="6do6r"><strong>74. throw 和 throws 的区别？</strong></h2>



<ul class="wp-block-list">
<li>throw：是真实抛出一个异常。</li>



<li>throws：是声明可能会抛出一个异常。</li>
</ul>



<h2 class="wp-block-heading" id="1c39e"><strong>75. final、finally、finalize 有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>final：是修饰符，如果修饰类，此类不能被继承；如果修饰方法和变量，则表示此方法和此变量不能在被改变，只能使用。</li>



<li>finally：是 try{} catch{} finally{} 最后一部分，表示不论发生任何情况都会执行，finally 部分可以省略，但如果 finally 部分存在，则一定会执行 finally 里面的代码。</li>



<li>finalize：是 Object 类的一个方法，在垃圾收集器执行的时候会调用被回收对象的此方法。</li>
</ul>



<h2 class="wp-block-heading" id="rq9f"><strong>76. try-catch-finally 中哪个部分可以省略？</strong></h2>



<p>try-catch-finally 其中 catch 和 finally 都可以被省略，但是不能同时省略，也就是说有 try 的时候，必须后面跟一个 catch 或者 finally。</p>



<h2 class="wp-block-heading" id="c2a5k"><strong>77. try-catch-finally 中，如果 catch 中 return 了，finally 还会执行吗？</strong></h2>



<p>finally 一定会执行，即使是 catch 中 return 了，catch 中的 return 会等 finally 中的代码执行完之后，才会执行。</p>



<h2 class="wp-block-heading" id="eup5i"><strong>78. 常见的异常类有哪些？</strong></h2>



<ul class="wp-block-list">
<li>NullPointerException 空指针异常</li>



<li>ClassNotFoundException 指定类不存在</li>



<li>NumberFormatException 字符串转换为数字异常</li>



<li>IndexOutOfBoundsException 数组下标越界异常</li>



<li>ClassCastException 数据类型转换异常</li>



<li>FileNotFoundException 文件未找到异常</li>



<li>NoSuchMethodException 方法不存在异常</li>



<li>IOException IO 异常</li>



<li>SocketException Socket 异常</li>
</ul>



<h2 class="wp-block-heading" id="bm75k"><strong>网络</strong></h2>



<h2 class="wp-block-heading" id="b15ln"><strong>79. http 响应码 301 和 302 代表的是什么？有什么区别？</strong></h2>



<p>301：永久重定向。</p>



<p>302：暂时重定向。</p>



<p>它们的区别是，301 对搜索引擎优化（SEO）更加有利；302 有被提示为网络拦截的风险。</p>



<h2 class="wp-block-heading" id="jjrp"><strong>80. forward 和 redirect 的区别？</strong></h2>



<p>forward 是转发 和 redirect 是重定向：</p>



<ul class="wp-block-list">
<li>地址栏 url 显示：foward url 不会发生改变，redirect url 会发生改变；</li>



<li>数据共享：forward 可以共享 request 里的数据，redirect 不能共享；</li>



<li>效率：forward 比 redirect 效率高。</li>
</ul>



<h2 class="wp-block-heading" id="c6muv"><strong>81. 简述 tcp 和 udp的区别？</strong></h2>



<p>tcp 和 udp 是 OSI 模型中的运输层中的协议。tcp 提供可靠的通信传输，而 udp 则常被用于让广播和细节控制交给应用的通信传输。</p>



<p>两者的区别大致如下：</p>



<ul class="wp-block-list">
<li>tcp 面向连接，udp 面向非连接即发送数据前不需要建立链接；</li>



<li>tcp 提供可靠的服务（数据传输），udp 无法保证；</li>



<li>tcp 面向字节流，udp 面向报文；</li>



<li>tcp 数据传输慢，udp 数据传输快；</li>
</ul>



<h2 class="wp-block-heading" id="5a9jv"><strong>82. tcp 为什么要三次握手，两次不行吗？为什么？</strong></h2>



<p>如果采用两次握手，那么只要服务器发出确认数据包就会建立连接，但由于客户端此时并未响应服务器端的请求，那此时服务器端就会一直在等待客户端，这样服务器端就白白浪费了一定的资源。若采用三次握手，服务器端没有收到来自客户端的再此确认，则就会知道客户端并没有要求建立请求，就不会浪费服务器的资源。</p>



<h2 class="wp-block-heading" id="ea13g"><strong>83. 说一下 tcp 粘包是怎么产生的？</strong></h2>



<p>tcp 粘包可能发生在发送端或者接收端，分别来看两端各种产生粘包的原因：</p>



<ul class="wp-block-list">
<li>发送端粘包：发送端需要等缓冲区满才发送出去，造成粘包；</li>



<li>接收方粘包：接收方不及时接收缓冲区的包，造成多个包接收。</li>
</ul>



<h2 class="wp-block-heading" id="91q2n"><strong>84. OSI 的七层模型都有哪些？</strong></h2>



<ul class="wp-block-list">
<li>物理层：利用传输介质为数据链路层提供物理连接，实现比特流的透明传输。</li>



<li>数据链路层：负责建立和管理节点间的链路。</li>



<li>网络层：通过路由选择算法，为报文或分组通过通信子网选择最适当的路径。</li>



<li>传输层：向用户提供可靠的端到端的差错和流量控制，保证报文的正确传输。</li>



<li>会话层：向两个实体的表示层提供建立和使用连接的方法。</li>



<li>表示层：处理用户信息的表示问题，如编码、数据格式转换和加密解密等。</li>



<li>应用层：直接向用户提供服务，完成用户希望在网络上完成的各种工作。</li>
</ul>



<h2 class="wp-block-heading" id="a9s5m"><strong>85. get 和 post 请求有哪些区别？</strong></h2>



<ul class="wp-block-list">
<li>get 请求会被浏览器主动缓存，而 post 不会。</li>



<li>get 传递参数有大小限制，而 post 没有。</li>



<li>post 参数传输更安全，get 的参数会明文限制在 url 上，post 不会。</li>
</ul>



<h2 class="wp-block-heading" id="fmft6"><strong>86. 如何实现跨域？</strong></h2>



<p>实现跨域有以下几种方案：</p>



<ul class="wp-block-list">
<li>服务器端运行跨域 设置 CORS 等于 *；</li>



<li>在单个接口使用注解 @CrossOrigin 运行跨域；</li>



<li>使用 jsonp 跨域；</li>
</ul>



<h2 class="wp-block-heading" id="9mpdt"><strong>87. 说一下 JSONP 实现原理？</strong></h2>



<p>jsonp：JSON with Padding，它是利用script标签的 src 连接可以访问不同源的特性，加载远程返回的“JS 函数”来执行的。</p>



<h1 class="wp-block-heading" id="b43jm"><strong>设计模式</strong></h1>



<h2 class="wp-block-heading" id="8tpj"><strong>88. 说一下你熟悉的设计模式？</strong></h2>



<ul class="wp-block-list">
<li>单例模式：保证被创建一次，节省系统开销。</li>



<li>工厂模式（简单工厂、抽象工厂）：解耦代码。</li>



<li>观察者模式：定义了对象之间的一对多的依赖，这样一来，当一个对象改变时，它的所有的依赖者都会收到通知并自动更新。</li>



<li>外观模式：提供一个统一的接口，用来访问子系统中的一群接口，外观定义了一个高层的接口，让子系统更容易使用。</li>



<li>模版方法模式：定义了一个算法的骨架，而将一些步骤延迟到子类中，模版方法使得子类可以在不改变算法结构的情况下，重新定义算法的步骤。</li>



<li>状态模式：允许对象在内部状态改变时改变它的行为，对象看起来好像修改了它的类。</li>
</ul>



<h2 class="wp-block-heading" id="1d30b"><strong>89. 简单工厂和抽象工厂有什么区别？</strong></h2>



<ul class="wp-block-list">
<li>简单工厂：用来生产同一等级结构中的任意产品，对于增加新的产品，无能为力。</li>



<li>工厂方法：用来生产同一等级结构中的固定产品，支持增加任意产品。</li>



<li>抽象工厂：用来生产不同产品族的全部产品，对于增加新的产品，无能为力；支持增加产品族。</li>
</ul>



<h2 class="wp-block-heading" id="ad4mj">内存泄漏和内存溢出</h2>



<p><strong>内存溢出：</strong>当 JVM 因为无法申请到足够的内存（可能已用完，也可能没有连续空间），并且垃圾收集器也无法回收更多内存时，就会抛出 <code>OutOfMemoryError</code></p>



<h3 class="wp-block-heading">常见的内存溢出场景</h3>



<ol start="1" class="wp-block-list">
<li><strong>Java 堆溢出</strong>
<ul class="wp-block-list">
<li>原因：对象过多（如无限创建对象）、对象过大、内存泄漏导致对象无法回收。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Java heap space</code></li>



<li>排查：通过 <code>jmap</code>、<code>MAT</code> 等工具分析堆转储文件，找出占用内存最多的对象及其引用链。</li>
</ul>
</li>



<li><strong>栈溢出</strong>
<ul class="wp-block-list">
<li>原因：线程请求的栈深度超过 JVM 允许的最大深度（如递归调用过深）；或栈帧过大。</li>



<li>错误信息：<code>java.lang.StackOverflowError</code>（通常不叫 OutOfMemoryError，但本质也是内存问题）。</li>



<li>排查：检查递归逻辑，减少局部变量大小，或通过 <code>-Xss</code> 增加栈大小。</li>
</ul>
</li>



<li><strong>元空间（Metaspace）溢出</strong>
<ul class="wp-block-list">
<li>原因：加载的类太多（如动态生成大量代理类）、字符串常量池过大（Java 8 后常量池在堆中，但类元数据在元空间）。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Metaspace</code></li>



<li>排查：调整 <code>-XX:MaxMetaspaceSize</code>，检查是否过度使用动态代理、CGLib 等。</li>
</ul>
</li>



<li><strong>直接内存溢出</strong>
<ul class="wp-block-list">
<li>原因：使用 NIO 的 <code>ByteBuffer.allocateDirect()</code> 分配了过多直接内存，且未释放。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: Direct buffer memory</code></li>



<li>排查：注意直接内存的回收依赖 <code>Cleaner</code> 和 GC，避免忘记释放。</li>
</ul>
</li>



<li><strong>GC 开销超过限制</strong>
<ul class="wp-block-list">
<li>原因：GC 花费了大量时间（如超过 98%）却只回收了极少内存，JVM 会抛出此异常。</li>



<li>错误信息：<code>java.lang.OutOfMemoryError: GC overhead limit exceeded</code></li>



<li>排查：这往往是内存泄漏或堆太小的前兆，需分析堆转储。</li>
</ul>
</li>
</ol>



<p><strong>内存泄漏：</strong>程序中已不再使用的对象，因为仍然被其他对象引用，导致垃圾收集器无法回收它们，这些对象无用地占据内存。随着时间推移，内存泄漏积累会耗尽可用内存，最终导致内存溢出。</p>



<h3 class="wp-block-heading">Java 中典型的内存泄漏场景</h3>



<ol start="1" class="wp-block-list">
<li><strong>静态集合类持有对象引用</strong>
<ul class="wp-block-list">
<li>例如：一个 <code>static List</code> 不断添加对象，但从未清理，这些对象生命周期与应用一致，无法回收。</li>



<li>解决：明确对象不再使用时从集合中移除，或使用 <code>WeakHashMap</code>。</li>
</ul>
</li>



<li><strong>未关闭的资源</strong>
<ul class="wp-block-list">
<li>数据库连接、文件流、网络连接等未正确关闭（<code>close()</code>），导致资源对象被 <code>finalizer</code> 或直接引用占用内存。</li>



<li>解决：使用 try-with-resources 自动关闭，或在 finally 中手动关闭。</li>
</ul>
</li>



<li><strong>内部类/匿名内部类持有外部类引用</strong>
<ul class="wp-block-list">
<li>非静态内部类会隐式持有外部类的引用，如果内部类对象生命周期较长，外部类对象就无法回收。</li>



<li>解决：将内部类改为静态内部类，或使用弱引用。</li>
</ul>
</li>



<li><strong>缓存设计不当</strong>
<ul class="wp-block-list">
<li>使用 <code>HashMap</code> 作为缓存，但未设置过期策略，缓存数据不断积累。</li>



<li>解决：使用 <code>WeakHashMap</code>、<code>LinkedHashMap</code> 的 <code>removeEldestEntry</code> 或第三方缓存（如 Caffeine、Guava Cache）。</li>
</ul>
</li>



<li><strong>监听器/回调注册后未注销</strong>
<ul class="wp-block-list">
<li>注册了事件监听器或回调，但在对象销毁时没有注销，导致监听器长期持有对象引用。</li>



<li>解决：在 <code>dispose()</code> 或类似方法中显式注销。</li>
</ul>
</li>



<li><strong>ThreadLocal 使用不当</strong>
<ul class="wp-block-list">
<li><code>ThreadLocal</code> 中的对象被当前线程持有，如果线程长期存活（如线程池中的线程），且未调用 <code>remove()</code>，则对象无法释放。</li>



<li>解决：每次用完 <code>ThreadLocal</code> 后调用 <code>remove()</code>，或设计为存放小对象。</li>
</ul>
</li>
</ol>



<h3 class="wp-block-heading">三、内存泄漏与内存溢出的关系</h3>



<ul class="wp-block-list">
<li><strong>内存泄漏是内存溢出的一个重要原因</strong>。泄漏导致可用内存逐渐减少，最终达到临界点，任何分配请求都无法满足时，就会发生内存溢出。</li>



<li>但内存溢出也可能由其他原因引起，例如一次性加载过多数据（即使没有泄漏，但内存瞬间被占满）、JVM 堆设置过小等。</li>



<li><strong>内存泄漏是“慢性”的</strong>，通常需要长时间运行才能显现；而内存溢出可能是“急性”的，例如创建超大数组立即触发。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">四、如何检测与解决</h3>



<h4 class="wp-block-heading">检测内存泄漏</h4>



<ul class="wp-block-list">
<li><strong>观察</strong>：程序长时间运行后响应变慢，甚至抛出 OOM。</li>



<li><strong>监控工具</strong>：使用 <code>jstat</code>、<code>jvisualvm</code>、<code>JConsole</code> 观察 GC 行为和堆内存趋势。</li>



<li><strong>堆转储分析</strong>：当怀疑泄漏时，通过 <code>jmap -dump:format=b,file=heap.hprof &lt;pid></code> 生成堆转储文件，然后用 <strong>Eclipse MAT</strong> 或 <strong>VisualVM</strong> 分析，查找“可疑对象”或“支配树”中占内存最大的对象，并查看其 GC Roots 引用链。</li>



<li><strong>GC 日志</strong>：开启 <code>-XX:+PrintGCDetails</code>，观察 GC 后内存是否持续增长。</li>
</ul>



<h4 class="wp-block-heading">解决策略</h4>



<ul class="wp-block-list">
<li><strong>代码审查</strong>：重点检查静态集合、未关闭资源、内部类、缓存等常见泄漏点。</li>



<li><strong>使用弱引用</strong>：对于缓存、观察者等场景，考虑使用 <code>WeakReference</code> 或 <code>WeakHashMap</code>。</li>



<li><strong>资源管理</strong>：确保所有 <code>Closeable</code> 资源在使用后及时关闭。</li>



<li><strong>避免不必要的对象持有</strong>：例如，不要将大对象长期保存在 <code>ThreadLocal</code> 中。</li>



<li><strong>合理设置 JVM 参数</strong>：根据应用特点调整堆大小、新生代比例等，但根本还是要避免泄漏。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">总结</h3>



<ul class="wp-block-list">
<li><strong>内存溢出</strong>是结果——内存不够用了。</li>



<li><strong>内存泄漏</strong>是原因之一——内存被无用对象占据，无法释放。</li>



<li>在开发中，应养成良好的编码习惯，定期使用工具检测内存占用趋势，及时修复泄漏隐患。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2025/07/07/316/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">316</post-id>	</item>
		<item>
		<title>【Java面试】多线程</title>
		<link>https://blog.lhyshome.com/2025/07/04/309/</link>
					<comments>https://blog.lhyshome.com/2025/07/04/309/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Fri, 04 Jul 2025 02:23:14 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=309</guid>

					<description><![CDATA[什么是线程？ 线程是操作系统能够进行运算调度的最小单位，它被包含在进程之中，是进程中的实际运作单位。程序员可以… <span class="read-more"><a href="https://blog.lhyshome.com/2025/07/04/309/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">什么是线程？</h2>



<p>线程是操作系统能够进行运算调度的最小单位，它被包含在进程之中，是进程中的实际运作单位。程序员可以通过它进行多处理器编程，你可以使用多线程对运算密集型任务提速。比如，如果一个线程完成一个任务要100毫秒，那么用十个线程完成该任务只需10毫秒。</p>



<h2 class="wp-block-heading">线程和进程有什么区别？</h2>



<p>一个进程是一个独立(self contained)的运行环境，它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。线程是进程的子集，一个进程可以有很多线程，每条线程并行执行不同的任务。不同的进程使用不同的内存空间，而同一进程的所有的线程共享一片相同的内存空间。别把它和栈内存搞混，每个线程都拥有单独的栈内存用来存储本地数据。</p>



<h2 class="wp-block-heading">如何在Java中实现线程？</h2>



<p>有四种创建线程的方法：</p>



<ul class="wp-block-list">
<li>一是实现Runnable接口，然后将它传递给Thread的构造函数，创建一个Thread对象；</li>



<li>二是直接继承Thread类</li>



<li>三是实现 Callable 接口</li>



<li>四是 通过线程池</li>
</ul>



<h2 class="wp-block-heading">用Runnable还是Thread？</h2>



<p>这个问题是上题的后续，大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程，问题是，那个方法更好呢？什么情况下使用它？这个问题很容易回答，如果你知道Java不支持类的多重继承，但允许你调用多个接口。所以如果你要继承其他类，当然是调用Runnable接口好了。</p>



<h2 class="wp-block-heading">Thread 类中的start() 和 run() 方法有什么区别？</h2>



<p>start()方法被用来启动新创建的线程，使该被创建的线程状态变为可运行状态。当你调用run()方法的时候，只会是在原来的线程中调用，没有新的线程启动，start()方法才会启动新线程。如果我们调用了Thread的run()方法，它的行为就会和普通的方法一样，直接运行run（）方法。为了在新的线程中执行我们的代码，必须先使用Thread.start()方法。</p>



<h2 class="wp-block-heading">Java中Runnable和Callable有什么不同？</h2>



<p>Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了，Callable是在JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常，而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。</p>



<h2 class="wp-block-heading">Java中CyclicBarrier 和 CountDownLatch有什么不同？</h2>



<p>CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是，CountdownLatch 不能重新使用。</p>



<h2 class="wp-block-heading">Java内存模型是什么？</h2>



<p>Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证，它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如，先行发生关系确保了：</p>



<p>线程内的代码能够按先后顺序执行，这被称为程序次序规则。<br>对于同一个锁，一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前，也叫做管程锁定规则。<br>前一个对volatile的写操作在后一个volatile的读操作之前，也叫volatile变量规则。<br>一个线程内的任何操作必需在这个线程的start()调用之后，也叫作线程启动规则。<br>一个线程的所有操作都会在线程终止之前，线程终止规则。<br>一个对象的终结操作必需在这个对象构造完成之后，也叫对象终结规则。<br>可传递性</p>



<h2 class="wp-block-heading">Java中的volatile 变量是什么？</h2>



<p>volatile是一个特殊的修饰符，只有成员变量才能使用它。在Java并发程序缺少同步类的情况下，多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。</p>



<h2 class="wp-block-heading">什么是线程安全？Vector是一个线程安全类吗？</h2>



<p>如果你的代码所在的进程中有多个线程在同时运行，而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的，而且其他的变量的值也和预期的是一样的，就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组，线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。</p>



<h2 class="wp-block-heading">Java中什么是竞态条件？</h2>



<p>当两个线程竞争同一资源时，如果对资源的访问顺序敏感，就称存在竞态条件。<br>竞态条件（Race Condition）：计算的正确性取决于多个线程的交替执行时序时，就会发生竞态条件。<br>导致竞态条件发生的代码区称作临界区。<br>在临界区中使用适当的同步就可以避免竞态条件。<br>临界区实现方法有两种，一种是用synchronized，一种是用Lock显式锁实现。</p>



<h2 class="wp-block-heading">Java中如何停止一个线程？</h2>



<p>Java提供了很丰富的API, 但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法，但是由于潜在的死锁威胁。因此在后续的JDK版本中他们被弃用了，之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束，如果要手动结束一个线程，可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。</p>



<h2 class="wp-block-heading">一个线程运行时发生异常会怎样？</h2>



<p>如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。</p>



<h2 class="wp-block-heading">如何在两个线程间共享数据？</h2>



<p>你可以通过共享对象来实现这个目的，或者是使用像阻塞队列这样并发的数据结构。</p>



<h2 class="wp-block-heading">Java中notify 和 notifyAll有什么区别？</h2>



<p>这又是一个刁钻的问题，因为多线程可以等待单监控锁，Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们，但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程，所以只有一个线程在等待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。</p>



<h2 class="wp-block-heading">为什么wait, notify 和 notifyAll这些方法不在thread类里面？</h2>



<p>一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的，每个对象都有锁，通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中，线程正在等待的是哪个锁就不明显了。简单的说，由于wait，notify和notifyAll都是锁级别的操作，所以把他们定义在Object类中因为锁属于对象。</p>



<h2 class="wp-block-heading">什么是ThreadLocal变量？</h2>



<p>多线程访问同一个共享变量的时候容易出现并发问题，特别是多个线程对一个变量进行写入的时候，为了保证线程安全，一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法，当我们在创建一个变量后，如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。</p>



<p>ThreadLocal是JDK包提供的，它提供线程本地变量，如果创建一个ThreadLocal变量，那么访问这个变量的每个线程都会有这个变量的一个副本，在实际多线程操作的时候，操作的是自己本地内存中的变量，从而规避了线程安全问题</p>



<pre class="wp-block-code"><code>public class ThreadLocalTest {
    static ThreadLocal&lt;String&gt; localVar = new ThreadLocal&lt;&gt;();
    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }

    public static void main(String&#91;] args) {
        Thread t1  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar1");
                //调用打印方法
                print("thread1");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });

        Thread t2  = new Thread(new Runnable() {
            @Override
            public void run() {
                //设置线程1中本地变量的值
                localVar.set("localVar2");
                //调用打印方法
                print("thread2");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
            }
        });

        t1.start();
        t2.start();
    }
}</code></pre>



<h2 class="wp-block-heading">什么是FutureTask？</h2>



<p>在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回，如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装，由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。</p>



<h2 class="wp-block-heading">Java中interrupted 和 isInterruptedd方法的区别？</h2>



<p>interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的，调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时，中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何，一个线程的中断状态有有可能被其它线程调用中断来改变。</p>



<h2 class="wp-block-heading">为什么wait和notify方法要在同步块中调用？</h2>



<p>当一个线程需要调用对象的wait()方法的时候，这个线程必须拥有该对象的锁，接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的，当一个线程需要调用对象的notify()方法时，它会释放这个对象的锁，以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁，这样就只能通过同步来实现，所以他们只能在同步方法或者同步块中被调用。如果你不这么做，代码会抛出IllegalMonitorStateException异常。</p>



<h2 class="wp-block-heading">为什么你应该在循环中检查等待条件?</h2>



<p>处于等待状态的线程可能会收到错误警报和伪唤醒，如果不在循环中检查等待条件，程序就会在没有满足结束条件的情况下退出。因此，当一个等待线程醒来时，不能认为它原来的等待状态仍然是有效的，在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因</p>



<h2 class="wp-block-heading">Java中的同步集合与并发集合有什么区别？</h2>



<p>同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合，不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用，阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap，不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。</p>



<p><strong>同步集合</strong> 可以简单地理解为通过synchronized来实现同步的集合。如果有多个线程调用同步集合的方法，它们将会串行执行。<br>Hashtable和vector、stack</p>



<p><strong>并发集合</strong> 是jdk5.0重要的特性，增加了并发包java.util.concurrent.*。Java内存模型、volatile变量及AbstractQueuedSynchronizer(简称AQS同步器)，是并发包众多实现的基础。</p>



<p>常见的并发集合：</p>



<ul class="wp-block-list">
<li>ConcurrentHashMap：线程安全的HashMap的实现</li>



<li>CopyOnWriteArrayList：线程安全且在读操作时无锁的ArrayList</li>



<li>CopyOnWriteArraySet：基于CopyOnWriteArrayList，不添加重复元素</li>



<li>ArrayBlockingQueue：基于数组、先进先出、线程安全，可实现指定时间的阻塞读写，并且容量可以限制</li>



<li>LinkedBlockingQueue：基于链表实现，读写各用一把锁，在高并发读写操作都多的情况下，性能优于ArrayBlockingQueue</li>
</ul>



<h2 class="wp-block-heading">Java中堆和栈有什么不同？</h2>



<p>为什么把这个问题归类在多线程和并发面试题里？因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存，用于存储本地变量，方法参数和栈调用，一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建，为了提升效率线程会从堆中弄一个缓存到自己的栈，如果多个线程使用该变量就可能引发问题，这时volatile 变量就可以发挥作用了，它要求线程从主存中读取变量的值。</p>



<h2 class="wp-block-heading">什么是线程池？ 为什么要使用它？</h2>



<p>创建线程要花费昂贵的资源和时间，如果任务来了才创建线程那么响应时间会变长，而且一个进程能创建的线程数有限。为了避免这些问题，在程序启动的时候就创建若干线程来响应处理，它们被称为线程池，里面的线程叫工作线程。从JDK1.5开始，Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池，每次处理一个任务；数目固定的线程池或者是缓存线程池（一个适合很多生存期短的任务的程序的可扩展线程池）。</p>



<h2 class="wp-block-heading">如何写代码来解决生产者消费者问题？</h2>



<p>在现实中你解决的许多线程问题都属于生产者消费者模型，就是一个线程生产任务供其它线程进行消费，你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用wait和notify来解决这个问题，比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型。</p>



<h2 class="wp-block-heading">如何避免死锁？</h2>



<p>Java多线程中的死锁</p>



<p>死锁是指两个或两个以上的进程在执行过程中，因争夺资源而造成的一种互相等待的现象，若无外力作用，它们都将无法推进下去。这是一个严重的问题，因为死锁会让你的程序挂起无法完成任务，死锁的发生必须满足以下四个条件：</p>



<ul class="wp-block-list">
<li>互斥条件：一个资源每次只能被一个进程使用。</li>



<li>请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。</li>



<li>不剥夺条件：进程已获得的资源，在末使用完之前，不能强行剥夺。</li>



<li>循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。</li>
</ul>



<p>避免死锁最简单的方法就是阻止循环等待条件，将系统中所有的资源设置标志位、排序，规定所有的进程申请资源必须以一定的顺序（升序或降序）做操作来避免死锁。</p>



<h2 class="wp-block-heading">Java中活锁和死锁有什么区别？</h2>



<p>这是上题的扩展，活锁和死锁类似，不同之处在于处于活锁的线程或进程的状态是不断改变的，活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到，两个人都试着避让对方好让彼此通过，但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是，活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。</p>



<h2 class="wp-block-heading">怎么检测一个线程是否拥有锁？</h2>



<p>在java.lang.Thread中有一个方法叫holdsLock()，它返回true如果当且仅当当前线程拥有某个具体对象的锁。</p>



<h2 class="wp-block-heading">你如何在Java中获取线程堆栈？</h2>



<p>对于不同的操作系统，有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时，JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows你可以使用Ctrl + Break组合键来获取线程堆栈，Linux下用kill -3命令。你也可以用jstack这个工具来获取，它对线程id进行操作，你可以用jps这个工具找到id。</p>



<h2 class="wp-block-heading">JVM中哪个参数是用来控制线程的栈堆大小的</h2>



<p>这个问题很简单， -Xss参数用来控制线程的堆栈大小。你可以查看JVM配置列表来了解这个参数的更多信息。</p>



<h2 class="wp-block-heading">Java中synchronized 和 ReentrantLock 有什么不同？</h2>



<p>Java在过去很长一段时间只能通过synchronized关键字来实现互斥，它有一些缺点。比如你不能扩展锁之外的方法或者块边界，尝试获取锁时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock，它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。</p>



<h2 class="wp-block-heading">有三个线程T1，T2，T3，怎么确保它们按顺序执行（确保main()方法所在的线程是Java程序最后结束的线程）？</h2>



<p>在多线程中有多种方法让线程按特定顺序执行，你可以用线程类的join()方法在一个线程中启动另一个线程，另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2，T2调用T1)，这样T1就会先完成而T3最后完成。</p>



<h2 class="wp-block-heading">Thread类中的yield方法有什么作用？</h2>



<p>yield方法可以暂停当前正在执行的线程对象，让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU，执行yield()的线程有可能在进入到暂停状态后马上又被执行。</p>



<h2 class="wp-block-heading">Java中ConcurrentHashMap的并发度是什么？</h2>



<p>ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的，它是ConcurrentHashMap类构造函数的一个可选参数，默认值为16，这样在多线程情况下就能避免争用。</p>



<h2 class="wp-block-heading">Java中Semaphore是什么？</h2>



<p>Java中的Semaphore是一种新的同步类，它是一个计数信号。从概念上讲，信号量维护了一个许可集合。如有必要，在许可可用前会阻塞每一个 acquire()，然后再获取该许可。每个 release()添加一个许可，从而可能释放一个正在阻塞的获取者。但是，不使用实际的许可对象，Semaphore只对可用许可的号码进行计数，并采取相应的行动。信号量常常用于多线程的代码中，比如数据库连接池。</p>



<h2 class="wp-block-heading">如果你提交任务时，线程池队列已满。会时发会生什么？</h2>



<p>这个问题问得很狡猾，许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。</p>



<h2 class="wp-block-heading">Java线程池中submit() 和 execute()方法有什么区别？</h2>



<p>两个方法都可以向线程池提交任务，execute()方法的返回类型是void，它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象，它定义在ExecutorService接口中，它扩展了Executor接口，其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。</p>



<h2 class="wp-block-heading">什么是阻塞式方法？</h2>



<p>阻塞式方法是指程序会一直等待该方法完成期间不做其他事情，ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前，当前线程会被挂起，直到得到结果之后才会返回。此外，还有异步和非阻塞式方法在任务完成前就返回。</p>



<h2 class="wp-block-heading">你对线程优先级的理解是什么？</h2>



<p>每一个线程都是有优先级的，一般来说，高优先级的线程在运行时会具有优先权，但这依赖于线程调度的实现，这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级，但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int变量(从1-10)，1代表最低优先级，10代表最高优先级。</p>



<h2 class="wp-block-heading">什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)？</h2>



<p>线程调度器是一个操作系统服务，它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它，它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制，所以由应用程序来控制它是更好的选择（也就是说不要让你的程序依赖于线程的优先级）。</p>



<h2 class="wp-block-heading">在多线程中，什么是上下文切换(context-switching)？</h2>



<p>上下文切换是存储和恢复CPU状态的过程，它使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。</p>



<h2 class="wp-block-heading">如何在Java中创建Immutable对象？</h2>



<p>Immutable对象可以在没有同步的情况下共享，降低了对该对象进行并发访问时的同步化开销。要创建不可变类，要实现下面几个步骤：通过构造方法初始化所有成员、对变量不要提供setter方法、将所有的成员声明为私有的，这样就不允许直接访问这些成员、在getter方法中，不要直接返回对象本身，而是克隆对象，并返回对象的拷贝。</p>



<h2 class="wp-block-heading">Java中的ReadWriteLock是什么？</h2>



<p>一般而言，读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口，一个ReadWriteLock维护一对关联的锁，一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的，你可以使用JDK中的ReentrantReadWriteLock来实现这个规则，它最多支持65535个写锁和65535个读锁。</p>



<h2 class="wp-block-heading">多线程中的忙循环是什么?</h2>



<p>忙循环就是程序员用循环让一个线程等待，不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制，而忙循环不会放弃CPU，它就是在运行一个空循环。这么做的目的是为了保留CPU缓存，在多核系统中，一个等待线程醒来的时候可能会在另一个内核运行，这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。</p>



<h2 class="wp-block-heading">volatile 变量和 atomic 变量有什么不同？</h2>



<p>这是个有趣的问题。首先，volatile 变量和 atomic 变量看起来很像，但功能却不一样。Volatile变量可以确保先行关系，即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一，其它数据类型和引用变量也可以进行相似操作。</p>



<h2 class="wp-block-heading">如果同步块内的线程抛出异常会发生什么？</h2>



<p>这个问题坑了很多Java程序员，若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的，里面的线程都会释放锁，所以对比锁接口我们更喜欢同步块，因为它不用花费精力去释放锁，该功能可以在finally block里释放锁实现。</p>



<h2 class="wp-block-heading">单例模式的双检锁是什么？</h2>



<p>这个问题在Java面试中经常被问到，但是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和Java1.5是如何对它修正的。它其实是一个用来创建线程安全的单例的老方法，当单例实例第一次被创建时它试图用单个锁进行性能优化，但是由于太过于复杂在JDK1.4中它是失败的。</p>



<h2 class="wp-block-heading">如何在Java中创建线程安全的Singleton？</h2>



<p>这是上面那个问题的后续，如果你不喜欢双检锁而面试官问了创建Singleton类的替代方法，你可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例，或者是利用枚举类型来创建Singleton。</p>



<h2 class="wp-block-heading">写出3条你遵循的多线程最佳实践</h2>



<p>以下三条最佳实践大多数Java程序员都应该遵循：</p>



<p>给你的线程起个有意义的名字。<br>这样可以方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了，给线程起一个和它要完成的任务相关的名字，所有的主要框架甚至JDK都遵循这个最佳实践。</p>



<p>避免锁定和缩小同步的范围<br>锁花费的代价高昂且上下文切换更耗费时间空间，试试最低限度的使用同步和锁，缩小临界区。因此相对于同步方法我更喜欢同步块，它给我拥有对锁的绝对控制权。</p>



<p>多用同步类少用wait 和 notify<br>首先，CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作，而用wait和notify很难实现对复杂控制流的控制。其次，这些类是由最好的企业编写和维护在后续的JDK中它们还会不断优化和完善，使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。</p>



<p>多用并发集合少用同步集合<br>这是另外一个容易遵循且受益巨大的最佳实践，并发集合比同步集合的可扩展性更好，所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map，你应该首先想到用ConcurrentHashMap。</p>



<h2 class="wp-block-heading">如何强制启动一个线程？</h2>



<p>这个问题就像是如何强制进行Java垃圾回收，目前还没有觉得方法，虽然你可以使用System.gc()来进行垃圾回收，但是不保证能成功。在Java里面没有办法强制启动一个线程，它是被线程调度器控制着且Java没有公布相关的API。</p>



<h2 class="wp-block-heading">Java中的fork join框架是什么？</h2>



<p>fork join框架是JDK7中出现的一款高效的工具，Java开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块设计的，目的是将所有可用的处理能力用来提升程序的性能。fork join框架一个巨大的优势是它使用了工作窃取算法，可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。</p>



<h2 class="wp-block-heading">Java多线程中调用wait() 和 sleep()方法有什么不同？</h2>



<p>Java程序中wait 和 sleep都会造成某种形式的暂停，它们可以满足不同的需要。wait()方法用于线程间通信，如果等待条件为真且其它线程被唤醒时它会释放锁，而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间，但不会释放锁。需要注意的是，sleep（）并不会让线程终止，一旦从休眠中唤醒线程，线程的状态将会被改变为Runnable，并且根据线程调度，它将得到执行。</p>



<h2 class="wp-block-heading">什么是ThreadGroup？为什么不建议使用它？</h2>



<p>ThreadGroup是一个类，它的目的是提供关于线程组的信息。</p>



<p>ThreadGroup API比较薄弱，它并没有比Thread提供了更多的功能。它有两个主要的功能：一是获取线程组中处于活跃状态线程的列表；二是设置为线程设置未捕获异常处理器(ncaught exception handler)。但在Java 1.5中Thread类也添加了setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法，所以ThreadGroup是已经过时的，不建议继续使用。</p>



<h2 class="wp-block-heading">什么是Java线程转储(Thread Dump)，如何得到它？</h2>



<p>线程转储是一个JVM活动线程的列表，它对于分析系统瓶颈和死锁非常有用。有很多方法可以获取线程转储——使用Profiler，Kill -3命令，jstack工具等等。我们更喜欢jstack工具，因为它容易使用并且是JDK自带的。由于它是一个基于终端的工具，所以我们可以编写一些脚本去定时的产生线程转储以待分析。</p>



<h2 class="wp-block-heading">什么是Java Timer类？如何创建一个有特定时间间隔的任务？</h2>



<p>java.util.Timer是一个工具类，可以用于安排一个线程在未来的某个特定时间执行。Timer类可以用安排一次性任务或者周期任务。</p>



<p>java.util.TimerTask是一个实现了Runnable接口的抽象类，我们需要去继承这个类来创建我们自己的定时任务并使用Timer去安排它的执行。</p>



<h2 class="wp-block-heading">什么是原子操作？在Java Concurrency API中有哪些原子类(atomic classes)？</h2>



<p>原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段。</p>



<p>int++并不是一个原子操作，所以当一个线程读取它的值并加1时，另外一个线程有可能会读到之前的值，这就会引发错误。</p>



<p>在 java.util.concurrent.atomic 包中添加原子变量类之后，这种情况才发生了改变。所有原子变量类都公开比较并设置原语（与比较并交换类似），这些原语都是使用平台上可用的最快本机结构（比较并交换、加载链接/条件存储，最坏的情况下是旋转锁）来实现的。 java.util.concurrent.atomic 包中提供了原子变量的 9 种风格（ AtomicInteger； AtomicLong； AtomicReference； AtomicBoolean；原子整型；长型；引用；及原子标记引用和戳记引用类的数组形式，其原子地更新一对值）。</p>



<h2 class="wp-block-heading">Java Concurrency API中的Lock接口(Lock interface)是什么？对比同步它有什么优势？</h2>



<p>Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构，可以具有完全不同的性质，并且可以支持多个相关类的条件对象。</p>



<p>它的优势有：</p>



<ul class="wp-block-list">
<li>可以使锁更公平</li>



<li>可以使线程在等待锁的时候响应中断</li>



<li>可以让线程尝试获取锁，并在无法获取锁的时候立即返回或者等待一段时间</li>



<li>可以在不同的范围，以不同的顺序获取和释放锁</li>
</ul>



<h2 class="wp-block-heading">什么是Executor框架？</h2>



<p>Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据一组执行策略调用，调度，执行和控制的异步任务的框架。</p>



<p>无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案，因为可以限制线程的数量并且可以回收再利用这些线程。利用Executor框架可以非常方便的创建一个线程池。</p>



<h2 class="wp-block-heading">Executors类是什么？</h2>



<p>Executors为Executor，ExecutorService，ScheduledExecutorService，ThreadFactory和Callable类提供了一些工具方法。</p>



<p>Executors可以用于方便的创建线程池。</p>



<h2 class="wp-block-heading">什么是阻塞队列？如何使用阻塞队列来实现生产者-消费者模型？</h2>



<p>java.util.concurrent.BlockingQueue的特性是：当队列是空的时，从队列中获取或删除元素的操作将会被阻塞，或者当队列是满时，往队列里添加元素的操作会被阻塞。</p>



<p>阻塞队列不接受空值，当你尝试向队列中添加空值的时候，它会抛出NullPointerException。</p>



<p>阻塞队列的实现都是线程安全的，所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。</p>



<p>BlockingQueue 接口是java collections框架的一部分，它主要用于实现生产者-消费者问题。</p>



<h2 class="wp-block-heading">什么是Callable和Future?</h2>



<p>Java 5在concurrency包中引入了java.util.concurrent.Callable 接口，它和Runnable接口很相似，但它可以返回一个对象或者抛出一个异常。<br><br>Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的，我们必须等待它返回的结果。java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象，使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果。</p>



<h2 class="wp-block-heading">什么是FutureTask?</h2>



<p>FutureTask包装器是一种非常便利的机制，可将Callable转换成Future和Runnable，它同时实现两者的接口。<br><br>FutureTask类是Future 的一个实现，并实现了Runnable，所以可通过Excutor(线程池) 来执行。也可传递给Thread对象执行。如果在主线程中需要执行比较耗时的操作时，但又不想阻塞主线程时，可以把这些作业交给Future对象在后台完成，当主线程将来需要时，就可以通过Future对象获得后台作业的计算结果或者执行状态。</p>



<h2 class="wp-block-heading">什么是并发容器的实现？</h2>



<p>Java集合类都是快速失败的，这就意味着当集合被改变且一个线程在使用迭代器遍历集合的时候，迭代器的next()方法将抛出ConcurrentModificationException异常。<br><br>并发容器：并发容器是针对多个线程并发访问设计的，在jdk5.0引入了concurrent包，其中提供了很多并发容器，如ConcurrentHashMap，CopyOnWriteArrayList等。并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性，例如在ConcurrentHashMap中采用了一种粒度更细的加锁机制，可以称为分段锁，在这种锁机制下，允许任意数量的读线程并发地访问map，并且执行读操作的线程和写操作的线程也可以并发的访问map，同时允许一定数量的写操作线程并发地修改map，所以它可以在并发环境下实现更高的吞吐量。</p>



<h2 class="wp-block-heading">用户线程和守护线程有什么区别？</h2>



<p>当我们在Java程序中创建一个线程，它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候，JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。</p>



<h2 class="wp-block-heading">有哪些不同的线程生命周期？</h2>



<p>当我们在Java程序中新建一个线程时，它的状态是New。当我们调用线程的start()方法时，状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。其他的线程状态还有Waiting，Blocked 和Dead。</p>



<h2 class="wp-block-heading">线程之间是如何通信的？</h2>



<p>当线程间是可以共享资源时，线程间通信是协调它们的重要的手段。Object类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。</p>



<h2 class="wp-block-heading">为什么Thread类的sleep()和yield()方法是静态的？</h2>



<p>Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作，并避免程序员错误的认为可以在其他非运行线程调用这些方法。</p>



<h2 class="wp-block-heading">如何确保线程安全？</h2>



<p>在Java中可以有很多方法来保证线程安全——同步，使用原子类(atomic concurrent classes)，实现并发锁，使用volatile关键字，使用不变类和线程安全类。</p>



<h2 class="wp-block-heading">同步方法和同步块，哪个是更好的选择？</h2>



<p>同步块是更好的选择，因为它不会锁住整个对象（当然你也可以让它锁住整个对象）。同步方法会锁住整个对象，哪怕这个类中有多个不相关联的同步块，这通常会导致他们停止执行并需要等待获得这个对象上的锁。</p>



<h2 class="wp-block-heading">如何创建守护线程？</h2>



<p>使用Thread类的setDaemon(true)方法可以将线程设置为守护线程，需要注意的是，需要在调用start()方法前调用这个方法，否则会抛出IllegalThreadStateException异常。</p>



<h2 class="wp-block-heading">线程调度策略？</h2>



<p>(1) 抢占式调度策略<br>Java运行时系统的线程调度算法是抢占式的 (preemptive)。Java运行时系统支持一种简单的固定优先级的调度算法。如果一个优先级比其他任何处于可运行状态的线程都高的线程进入就绪状态，那么运行时系统就会选择该线程运行。新的优先级较高的线程抢占(preempt)了其他线程。但是Java运行时系统并不抢占同优先级的线程。换句话说，Java运行时系统不是分时的(time-slice)。然而，基于Java Thread类的实现系统可能是支持分时的，因此编写代码时不要依赖分时。当系统中的处于就绪状态的线程都具有相同优先级时，线程调度程序采用一种简单的、非抢占式的轮转的调度顺序。<br><br>(2) 时间片轮转调度策略<br>有些系统的线程调度采用时间片轮转(round-robin)调度策略。这种调度策略是从所有处于就绪状态的线程中选择优先级最高的线程分配一定的CPU时间运行。该时间过后再选择其他线程运行。只有当线程运行结束、放弃(yield)CPU或由于某种原因进入阻塞状态，低优先级的线程才有机会执行。如果有两个优先级相同的线程都在等待CPU，则调度程序以轮转的方式选择运行的线程。</p>



<h2 class="wp-block-heading">在线程中你怎么处理不可捕捉异常？</h2>



<p>Thread.UncaughtExceptionHandler是java SE5中的新接口，它允许我们在每一个Thread对象上添加一个异常处理器。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2025/07/04/309/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">309</post-id>	</item>
		<item>
		<title>【Java面试】JVM</title>
		<link>https://blog.lhyshome.com/2025/03/09/283/</link>
					<comments>https://blog.lhyshome.com/2025/03/09/283/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Sun, 09 Mar 2025 12:10:04 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[面试]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=283</guid>

					<description><![CDATA[什么是JVM（Java虚拟机）？ JVM（Java Virtual Machine）是一种虚拟计算机环境，它在… <span class="read-more"><a href="https://blog.lhyshome.com/2025/03/09/283/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">什么是JVM（Java虚拟机）？</h2>



<p>JVM（Java Virtual Machine）是一种虚拟计算机环境，它在物理计算机上运行Java字节码（Java bytecode）。JVM负责将Java源代码编译成字节码，并在运行时执行字节码。这使得Java应用程序可以在不同的操作系统和硬件平台上运行，实现了跨平台性。</p>



<h2 class="wp-block-heading">JVM的主要组件是什么？</h2>



<p>JVM主要由以下几个组件组成：</p>



<ul class="wp-block-list">
<li>类加载器（Class Loader）： 负责加载类文件并生成Java类对象。</li>



<li>执行引擎（Execution Engine）：执行Java字节码，将其翻译为本地机器代码。</li>



<li>内存区域（Memory Areas）： 包括堆、方法区（元数据区）、栈、本地方法栈和程序计数器等。</li>



<li>垃圾回收器（Garbage Collector）：负责管理和回收内存中不再使用的对象。</li>



<li>本地接口（Native Interface）： 允许Java代码与本地库进行交互。</li>
</ul>



<h2 class="wp-block-heading">JVM与JRE之间有什么区别？</h2>



<ul class="wp-block-list">
<li>JVM（Java虚拟机）：JVM是Java程序的运行时环境，它负责解释和执行Java字节码。它是Java应用程序和操作系统之间的中间层。 </li>



<li>JRE（Java Runtime Environment）： JRE是包括JVM在内的Java运行时环境的集合。它包括Java类库、JVM和其他运行时组件。JRE允许您运行Java应用程序，但不包括开发工具。 简而言之，JVM是JRE的一部分，用于执行Java程序，而JRE包含了Java应用程序的运行时环境和所需的库</li>
</ul>



<h2 class="wp-block-heading">什么是字节码（Bytecode）？</h2>



<p>字节码是一种中间代码，它是由Java编译器生成的，用于在Java虚拟机（JVM）上执行Java程序。字节码是一种与平台无关的二进制格式，类似于汇编语言。Java源代码在编译过程中被翻译成字节码，而不是直接编译成机器代码。这种中间表示使得Java程序具有跨平台性，因为字节码可以在不同的操作系统和硬件平台上运行。</p>



<h2 class="wp-block-heading">为什么Java被称为“Write Once, Run Anywhere”？</h2>



<p>Java被称为“Write Once, Run Anywhere”（WORA）是因为Java程序可以编写一次，然后在不同的平台上运行，而无需修改源代码。这是由于Java程序被编译成字节码，而不是特定于操作系统的机器代码。字节码可以在任何支持Java虚拟机（JVM）的平台上运行。这种跨平台性使得Java成为一种非常强大的编程语言，适用于多种不同的设备和操作系统。</p>



<h2 class="wp-block-heading">JVM的内存结构是什<strong>么？</strong></h2>



<p>JVM的内存结构通常分为以下几个主要区域：</p>



<ul class="wp-block-list">
<li>堆（Heap）： 用于存储对象实例，包括新生代和老年代。</li>



<li>方法区（Method Area）：用于存储类信息、静态变量、常量池等。</li>



<li>栈（Stack）： 用于存储方法调用和局部变量，包括虚拟机栈和本地方法栈。</li>



<li>程序计数器（Program Counter Register）： 存储当前线程执行的字节码指令地址。</li>



<li>本地方法栈（Native Method Stack）： 用于执行本地方法。</li>
</ul>



<h2 class="wp-block-heading">堆和栈区别是什么？</h2>



<ul class="wp-block-list">
<li><strong>堆</strong>：<strong>线程共享</strong>，存储对象实例，生命周期由垃圾回收管理，大小通常较大。</li>



<li><strong>栈</strong>：<strong>线程私有</strong>，存储基本数据类型和对象引用，每个方法调用创建一个栈帧，生命周期随方法结束而结束，内存自动释放。</li>
</ul>



<h2 class="wp-block-heading">什么是元数据区（Metaspace）？</h2>



<p>元数据区是Java 8及之后版本中的一部分内存，用于存储类加载器加载</p>



<h2 class="wp-block-heading">什么是新生代（Young Generation）和老年代（Old Generation）？</h2>



<ul class="wp-block-list">
<li><strong>新生代（Young Generation）：</strong>是堆内存的一部分，用于存储新创建的对象。它通常被划分为伊甸园区（Eden）和两个幸存者区（Survivor）。大多数对象在新生代中被创建和销毁，具有短暂的生命周期。</li>



<li><strong>老年代（Old Generation）：</strong>也是堆内存的一部分，用于存储已经经过多次垃圾回收的对象，具有较长的生命周期。老年代中的对象在存活时间较长，因此需要更高效的垃圾回收器来管理。</li>
</ul>



<h2 class="wp-block-heading">什么是堆（Heap）内存？它的作用是什么？</h2>



<p>堆内存 是JVM中的一个重要区域，主要用于存储对象实例。它被进一步划分为新生代（Young Generation）和老年代（Old Generation）。</p>



<ul class="wp-block-list">
<li>新生代：用于存储新创建的对象，通常包括伊甸园区（Eden）和两个幸存者区（Survivor）。</li>



<li>老年代：用于存储已经经过多次垃圾回收的对象，具有较长的生命周期。</li>
</ul>



<p>堆内存的作用是管理对象的创建、分配和回收。垃圾回收器负责在堆内存中回收不再被引用的对象。</p>



<h2 class="wp-block-heading">什么是栈（Stack）内存？它与堆有什么不同？</h2>



<p>栈内存用于存储方法调用和局部变量。每个线程都有自己的栈，栈中的数据是线程私有的。<br>栈内存中存储方法的调用栈，每次方法调用时都会创建一个新的栈帧（Stack Frame），用于存储方法的局部变量和操作数栈。<br>与堆内存不同，栈内存的生命周期与线程的生命周期相同，它在方法执行结束时会被自动销毁。</p>



<h2 class="wp-block-heading"><strong>什么是方法区（Method Area）？它存</strong>储<strong>哪些信息？</strong></h2>



<p>方法区也被称为永久代（在Java 7之前）或元数据区（在Java 8之后），它用于存储<strong>类信息、静态变量、常量池、方法代码等</strong>。<br>方法区存储的信息包括类的结构信息、运行时常量池、字段和方法的描述、字节码等。它在运行时保存了类的元数据。</p>



<h2 class="wp-block-heading">什么是永久代（Permanent Generation）？它在Java 8之后有什么替代品？</h2>



<p>永久代是Java 7及之前版本中的一部分内存，用于存储类加载器加载的类信息、静态变量、常量池等。在Java 8及之后的版本中，永久代被元数据区（Metaspace）所替代。元数据区不再是固定大小的，它可以动态地分配和释放内存，使得更好地适应应用程序的需求。这个改变旨在解决永久代空间不足和内存泄漏的问题。</p>



<h2 class="wp-block-heading"><strong>什么是本地方法栈</strong>（<strong>Native Method Stack）？</strong></h2>



<p>本地方法栈 是JVM中的一部分，用于执行本地方法，即使用本地语言（如C、C++）编写的方法。<br>与虚拟机栈（Java方法调用）类似，本地方法栈用于管理本地方法的调用和局部变量。<br>本地方法栈与虚拟机栈的主要区别在于，本地方法栈执行本地方法调用，而虚拟机栈执行Java方法调用。</p>



<h2 class="wp-block-heading">JVM中的程序计数器（Program Counter Register）的作用是什么？</h2>



<p>程序计数器 是JVM中的一个重要寄存器，用于存储当前线程执行的字节码指令地址。<br>在多线程环境下，每个线程都有独立的程序计数器，用于追踪线程执行的指令。它是线程私有的。<br>程序计数器在方法调用和返回时起到重要作用，确保线程能够正确执行字节码指令序列。</p>



<h2 class="wp-block-heading">什么是垃圾回收（Garbage Collection）？</h2>



<p>垃圾回收是一种自动管理内存的机制，用于检测和回收不再被程序使用的内存对象，以便释放内存资源并减少内存泄漏的风险。在Java中，垃圾回收是由Java虚拟机（JVM）自动执行的过程，它会自动回收不再被引用的对象，并将它们的内存释放出来。</p>



<h2 class="wp-block-heading">垃圾回收的目的是什么？</h2>



<ul class="wp-block-list">
<li>释放不再被引用的内存对象，以避免内存泄漏。</li>



<li>提高内存资源的有效利用，减少内存碎片化。</li>



<li>减少程序员手动管理内存的工作，提高开发效率。</li>



<li>增加应用程序的稳定性和可靠性，减少内存相关的错误。</li>
</ul>



<h2 class="wp-block-heading">常见的垃圾回收算法有哪些？</h2>



<ul class="wp-block-list">
<li><strong>标记-清除算法（Mark and Sweep）</strong>：首先标记不再使用的对象，然后清除（删除）它们。</li>



<li><strong>复制算法（Copying）</strong>：将存活的对象复制到新的内存区域，然后清除旧区域。</li>



<li><strong>标记-整理算法（Mark and Compact）： </strong>首先标记不再使用的对象，然后将存活的对象移到一侧，并清除未使用的内存。</li>



<li><strong>分代算法（Generational）：</strong>将内存划分为不同的代，年轻代和老年代，根据对象的生命周期分别采用不同的垃圾回收算法。</li>
</ul>



<h2 class="wp-block-heading">什么是“Stop-the-World”（停止时间）事件？</h2>



<p>“Stop-the-World”事件是指在进行垃圾回收时，JVM会暂停应用程序的执行，以执行垃圾回收操作。这意味着在某个时间点上，所有的应用程序线程都会被停止，直到垃圾回收完成。停止时间的长短取决于垃圾回收算法和实现，较长的停顿时间可能会影响应用程序的响应性能。<br>为了减少“Stop-the-World”事件的影响，一些垃圾回收器采用并发垃圾回收技术，允许垃圾回收与应用程序线程并发执行。</p>



<h2 class="wp-block-heading">如何手动触发垃圾回收？</h2>



<p>要手动触发垃圾回收，可以使用<strong>System.gc()</strong>方法或<strong>Runtime.getRuntime().gc()</strong>方法。但是，这只是请求垃圾回收，而不是强制执行。JVM可以选择忽略这个请求。手动触发垃圾回收的主要目的是建议JVM在某些情况下进行回收，但通常不建议频繁使用它，因为垃圾回收通常由JVM自动管理。</p>



<h2 class="wp-block-heading">什么是JVM的默认垃圾回收器？</h2>



<p>JVM的默认垃圾回收器通常取决于使用的Java版本和操作系统。在过去，一些JVM版本的默认垃圾回收器是Serial GC。但从Java 9开始，G1（Garbage-First） GC成为了默认的垃圾回收器。需要注意的是，不同的JVM实现可能会有不同的默认回收器。</p>



<h2 class="wp-block-heading">请列举一些常见的垃圾回收器。</h2>



<ul class="wp-block-list">
<li>Serial GC（串行垃圾回收器）</li>



<li>Parallel GC（并行垃圾回收器）</li>



<li>CMS GC（Concurrent Mark-Sweep，并发标记-清除垃圾回收器）</li>



<li>G1 GC（Garbage-First，垃圾优先垃圾回收器）</li>



<li>ZGC（Z Garbage Collector）</li>



<li>Shenandoah GC（Shenandoah Garbage Collector）</li>
</ul>



<h2 class="wp-block-heading">请描述Serial GC（串行）的工作原理。</h2>



<p>Serial GC是一种单线程的垃圾回收器，它的工作原理包括以下步骤：<br>首先，它会暂停所有应用程序线程（&#8221;Stop-the-World&#8221;事件）。<br>然后，它会标记不再使用的对象。<br>接着，它会清除标记的对象，回收内存空间。<br>最后，它会解除停顿，恢复应用程序线程的执行。<br>Serial GC适用于单线程应用程序或具有小堆内存的环境。</p>



<h2 class="wp-block-heading">请描述Parallel GC（并行）的工作原理。</h2>



<p>Parallel GC是一种多线程的垃圾回收器，它的工作原理包括以下步骤：<br>首先，它会暂停所有应用程序线程（&#8221;Stop-the-World&#8221;事件）。<br>然后，多个线程并行地标记不再使用的对象。<br>接着，多个线程并行地清除标记的对象，回收内存空间。<br>最后，它会解除停顿，恢复应用程序线程的执行。<br>Parallel GC适用于多核处理器环境，能够充分利用多核性能来加速垃圾回收。</p>



<h2 class="wp-block-heading">请描述CMS（Concurrent Mark-Sweep） GC的工作原理。</h2>



<p>CMS GC是一种并发垃圾回收器，它的工作原理包括以下步骤：<br>首先，它会标记不再使用的对象，但不会暂停应用程序线程。<br>然后，它会并发地清除标记的对象，回收内存空间。这一阶段不会停止应用程序线程。<br>最后，它会进行一次小的&#8221;Stop-the-World&#8221;事件，以处理并发清除过程中产生的新垃圾。<br>CMS GC旨在减少停顿时间，适用于需要低停顿时间的应用程序。</p>



<h2 class="wp-block-heading"><strong>请描述G1（Garbage-First） GC的工作原理。</strong></h2>



<p>G1 GC是一种面向大堆内存的垃圾回收器，其工作原理包括以下步骤：<br>首先，它将堆划分为多个区域，包括伊甸园区、幸存者区、老年代等。<br>然后，它会并发地标记不再使用的对象。<br>接着，它会根据各个区域的垃圾量优先回收垃圾。这个过程通常不会导致大的停顿。<br>最后，它会进行一次小的&#8221;Stop-the-World&#8221;事件，以处理剩余的垃圾。<br>G1 GC旨在实现更可预测的停顿时间，并且适用于大堆内存和需要低停顿时间的应用程序。</p>



<h2 class="wp-block-heading">JVM性能调优的目标是什么？</h2>



<ul class="wp-block-list">
<li>提高应用程序的响应性能。</li>



<li>最小化垃圾回收的停顿时间。</li>



<li>减少内存占用，避免内存泄漏。</li>



<li>最大程度地利用硬件资源，提高吞吐量。</li>



<li>优化应用程序的整体性能和稳定性。</li>
</ul>



<h2 class="wp-block-heading">如何选择适合应用程序的垃圾回收器？</h2>



<ul class="wp-block-list">
<li>应用程序的性能需求：低停顿时间、高吞吐量等。</li>



<li>堆内存大小：小堆和大堆需要不同的回收器。</li>



<li>硬件资源：多核CPU、内存大小等。</li>



<li>Java版本和JVM实现：不同版本和实现支持不同的回收器。</li>



<li>应用程序的特性：长时间运行、短时间运行、大对象等。</li>



<li>通常，可以开始使用默认的垃圾回收器，然后根据性能需求进行调优和选择。</li>
</ul>



<h2 class="wp-block-heading">什么是Xmx和Xms标志的作用？</h2>



<p><strong>-Xmx</strong>标志用于设置Java堆的最大可用内存大小，即堆的上限。<br><strong>-Xms</strong>标志用于设置Java堆的初始内存大小，即堆的下限。<br>这两个标志一起帮助控制Java应用程序的堆内存大小范围，以满足应用程序的内存需求。</p>



<h2 class="wp-block-heading">如何调整堆大小以优化性能？</h2>



<p>可以使用-Xmx和-Xms标志来调整堆大小。其中：<br><strong>-Xmx</strong>用于设置堆的最大大小，例如-Xmx2g表示将堆的最大大小设置为2GB。<br><strong>-Xms</strong>用于设置堆的初始大小，例如-Xms512m表示将堆的初始大小设置为512MB。<br>调整堆大小的目标是确保堆足够大，以容纳应用程序的内存需求，同时避免浪费过多内存。</p>



<h2 class="wp-block-heading">如何避免内存泄漏（Memory Leaks）？</h2>



<ul class="wp-block-list">
<li>及时释放不再使用的对象的引用。</li>



<li>使用弱引用或软引用来管理对象引用，使得对象可以被垃圾回收。</li>



<li>使用工具（如内存分析工具）来检测和分析内存泄漏。</li>



<li>注意关闭资源（如文件、数据库连接）以防止资源泄漏。</li>
</ul>



<h2 class="wp-block-heading">什么是“对象存活周期”（Object Lifecycle）？</h2>



<p>对象存活周期是指对象从创建到被垃圾回收的整个生命周期。它包括以下阶段：</p>



<ul class="wp-block-list">
<li>创建：对象被实例化。</li>



<li>存活：对象被引用并继续存在。</li>



<li>不可达：对象不再被引用，但尚未被回收。</li>



<li>回收：对象被垃圾回收器回收内存。</li>
</ul>



<p>理解对象的存活周期有助于识别内存泄漏和优化内存使用。</p>



<h2 class="wp-block-heading">为什么避免使用<code>finalize()</code>方法？</h2>



<p>finalize()方法是Java中用于对象清理的方法，但不建议使用它，因为它存在以下问题：</p>



<ul class="wp-block-list">
<li>不可靠性：不能保证finalize()方法何时被调用，可能导致内存泄漏。</li>



<li>性能开销：finalize()方法的调用会增加垃圾回收的开销。</li>



<li>失去控制：不容易管理资源的释放。</li>



<li>推荐使用try-with-resources或finally块来确保资源的及时释放。</li>
</ul>



<h2 class="wp-block-heading">如何减少对象的创建和销毁？</h2>



<ul class="wp-block-list">
<li>使用对象池或连接池，重复使用对象。</li>



<li>使用不可变对象，避免频繁的对象变化。</li>



<li>考虑使用单例模式，共享对象实例。</li>



<li>使用缓存来缓存常用的对象，减少创建开销。</li>
</ul>



<h2 class="wp-block-heading">为什么要使用缓存（Caching）？</h2>



<p>缓存是一种将计算结果或数据存储在内存中的技术，以加速对数据的访问。它可以提高应用程序的性能，减少对后端数据存储的访问频率，降低延迟。缓存通常用于存储频繁访问的数据，以提高响应性能。</p>



<h2 class="wp-block-heading">如何监控JVM性能？</h2>



<ul class="wp-block-list">
<li>使用JVM自带的JVisualVM、JConsole等监控工具。</li>



<li>使用第三方性能监控工具（如Prometheus、Grafana、New Relic等）。</li>



<li>收集并分析垃圾回收日志。</li>



<li>监控内存使用、线程数、CPU使用率等关键指标。</li>



<li>使用日志和度量库记录应用程序性能数据。</li>



<li>类加载和类加载器（Class Loading）</li>
</ul>



<h2 class="wp-block-heading">什么是类加载（Class Loading）？</h2>



<p>类加载是Java虚拟机（JVM）将字节码文件加载到内存中，并将其转换为可执行类的过程。在Java中，类加载是动态的，只有在使用类时才会进行加载。类加载器负责加载类文件，并将其定义为Java类的实例。类加载过程是Java的重要特性之一，它允许应用程序在运行时动态加载和使用类。</p>



<h2 class="wp-block-heading">JVM的类加载过程包括哪些步骤？</h2>



<ul class="wp-block-list">
<li><strong>加载（Loading）：</strong>加载器负责查找字节码文件，并将它们加载到内存中。</li>



<li><strong>验证（Verification）：</strong>验证器检查字节码文件的合法性和安全性。</li>



<li><strong>准备（Preparation）：</strong>为类的静态变量分配内存空间，并设置默认初始值。</li>



<li><strong>解析（Resolution）：</strong>将符号引用转换为直接引用，解析类、方法和字段。</li>



<li><strong>初始化（Initialization）：</strong>执行类的初始化代码块（静态初始化块）和静态变量初始化。</li>
</ul>



<h2 class="wp-block-heading">什么是类加载器（Class Loader）？</h2>



<p>类加载器是Java虚拟机（JVM）的一部分，负责加载类文件并生成Java类对象。类加载器的主要任务是将字节码文件转化为可执行的Java类实例。Java中有多个类加载器，它们按照一定的层次结构来组织和加载类。类加载器具有双亲委派模型，其中父类加载器会尝试加载类，只有在无法找到类的情况下才会委派给子类加载器。</p>



<h2 class="wp-block-heading">请列举不同类型的类加载器。</h2>



<ul class="wp-block-list">
<li>引导类加载器（Bootstrap Class Loader）： 负责加载Java核心类库，通常由JVM实现提供，不是Java类。</li>



<li>扩展类加载器（Extension Class Loader）： 负责加载Java扩展库，位于jre/lib/ext目录下。</li>



<li>应用程序类加载器（Application Class Loader）： 负责加载应用程序的类，通常位于类路径（Classpath）中。</li>



<li>除了这些标准类加载器，还可以自定义类加载器来加载特定的类。</li>
</ul>



<h2 class="wp-block-heading"><strong>什么是双亲委派模型（Parent-Delegation Model）？</strong></h2>



<p>双亲委派模型是Java类加载器的工作原则，根据这个模型，一个类加载器在尝试加载类时首先将请求委派给其父加载器。父加载器也会按照同样的方式将请求委派给其父加载器，依此类推，直到达到顶层的引导类加载器。只有当所有父加载器都无法加载类时，才会由当前加载器尝试加载。<br>这个模型的主要优势是确保类的唯一性和安全性，防止不同的加载器重复加载相同的类。</p>



<h2 class="wp-block-heading"><strong>为什么使用双亲委</strong>派<strong>模型？</strong></h2>



<ul class="wp-block-list">
<li>避免类的重复加载，提高类的唯一性。</li>



<li>提供了一种类加载器层次结构，有助于隔离不同的类加载器，确保类的安全性。</li>



<li>确保Java核心类库和扩展库的一致性，防止用户自定义类库覆盖标准库。</li>



<li>双亲委派模型有助于维护Java类加载器的整洁和有序结构。</li>
</ul>



<h2 class="wp-block-heading">什么是类加载器泄漏（ClassLoader Leak）？</h2>



<p>类加载器泄漏是指由于某种原因导致类加载器及其加载的类无法被垃圾回收，从而导致内存泄漏。这通常发生在动态创建和卸载类加载器的情况下，如果没有正确释放对类加载器的引用，加载的类及相关资源将无法被回收。<br>类加载器泄漏可能导致应用程序的内存消耗不断增加，最终导致内存耗尽和性能问题。</p>



<h2 class="wp-block-heading">什么是性能分析工具？</h2>



<p>性能分析工具是用于监测、诊断和优化应用程序性能的软件工具。它们可用于收集和分析应用程序的运行时数据，包括内存使用、CPU利用率、线程活动等，以帮助开发人员识别性能瓶颈并改进应用程序的性能。</p>



<h2 class="wp-block-heading">列举一些常用的性能分析工具。</h2>



<ul class="wp-block-list">
<li>JVisualVM： 一个免费的Java性能监视和诊断工具。</li>



<li>JProfiler： 商业性能分析工具，提供深入的分析和优化功能。</li>



<li>YourKit： 商业Java性能分析器，用于性能调优和内存分析。</li>



<li>Visual Studio Profiler： 用于.NET平台的性能分析工具。</li>



<li>Glowroot： 免费的开源Java性能监控工具。</li>



<li>New Relic： 云端性能监控服务，支持多种编程语言。</li>
</ul>



<h2 class="wp-block-heading">什么是JVM监控工具？提供一些示例。</h2>



<ul class="wp-block-list">
<li>JVisualVM： 免费的JVM监控和分析工具，包含在JDK中。</li>



<li>JConsole： 免费的JVM监控工具，用于监视JVM的各种性能指标。</li>



<li>VisualVM： 与JVisualVM类似的工具，提供了更多扩展和插件。</li>



<li>Java Mission Control（JMC）： 一种商业工具，用于监视、管理和诊断Java应用程序。</li>



<li>AppDynamics： 商业应用性能监控工具，可监视Java应用程序的性能。</li>
</ul>



<h2 class="wp-block-heading">如何使用JVisualVM监控JVM？</h2>



<p>启动JVisualVM：在JDK的bin目录中找到jvisualvm.exe（Windows）或jvisualvm（Linux/macOS）并运行它。<br>在JVisualVM中，选择要监控的Java进程。<br>在监视器标签页中，您可以查看各种性能指标、线程活动和内存使用情况。<br>可以使用插件和扩展来进一步扩展JVisualVM的功能。<br>JVisualVM还支持通过JMX连接到远程JVM进行监控和诊断。</p>



<h2 class="wp-block-heading">如何生成和分析GC日志？</h2>



<p><strong>生成GC日志：</strong><br>启动Java应用程序时，使用以下标志之一来启用GC日志：<br>-Xloggc:：将GC日志输出到指定文件。<br>-XX:+PrintGCTimeStamps：打印GC时间戳。<br>-XX:+PrintGCDateStamps：打印GC日期时间戳。<br>-XX:+PrintGCDetails：打印GC详细信息。<br><strong>分析GC日志：</strong><br>收集应用程序的GC日志文件。<br>使用工具（如G1 GC日志分析器、VisualVM、YourKit等）来分析GC日志。<br>分析日志以识别GC事件、停顿时间、内存占用等信息。<br>根据分析结果采取措施来优化应用程序的内存管理和性能。<br>GC日志分析可以帮助识别内存泄漏、性能问题和垃圾回收器的性能瓶颈。</p>



<h2 class="wp-block-heading">什么是JVM参数（JVM Options）？</h2>



<p>JVM参数是用于配置Java虚拟机（JVM）运行时行为和性能特性的设置。这些参数可以控制堆内存大小、垃圾回收器的选择、线程数量、性能监控等。JVM参数以-或-D开头，例如-Xmx512m和-Dmy.property=value。</p>



<h2 class="wp-block-heading">如何设置JVM参数？</h2>



<p>VM参数可以通过以下方式设置：<br>命令行参数：在启动Java应用程序时，可以使用命令行参数将JVM参数传递给java命令，例如java -Xmx512m -cp MyApp.jar。<br>环境变量：可以通过设置_JAVA_OPTIONS环境变量来指定默认的JVM参数。<br>配置文件：可以在jvm.options或其他配置文件中指定JVM参数。<br>Java代码：在Java代码中使用System.setProperty()方法设置系统属性，例如System.setProperty(&#8220;my.property&#8221;, &#8220;value&#8221;)。</p>



<h2 class="wp-block-heading">常用的 JVM 参数有哪些？</h2>



<ul class="wp-block-list">
<li>堆设置：<code>-Xms</code>（初始堆大小）、<code>-Xmx</code>（最大堆大小）、<code>-Xmn</code>（新生代大小）</li>



<li>栈设置：<code>-Xss</code>（栈大小）</li>



<li>方法区：<code>-XX:MetaspaceSize</code>、<code>-XX:MaxMetaspaceSize</code></li>



<li>GC 日志：<code>-XX:+PrintGCDetails</code>、<code>-Xloggc:gc.log</code></li>



<li>内存溢出：<code>-XX:+HeapDumpOnOutOfMemoryError</code></li>



<li>选择 GC：<code>-XX:+UseG1GC</code>、<code>-XX:+UseParallelGC</code></li>
</ul>



<h2 class="wp-block-heading"><strong>什么是-Xmx和-Xms标志的作用？</strong></h2>



<p>-Xmx标志用于设置Java堆的最大可用内存大小，即堆的上限。例如，-Xmx512m表示将堆的最大大小设置为512MB。<br>-Xms标志用于设置Java堆的初始内存大小，即堆的下限。例如，-Xms256m表示将堆的初始大小设置为256MB。<br>这两个标志一起用来控制Java应用程序的堆内存大小，以满足应用程序的内存需求。</p>



<h2 class="wp-block-heading">如何打开和关闭逃逸分析（Escape Analysis）？</h2>



<p>逃逸分析是JVM的一项优化技术，用于识别局部对象的生命周期是否超出了方法的范围。逃逸分析可以在不分配对象的情况下优化代码，提高性能。在大多数情况下，JVM会自动启用逃逸分析。<br>若要显式控制逃逸分析，可以使用JVM参数：<br>-XX:+DoEscapeAnalysis：启用逃逸分析。<br>-XX:-DoEscapeAnalysis：禁用逃逸分析。<br>默认情况下，通常不需要手动配置逃逸分析，因为JVM会自动选择是否使用它。</p>



<h2 class="wp-block-heading">如何设置堆外内存大小？</h2>



<p>堆外内存是指位于Java堆之外的内存，通常由直接内存（Direct Memory）所使用，用于NIO（New I/O）操作和本地内存分配。可以使用以下JVM参数来设置堆外内存大小：<br>-XX:MaxDirectMemorySize=：设置堆外内存的最大大小，其中可以是以字节为单位的数值，例如-XX:MaxDirectMemorySize=256m表示将堆外内存的最大大小设置为256MB。<br>注意，堆外内存的大小不受Java堆大小限制，因此需要谨慎设置，以避免影响应用程序的性能和稳定性。</p>



<h2 class="wp-block-heading">什么是锁竞争（Lock Contention）？</h2>



<p>锁竞争（Lock Contention）是多线程编程中的一种情况，其中多个线程尝试同时访问共享资源或临界区域，并且只有一个线程能够成功访问，其他线程必须等待。这种等待会导致性能下降，因为线程在等待锁的释放时无法执行其他有用的工作，从而浪费了计算资源。</p>



<h2 class="wp-block-heading">如何避免锁竞争？</h2>



<p>使用细粒度的锁：将共享资源分成多个较小的部分，并为每个部分使用不同的锁，以减少线程之间的竞争。<br>使用无锁数据结构：一些数据结构，如无锁队列和无锁哈希表，可以减少锁的使用，从而减少锁竞争。<br>使用读写锁：对于读多写少的场景，可以使用读写锁来允许多个线程同时读取共享数据，而只有一个线程能够写入。<br>使用并发集合：Java提供了一些并发集合类（如ConcurrentHashMap），它们内部使用了各种技术来减少锁竞争。</p>



<h2 class="wp-block-heading">什么是线程池（ThreadPool）？如何使用它来提高性能？</h2>



<p>线程池（ThreadPool）是一组维护和管理线程的工具，它们在需要执行任务时从线程池中获取线程，执行任务后将线程返回到池中，以便重用。线程池的目的是减少线程的创建和销毁开销，提高应用程序的性能和资源利用率。通过使用线程池，可以更有效地管理大量的并发任务，控制线程的数量，避免资源竞争和线程创建销毁的开销。</p>



<h2 class="wp-block-heading">什么是延迟加载（Lazy Loading）？</h2>



<p>延迟加载（Lazy Loading）是一种设计模式，它延迟加载对象或资源，直到它们被真正需要的时候才进行加载。这可以帮助减少程序启动时间和内存占用，因为不需要在一开始就加载所有可能需要的对象或资源。</p>



<h2 class="wp-block-heading">如何利用延迟加载提高性能？</h2>



<ul class="wp-block-list">
<li>使用单例模式：将对象的创建延迟到首次访问时，以减少启动时的资源消耗。</li>



<li>惰性初始化：只有在需要的时候才初始化对象，而不是在程序启动时就进行初始化。</li>



<li>缓存：将已加载的对象或资源缓存起来，以便后续访问时可以快速获取，而不必重新加载。</li>
</ul>



<h2 class="wp-block-heading">什么是Java内联（Inlining）？如何使用它来提高性能？</h2>



<p>Java内联（Inlining）是一种编译器优化技术，它将方法调用替换为方法体的实际代码，从而减少方法调用的开销。内联可以提高程序的性能，因为它减少了方法调用的栈帧创建和销毁开销，以及跳转到方法体的开销。在Java中，编译器会自动进行一些内联优化，但也可以使用注解（如@Inline）来强制编译器进行内联优化。<br>要使用内联来提高性能，需要注意以下几点：<br>内联并不适用于所有方法，只有一些较小的、频繁调用的方法才适合内联。<br>过度内联可能会导致代码膨胀，增加代码大小，降低缓存命中率，因此需要权衡内联的使用。<br>在Java中，可以使用编译器选项或注解来控制内联行为，但不同的JVM实现可能会有不同的内联策略。</p>



<h2 class="wp-block-heading"><strong>什么是Visual</strong>V<strong>M？</strong></h2>



<p>VisualVM（Visual Virtual Machine）是一个用于监视、管理和分析Java应用程序性能的开源工具。它提供了丰富的功能，包括实时性能监控、内存分析、线程分析、堆转储分析等，可以帮助开发人员识别和解决Java应用程序中的性能问题。VisualVM通常包括在JDK（Java Development Kit）中，并且是一个强大的工具，特别适用于开发和调试Java应用程序</p>



<h2 class="wp-block-heading">什么是JProfiler？</h2>



<p>JProfiler是一款商业性能分析工具，用于分析Java应用程序的性能和内存使用情况。它提供了多种性能分析工具和可视化界面，能够帮助开发人员识别和解决性能瓶颈、内存泄漏等问题。JProfiler具有强大的性能分析和调试功能，适用于复杂的Java应用程序性能优化。</p>



<h2 class="wp-block-heading">什么是YourKit？</h2>



<p>YourKit是另一款商业性能分析工具，用于Java、.NET和Node.js应用程序的性能分析。它提供了各种性能分析工具、堆转储分析、线程分析等功能，以帮助开发人员识别和解决性能问题。YourKit被广泛用于Java应用程序的性能调优和优化。</p>



<h2 class="wp-block-heading">什么是Java Flight Recorder（JFR）？</h2>



<p>Java Flight Recorder（JFR）是Java平台自带的性能分析工具，它用于收集和记录Java应用程序的性能数据，包括CPU使用率、内存使用情况、线程活动等。JFR可以通过Java命令行参数启用，并生成事件数据文件，开发人员可以使用Java Mission Control等工具来分析和可视化这些数据，以帮助诊断和解决性能问题。JFR通常用于生产环境中进行性能分析和故障排除。</p>



<h2 class="wp-block-heading">什么是内存溢出错误（OutOfMemoryError）？有哪些常见的内存溢出错误类型？</h2>



<p>内存溢出错误（OutOfMemoryError）是一种Java程序运行时错误，表示程序试图分配更多内存，但没有足够的内存可供使用。内存溢出错误是由于程序中创建的对象太多，而Java虚拟机（JVM）的堆内存不足以容纳这些对象而引起的。常见的内存溢出错误类型包括：<br>java.lang.OutOfMemoryError: Java heap space：表示堆内存溢出。<br>java.lang.OutOfMemoryError: PermGen space（在旧版本的JVM中）：表示永久代内存溢出。<br>java.lang.OutOfMemoryError: Metaspace（在新版本的JVM中）：表示元空间内存溢出。<br>java.lang.OutOfMemoryError: GC overhead limit exceeded：表示垃圾回收开销过大。</p>



<h2 class="wp-block-heading">如何排查内存溢出错误？</h2>



<p>查看错误信息和堆栈跟踪，确定是哪种内存溢出错误类型。<br>使用Java监控工具（如VisualVM、JProfiler）或分析工具（如MAT &#8211; Memory Analyzer Tool）来分析堆内存的使用情况，找出内存泄漏的原因。<br>检查程序中是否有不合理的对象引用，可能导致无法回收的对象堆积。<br>增加JVM堆内存的大小，如果是堆内存溢出问题。<br>优化代码，减少对象的创建和持有，释放不再使用的资源。</p>



<h2 class="wp-block-heading">什么是死锁（Deadlock）？如何排查和预防死锁？</h2>



<p>死锁（Deadlock）是多线程编程中的一种情况，其中两个或多个线程相互等待对方释放资源，从而导致所有线程无法继续执行。要排查和预防死锁，可以采取以下措施：<br>使用线程安全的锁策略，确保所有线程按照相同的顺序获取锁。<br>使用锁超时机制，如果线程无法获取所需的锁，可以等待一段时间后放弃锁。<br>使用线程池和并发工具类，而不是手动管理线程和锁。<br>使用工具来分析和诊断死锁，如线程转储和监控工具。<br>避免嵌套锁，如果必须使用多个锁，请确保它们的顺序是一致的。</p>



<h2 class="wp-block-heading">如何分析线程转储（Thread Dump）？</h2>



<p>分析线程转储（Thread Dump）是诊断多线程应用程序问题的关键步骤之一。以下是一般的线程转储分析步骤：<br>获取线程转储：在应用程序运行时，可以使用工具（如jstack命令，VisualVM等）来生成线程转储。通常，您可以通过以下方式获取线程转储：<br>使用jstack命令：运行jstack ，其中是目标Java进程的进程ID。<br>使用监控工具：如果您使用监控工具（如VisualVM、JConsole等），通常可以通过工具界面生成线程转储。<br>在发生问题时生成线程转储：如果应用程序出现性能问题或死锁，您可以使用操作系统工具来生成线程转储，例如在Unix/Linux系统上可以使用kill -3 命令。<br>查看线程状态：打开生成的线程转储文件（通常是文本文件），查看线程列表。了解每个线程的状态，例如RUNNABLE（运行中）、WAITING（等待中）、BLOCKED（阻塞中）等。<br>分析堆栈跟踪：对于每个线程，查看其堆栈跟踪以了解线程执行的代码路径。堆栈跟踪通常包含了方法和类名，帮助您确定问题代码的位置。<br>查找共享资源和锁信息：如果线程转储包含锁信息，查看哪些线程正在等待获取哪些锁，以及哪些线程已经持有锁。这有助于识别死锁或资源争用问题。<br>识别问题线程：找出占用CPU高或处于异常状态的线程，以及哪些线程可能导致了性能问题或死锁。<br>使用性能分析工具：如果需要更深入的分析，您可以使用性能分析工具（如VisualVM、JProfiler、YourKit等）来可视化和交互式地分析线程转储数据。这些工具通常提供更强大的分析功能，有助于更轻松地识别问题。<br>解决问题：根据分析的结果，采取必要的措施来解决线程问题。这可能包括优化代码、解决死锁、减少线程争用等。<br>监控和预防：定期监控应用程序的线程情况，以及线程转储文件，以便在早期发现和解决问题。采取预防措施，如合理的锁策略、避免死锁、资源池管理等，以减少线程问题的发生。<br>线程转储分析通常需要一定的经验和技能，特别是在处理复杂的多线程应用程序时。性能分析工具可以大大简化这个过程，并提供更多的可视化信息，有助于更快地定位和解决问题。</p>



<h2 class="wp-block-heading">什么是CPU占用高（High CPU Usage）问题？如何排查？</h2>



<p>CPU占用高（High CPU Usage）问题是指应用程序或进程占用了大量的CPU资源，导致系统负载升高、性能下降或系统变得不响应。要排查高CPU占用问题，可以采取以下步骤：</p>



<ul class="wp-block-list">
<li>监控CPU使用率：使用系统监控工具（如top、htop、Windows任务管理器）来检查哪个进程或线程占用了大量的CPU资源。</li>



<li>查看线程转储：如果CPU占用问题与多线程应用程序相关，生成线程转储并查看问题线程的堆栈跟踪，以了解问题的代码路径。</li>



<li>分析代码：确定占用CPU的代码部分。查看哪些方法或循环消耗了大量的CPU时间。</li>



<li>检查循环：查看是否存在无限循环或非预期的循环条件，这可能导致CPU占用问题。</li>



<li>检查阻塞操作：查看是否存在长时间的阻塞操作，例如等待网络或文件系统操作，这可能导致CPU资源浪费。</li>



<li>使用性能分析工具：使用性能分析工具（如VisualVM、JProfiler、YourKit）来更详细地分析CPU占用问题。这些工具可以提供更多的可视化数据和分析功能。</li>



<li>优化代码：根据分析结果采取必要的措施来优化代码，减少CPU占用。这可能包括改进算法、减少不必要的计算或循环、并行化任务等。</li>



<li>避免不必要的轮询：如果应用程序中存在轮询机制，尽量避免过于频繁的轮询，采用事件驱动或异步机制。</li>



<li>增加硬件资源：如果必要，增加CPU核心数量或升级硬件来缓解CPU占用问题。</li>
</ul>



<p>通过以上步骤，您可以确定高CPU占用问题的原因，并采取适当的措施来解决问题，从而提高应用程序的性能。</p>



<h2 class="wp-block-heading">什么是“惰性初始化”（Lazy Initialization）？</h2>



<p>惰性初始化（Lazy Initialization）是一种设计模式，它延迟对象或资源的创建或初始化，直到它们被首次访问或需要的时候才进行初始化。这种延迟初始化可以帮助减少应用程序的启动时间和内存消耗，因为不需要在一开始就创建和初始化所有可能不会立即使用的对象。惰性初始化常用于单例模式中，其中单例对象只有在首次访问时才会被创建。</p>



<h2 class="wp-block-heading">什么是“双重检查锁定”（Double-Checked Locking）？</h2>



<p>双重检查锁定（Double-Checked Locking）是一种多线程编程的设计模式，用于实现惰性初始化的对象的创建。在双重检查锁定模式中，首先检查对象是否已经被创建，如果没有则获取锁，并在锁内再次检查对象是否已经被创建，如果没有则创建对象。这个模式的目的是在多线程环境下避免重复创建对象，同时减少锁的竞争。需要注意的是，双重检查锁定模式在某些编程语言中需要特殊的线程安全性保证才能正常工作。</p>



<h2 class="wp-block-heading">什么是“对象池”（Object Pool）？</h2>



<p>对象池（Object Pool）是一种创建和管理对象的设计模式，其目的是减少对象的创建和销毁开销，提高性能和资源利用率。对象池维护一个池子，其中包含一组可重复使用的对象。当需要对象时，从池中获取，使用完后将其返回到池中，而不是创建新对象。对象池特别适用于那些创建成本高昂的对象，如数据库连接、线程等。</p>



<h2 class="wp-block-heading">什么是“多线程模式”（Multithreaded Patterns）？</h2>



<p>多线程模式（Multithreaded Patterns）是一组设计模式，用于解决多线程编程中的常见问题和挑战。这些模式包括单例模式、生产者-消费者模式、读写锁模式、线程池模式等，它们提供了可重用的解决方案，有助于简化多线程编程中的复杂性，并提高程序的性能和可维护性。</p>



<h2 class="wp-block-heading">什么是“缓存模式”（Caching Patterns）？</h2>



<p>缓存模式（Caching Patterns）是一组设计模式，用于优化应用程序的性能，通过在内存中存储常用的数据或计算结果，从而减少重复的计算或数据库访问。常见的缓存模式包括本地缓存、分布式缓存、页面缓存等。缓存模式可以显著降低系统的负载，并加速数据访问，但需要谨慎管理缓存的一致性和过期策略，以避免数据不一致或过时的问题。</p>



<h2 class="wp-block-heading">什么是Java 8中的“永久代”（PermGen）？为什么被移除？</h2>



<p>Java 8 中的 &#8220;永久代&#8221;（PermGen）是 Java 虚拟机（JVM）中的一部分内存区域，用于存储类的元数据、静态变量和常量池等信息。永久代有一个固定的大小，因此在某些情况下可能会导致内存不足的问题，特别是在运行时频繁加载大量类的情况下。永久代不受垃圾回收器的管理，这也可能导致内存泄漏问题。<br>Java 8 中永久代被移除的原因是引入了一个新的内存区域称为 &#8220;元空间&#8221;（Metaspace），元空间更加灵活，可以根据需要动态分配内存，不再有固定大小的限制。元空间还可以受到垃圾回收的管理，因此避免了永久代的一些问题，如内存泄漏和永久代大小的调优问题。这个改变提高了 JVM 的可用性和稳定性。</p>



<h2 class="wp-block-heading">Java 9引入了哪些新特性？</h2>



<ul class="wp-block-list">
<li>模块系统（模块化编程）：引入了模块化系统，使得应用程序可以更好地管理和组织代码，提高了可维护性和可扩展性。</li>



<li>JShell（交互式编程环境）：引入了一个交互式编程环境，允许开发人员在命令行中编写和执行Java代码片段，用于快速尝试和测试代码。</li>



<li>HTTP/2 客户端：引入了支持 HTTP/2 协议的原生 HTTP 客户端，提供了更快的网络通信性能。</li>



<li>改进的性能：Java 9 包括了许多性能改进，包括 G1 垃圾回收器的改进和新的编译器接口。</li>



<li>新的API：引入了一些新的API，如 Flow API 用于响应式编程、ProcessHandle API 用于处理本地进程、HttpClient API 用于处理HTTP请求等。</li>



<li>私有接口方法：允许在接口中定义私有方法，以提高代码重用性和可读性。</li>



<li>集合工厂方法：引入了一组便捷的工厂方法，用于创建不可变集合。</li>
</ul>



<h2 class="wp-block-heading">Java 11的重要变化有哪些？</h2>



<ul class="wp-block-list">
<li>HTTP 客户端的正式发布：Java 11 将 HTTP 客户端模块（java.net.http）正式发布，以替代不稳定的 HTTP/2 客户端，提供更强大的 HTTP 请求和响应功能。</li>



<li>本地变量类型推断：引入了局部变量的类型推断，允许使用 var 关键字来声明局部变量，减少样板代码。</li>



<li>单一文件执行：Java 11 引入了单一文件执行功能，允许通过命令行运行单个源文件而不需要显式编译。</li>



<li>ZGC 垃圾回收器的稳定版本：Z Garbage Collector（ZGC）成为 Java 11 中的一个稳定特性，它提供了低停顿时间的垃圾回收。</li>



<li>Epsilon 垃圾回收器：引入了 Epsilon 垃圾回收器，它是一种不执行垃圾回收的垃圾回收器，主要用于性能测试和性能调优。</li>



<li>Unicode 10 支持：Java 11 支持 Unicode 10.0 版本，包括新的字符和符号。</li>
</ul>



<h2 class="wp-block-heading"><strong>什么是性能测试（Performance Testing）？</strong></h2>



<p>性能测试（Performance Testing）是一种软件测试方法，旨在评估应用程序或系统的性能，包括其响应时间、吞吐量、稳定性和资源利用率等方面的表现。性能测试通常用于发现和诊断性能问题，确保应用程序在不同负载和条件下都能正常运行，并且能够满足性能需求和期望。性能测试可以帮助确定系统在特定负载下的性能极限，同时也可以用来比较不同版本或配置的性能。<br>性能测试的主要类型包括：</p>



<ul class="wp-block-list">
<li>负载测试：评估应用程序在不同负载条件下的性能表现。</li>



<li>压力测试：测试应用程序在超过其正常负载的情况下的性能。</li>



<li>稳定性测试：测试应用程序在长时间运行或高负载下的稳定性和可靠性。</li>



<li>容量规划测试：评估系统的性能极限，以确定是否需要扩展资源。</li>
</ul>



<h2 class="wp-block-heading">什么是基准测试（Benchmarking）？</h2>



<p>基准测试（Benchmarking）是一种性能测试的子集，它专注于测量和比较不同软件、硬件或配置的性能。基准测试的目标是确定特定配置或实现的性能水平，通常与其他配置或实现进行比较。基准测试通常用于评估硬件设备、编程语言、算法或库的性能，以做出优化或选择决策。基准测试通常不仅关注应用程序整体的性能，还可能关注某个特定的函数、模块或组件的性能。</p>



<h2 class="wp-block-heading">如何编写基准测试用例？</h2>



<p>要编写基准测试用例，通常需要遵循以下步骤：<br>选择基准测试工具：选择适合您需求的基准测试框架或工具，如 JMH（Java Microbenchmarking Harness）、Apache Benchmark、wrk 等。<br>定义基准测试目标：明确定义您要测量的性能指标和目标。这可能包括吞吐量、延迟、资源利用率等。<br>创建测试场景：编写基准测试用例，包括测试代码和测试数据。测试场景应该模拟实际应用程序的使用情况。<br>运行基准测试：使用选定的基准测试工具运行测试，并记录性能指标的结果。<br>分析和比较结果：分析测试结果，比较不同配置或实现的性能。识别性能瓶颈和潜在优化点。<br>优化和重复测试：根据测试结果进行优化，然后重复测试，直到满足性能目标或找到最佳配置。</p>



<h2 class="wp-block-heading">什么是JMH（Java Microbenchmarking Harness）？</h2>



<p>JMH（Java Microbenchmarking Harness）是一个用于编写、运行和分析Java微基准测试的框架。它是由OpenJDK项目提供的，并且被广泛用于评估和比较Java代码片段的性能。<br>JMH 提供了丰富的功能，使得编写准确、可靠的微基准测试变得更容易。它可以处理许多微基准测试的挑战，包括优化代码、避免JVM热身效应、消除测量误差等。JMH的用法包括定义基准测试方法、配置测试参数、运行测试、分析测试结果等。<br>JMH 的优点包括高度可靠的测量、自动优化消除、多线程测试支持等。它是Java社区中进行性能测量和优化的首选工具之一。</p>



<h2 class="wp-block-heading">JVM的安全性是什么？有哪些安全性考虑？</h2>



<p>JVM的安全性指的是Java虚拟机（JVM）及其运行Java应用程序的环境所具有的安全性特性和保护机制。JVM的安全性考虑通常包括以下方面：</p>



<ul class="wp-block-list">
<li>字节码验证：JVM会在加载Java类文件时对字节码进行验证，以确保它们遵循Java语言规范，防止恶意字节码的执行。</li>



<li>访问控制：JVM通过类加载器和安全管理器（Security Manager）来控制类和资源的访问权限，以防止未经授权的访问。</li>



<li>内存管理：JVM管理内存分配和垃圾回收，以防止内存泄漏和溢出攻击。</li>



<li>安全沙箱：JVM可以创建安全沙箱来隔离受信任和不受信任的代码，以限制潜在的恶意行为。</li>



<li>加密和身份验证：JVM提供了加密和身份验证的API，以加强应用程序的安全性。</li>



<li>安全管理器：JVM的安全管理器可用于实施自定义的安全策略，以控制应用程序的行</li>
</ul>



<h2 class="wp-block-heading">什么是安全沙箱（Security Sandbox）？</h2>



<p>安全沙箱（Security Sandbox）是一种安全机制，用于隔离受信任和不受信任的代码，以限制不受信任的代码对系统的访问权限。在Java中，安全沙箱通常指的是将不受信任的Java代码（例如来自未知来源的Applet或外部插件）限制在受限的执行环境中，以确保其不会对系统造成损害。<br>安全沙箱通常通过以下方式实现：</p>



<ul class="wp-block-list">
<li>限制文件系统访问。</li>



<li>限制网络访问。</li>



<li>限制系统资源（如CPU、内存）的使用。</li>



<li>阻止本机代码的执行。</li>



<li>禁止敏感操作，如文件删除或系统属性更改。</li>
</ul>



<h2 class="wp-block-heading">JVM安全管理器（Security Manager）的作用是什么？</h2>



<p>JVM安全管理器（Security Manager）是Java平台的一部分，用于实施安全策略以控制Java应用程序的行为。安全管理器通过允许或拒绝特定操作来保护Java应用程序免受潜在的不安全行为的影响。它可以用于实现诸如访问控制、文件系统访问限制、网络访问限制等安全策略。<br>安全管理器的主要作用包括：</p>



<ul class="wp-block-list">
<li>防止未经授权的文件系统访问。</li>



<li>防止未经授权的网络访问。</li>



<li>防止未经授权的系统资源访问。</li>



<li>防止恶意代码的执行。</li>
</ul>



<p>开发人员可以通过配置安全管理器来定义自定义的安全策略，以满足特定应用程序的安全需求。</p>



<h2 class="wp-block-heading">什么是Java安全性漏洞？</h2>



<p>Java安全性漏洞是指Java平台中可能存在的安全问题或漏洞，这些漏洞可能导致恶意行为、数据泄漏或系统崩溃。Java安全性漏洞可能涉及多个方面，包括：</p>



<ul class="wp-block-list">
<li>代码执行漏洞：允许不受信任的代码执行恶意操作。</li>



<li>权限提升漏洞：允许攻击者提升其权限以执行受限操作。</li>



<li>拒绝服务漏洞：可能导致应用程序或系统不可用。</li>



<li>数据泄漏漏洞：可能导致敏感数据泄露给攻击者。</li>



<li>跨站脚本（XSS）漏洞：可能导致恶意脚本在用户的浏览器中执行。</li>
</ul>



<p>为了应对Java安全性漏洞，Java开发团队会定期发布安全更新，修复已知漏洞，并提供建议和最佳实践，以帮助开发人员编写更安全的Java应用程序。因此，及时更新Java运行时环境（JRE）和遵循安全最佳实践对于保持Java应用程序的安全性至关重要。</p>



<h2 class="wp-block-heading">JVM如何与操作系统交互？它如何管理系统资源？</h2>



<p>JVM与操作系统的交互通常通过Java本机接口（Java Native Interface，JNI）来实现，这是Java平台的一项功能，允许Java代码与本机（C、C++等）代码进行交互。通过JNI，Java代码可以调用本机方法，这些本机方法可以直接与操作系统进行交互，访问操作系统的功能和资源。<br>JVM管理系统资源的方式包括：</p>



<ul class="wp-block-list">
<li>内存管理：JVM负责分配和回收内存，以满足Java程序的内存需求。它通过垃圾回收器来回收不再使用的内存，并将内存释放给操作系统。</li>



<li>线程管理：JVM负责创建、启动和管理Java线程，这些线程映射到操作系统的本机线程。JVM通过线程调度器来协调线程的执行。</li>



<li>文件和网络IO：JVM通过Java标准库提供了对文件和网络IO的访问，这些库在底层使用操作系统提供的API来实现。</li>



<li>安全性管理：JVM通过Java安全管理器来管理Java程序的安全性，限制不受信任的代码对系统资源的访问。</li>



<li>本机方法调用：JVM通过JNI允许Java代码调用本机方法，这些本机方法可以执行操作系统特定的操作。</li>
</ul>



<h2 class="wp-block-heading">什么是本地方法（Native Method）？如何调用本地方法？</h2>



<p>本地方法（Native Method）是一种Java方法，它的实现是用本机编程语言（如C、C++）编写的，并且通过Java Native Interface（JNI）与Java代码进行交互。本地方法通常用于执行与操作系统或底层硬件相关的任务，或者是对性能要求非常高的任务。<br>要调用本地方法，需要执行以下步骤：<br>编写本地方法的本机代码，通常是C或C++代码，并将其编译成共享库（例如，动态链接库.so或.dll文件）。<br>在Java代码中使用native关键字声明本地方法，并提供本机方法的声明。<br>使用JNI库中的函数来加载本机库和链接本地方法的实现。<br>在Java代码中调用本地方法，JVM将在运行时加载和执行本地方法的实现。</p>



<pre class="wp-block-code"><code>public class NativeExample {
    // 使用native关键字声明本地方法
    public native void nativeMethod();

    public static void main(String&#91;] args) { 
        // 加载本机库 System.loadLibrary("nativeLibrary"); 
        // 创建对象并调用本地方法 
        NativeExample example = new NativeExample();
        example.nativeMethod();
    }
}</code></pre>



<h2 class="wp-block-heading">提供一个实际的JVM性能优化案例，包括问题诊断和优化策略。</h2>



<p>JVM性能优化案例：<br><strong>问题诊断：</strong><br>问题：Java应用程序的响应时间较长，导致性能下降。<br>诊断：使用性能分析工具（如VisualVM或JProfiler）检查应用程序，发现了一些瓶颈代码段。<br>优化策略：</p>



<ul class="wp-block-list">
<li>优化：重构瓶颈代码以减少不必要的锁竞争。</li>



<li>优化：通过使用本机线程池减少线程的创建开销。</li>



<li>优化：对数据库访问进行缓存以减少IO开销。</li>



<li>优化：调整垃圾回收策略以减少停顿时间。</li>
</ul>



<p><strong>结果：</strong><br>优化后，应用程序的响应时间显著缩短，性能得到了改善。<br>优化大规模应用程序的JVM性能通常需要综合考虑多个方面，包括代码、内存、线程、垃圾回收和IO等。以下是一些通用的优化策略：<br>代码优化：优化代码以减少不必要的循环和计算，使用合适的数据结构和算法，减少同步和锁竞争。<br>内存管理：调整堆内存大小以确保足够的内存可用，避免内存泄漏，优化对象的创建和销毁，使用对象池来减少对象的创建开销。<br>多线程优化：合理设计多线程架构，使用线程池来减少线程的创建和销毁，使用线程安全的数据结构，避免死锁和竞争条件。<br>垃圾回收优化：选择合适的垃圾回收器和参数，减少垃圾回收的频率和停顿时间，避免内存泄漏。<br>IO优化：使用非阻塞IO或异步IO来提高IO性能，避免频繁的文件和网络操作。|<br>性能监控：使用性能监控工具来识别性能瓶颈，及时发现和解决问题。<br>缓存优化：使用适当的缓存策略来加速数据访问，减少对后端系统的请求。<br>分布式架构优化：在大规模应用程序中，考虑分布式架构的优化，包括负载均衡、缓存、分片等策略。<br>综合考虑这些方面，并根据具体的应用场景进行优化，可以显著改善大规模应用程序的JVM性能。不过，请注意，优化是一个持续的过程，需要不断监测和调整以适应变化的需求和负载。</p>



<h2 class="wp-block-heading">如何优化大规模应用程序的JVM性能？</h2>



<p>优化大规模应用程序的JVM性能是一个复杂的任务，需要综合考虑多个因素。以下是一般性的优化策略：</p>



<ul class="wp-block-list">
<li>性能监控：使用性能监控工具来收集应用程序的性能数据，包括CPU使用率、内存使用、线程情况、GC活动等。</li>



<li>性能测试：进行负载测试和性能测试，以模拟实际负载条件，识别性能瓶颈和问题。</li>



<li>JVM参数调优：调整JVM参数，如堆大小、垃圾回收器、线程池大小等，以满足应用程序的需求。</li>



<li>内存管理：管理内存分配和回收，避免内存泄漏，使用对象池来减少对象创建。</li>



<li>多线程优化：避免过度的线程同步和锁竞争，使用线程池来降低线程创建开销。</li>



<li>IO优化：使用非阻塞IO或异步IO来提高IO性能，减少IO操作的次数。</li>



<li>代码优化：重构性能瓶颈的代码，减少不必要的计算和循环。</li>



<li>数据库优化：优化数据库查询和访问模式，使用数据库连接池来管理连接。</li>



<li>分布式优化：在大规模应用中，考虑分布式架构的优化，包括负载均衡、缓存策略等。</li>



<li>安全性能权衡：在安全性和性能之间找到平衡，不要过度限制性能以提高安全性。</li>



<li>定期审查和调整：性能优化是一个持续的过程，需要定期审查和调整策略以适应变化的需求和负载。</li>
</ul>



<h2 class="wp-block-heading">您在JVM性能调优方面的经验是什么？</h2>



<h2 class="wp-block-heading">在面对性能问题时，您通常采取什么步骤来排查和解决问题？</h2>



<ul class="wp-block-list">
<li>确定性能问题：首先确认性能问题是否存在，通过性能监控工具和性能测试来验证。</li>



<li>收集性能数据：使用性能监控工具来收集性能数据，包括CPU使用率、内存使用、线程情况、GC活动等。</li>



<li>分析性能数据：分析性能数据以识别性能瓶颈和问题的根本原因。</li>



<li>制定优化策略：根据分析结果制定优化策略，可能涉及JVM参数调优、代码重构、内存管理、多线程优化等方面。</li>



<li>实施优化策略：根据制定的策略，实施性能优化，修改配置和代码。</li>



<li>性能测试验证：进行性能测试，验证优化策略是否有效，如果不满足预期，返回步骤3和4进行进一步优化。</li>



<li>监控和维护：持续监控应用程序的性能，定期审查和调整优化策略，以适应变化的负载和需求。</li>



<li>文档和分享：记录优化过程和结果，分享给团队成员和其他相关人员，以便知识共享和团队学习。</li>
</ul>



<h2 class="wp-block-heading">给定一个Java应用程序，如何确定它的性能问题所在？</h2>



<ul class="wp-block-list">
<li>使用性能分析工具：使用工具如VisualVM、Java Mission Control、YourKit等来监视应用程序的性能，查看CPU使用率、内存占用、线程情况等。</li>



<li>日志记录：记录应用程序的日志，包括耗时操作、异常和错误信息。分析这些日志可以帮助识别性能问题的源头。</li>



<li>剖析（Profiling）：使用性能剖析工具，例如CPU剖析器（CPU profiler）或内存剖析器（Memory profiler），来识别性能瓶颈和内存泄漏。</li>



<li>压力测试：模拟高负载场景，观察应用程序在压力下的表现，查找性能瓶颈。</li>



<li>代码审查：仔细审查代码，查找可能导致性能问题的原因，例如不必要的循环、重复计算等。</li>
</ul>



<h2 class="wp-block-heading">请解释“内存泄漏”的概念，并提供检测和解决内存泄漏的方法。</h2>



<p>内存泄漏是指应用程序在运行时持续分配内存，但不再使用或释放这些内存，导致内存占用不断增加，最终耗尽可用内存。</p>



<p>检测和解决内存泄漏的方法包括：</p>



<ul class="wp-block-list">
<li>使用内存剖析工具：工具如Eclipse Memory Analyzer（MAT）或VisualVM可以帮助分析内存快照，识别对象引用链和泄漏源。</li>



<li>代码审查：查找可能导致内存泄漏的代码模式，如未关闭的资源、静态集合保留引用等。</li>



<li>使用弱引用和软引用：在某些情况下，可以使用Java的弱引用和软引用来管理对象的生命周期，以防止内存泄漏。</li>



<li>内存压力测试：模拟内存紧张情况，观察内存使用情况是否正常。</li>



<li>定期执行垃圾回收：虽然不能解决内存泄漏本身，但可以减缓其影响，使应用程序在发生泄漏时更慢地耗尽内存</li>
</ul>



<h2 class="wp-block-heading">如何为一个大型Java应用程序选择合适的垃圾回收器和堆大小？</h2>



<p>为大型Java应用程序选择合适的垃圾回收器和堆大小需要考虑以下因素：<br>应用程序性能需求：根据应用程序的性能需求选择垃圾回收器，例如，选择CMS（Concurrent Mark-Sweep）或G1（Garbage-First）回收器以降低暂停时间。<br>堆大小：根据应用程序的内存需求和负载情况来设置堆大小。可以通过-Xmx和-Xms参数来调整堆大小。<br>垃圾回收器参数：根据选择的垃圾回收器，可以配置相关参数以优化性能，例如，设置年轻代和老年代的比例、调整触发垃圾回收的阈值等。<br>监控和调整：使用性能监控工具来监视垃圾回收行为和内存使用情况，随着时间的推移调整参数以满足性能需求</p>



<h2 class="wp-block-heading">什么是“OOM（Out of Memory）错误”？如何排查和解决OOM错误？</h2>



<p>OOM（Out of Memory）错误是指Java应用程序尝试分配内存时，无法获得足够的可用内存，从而导致程序崩溃。要排查和解决OOM错误，可以采取以下步骤：</p>



<ul class="wp-block-list">
<li>分析错误信息：查看错误信息以确定是哪种类型的OOM错误，例如Heap Space、PermGen Space等。</li>



<li>内存剖析：使用内存剖析工具分析内存快照，找出内存泄漏或大对象。</li>



<li>调整堆大小：根据错误类型，可以通过调整堆大小来增加可用内存。</li>



<li>优化代码：查找可能导致内存泄漏的代码，修复资源未释放、循环引用等问题。</li>



<li>使用更合适的垃圾回收器：根据应用程序需求选择合适的垃圾回收器，以降低内存占用</li>
</ul>



<h2 class="wp-block-heading">如何使用JVM工具来监控应用程序的性能和资源使用情况？</h2>



<ul class="wp-block-list">
<li>JConsole：一个Java监视和管理控制台，可用于监视内存、线程、垃圾回收等信息。</li>



<li>VisualVM：一款功能强大的Java监控和调试工具，可以监视线程、堆内存、垃圾回收、CPU使用率等。</li>



<li>Java Mission Control（JMC）：官方的Java性能监控工具，提供实时数据、历史数据分析等功能。</li>



<li>JVM参数：使用诸如-Xmx、-Xms、-XX:+PrintGCDetails等参数来控制JVM的日志输出和性能监控信息。</li>
</ul>



<h2 class="wp-block-heading">什么是线程转储（Thread Dump）？如何分析它以诊断线程问题？</h2>



<p>线程转储（Thread Dump）是一个记录当前Java应用程序中所有线程状态的快照。要分析线程转储以诊断线程问题，可以使用以下方法：</p>



<ul class="wp-block-list">
<li>使用jstack命令或JVisualVM来生成线程转储。</li>



<li>分析线程状态：查看每个线程的状态、堆栈跟踪和等待对象，以识别是否存在死锁、线程阻塞或其他问题。</li>



<li>识别线程问题：查找异常、死锁、长时间等待或竞争条件等线程问题。</li>



<li>修复问题：根据分析结果采取相应的措施，例如修复代码中的同步问题、调整线程池配置等。</li>
</ul>



<h2 class="wp-block-heading">什么是JIT编译器（Just-In-Time Compiler）？它如何影响应用程序性能？</h2>



<p>JIT编译器（Just-In-Time Compiler）是Java虚拟机中的一种编译器，它将Java字节码（即.class文件）编译成本地机器代码，以提高应用程序的执行速度。JIT编译器通过将热点代码编译成本地代码来优化性能，从而减少解释执行的开销。它对应用程序性能有积极影响，因为编译后的代码通常比解释执行的代码更快。</p>



<h2 class="wp-block-heading">请解释“锁粗化”（Lock Coarsening）和“锁消除”（Lock Elimination）的概念。</h2>



<p>锁粗化（Lock Coarsening）和锁消除（Lock Elimination）是Java虚拟机在运行时进行的优化技术：</p>



<ul class="wp-block-list">
<li>锁粗化：当虚拟机检测到一系列的连续的加锁和解锁操作时，会将这些细粒度的锁操作合并成一个大的锁操作，从而减少锁操作的开销。</li>



<li>锁消除：虚拟机在编译时或运行时分析代码，如果确定某个锁对象不会导致竞争，就会将锁操作完全消除，以提高性能。</li>
</ul>



<p>这些优化技术能够减少锁开销和提高并发性能，但需要谨慎使用，因为在某些情况下，它们可能会导致不正确的结果。</p>



<h2 class="wp-block-heading">什么是GC暂停（GC Pause）？如何最小化GC暂停对应用程序性能的影响？</h2>



<p>GC暂停（GC Pause）是垃圾收集器执行垃圾回收时，暂停应用程序的执行的时间段。为了最小化GC暂停对应用程序性能的影响，可以考虑以下策略：</p>



<ul class="wp-block-list">
<li>使用低暂停垃圾回收器：选择具有低停顿时间的垃圾回收器，如G1或Z Garbage Collector。</li>



<li>调整堆大小：合理调整堆大小，以减少垃圾回收的频率。</li>



<li>使用并行收集器：对于大型应用程序，可以考虑使用并行垃圾回收器来充分利用多核处理器。</li>



<li>避免内存泄漏：确保应用程序没有内存泄漏，以减少垃圾回收的负担。</li>



<li>监控和调整：使用性能监控工具来监视GC暂停时间，根据需要调整垃圾回收器参数。</li>
</ul>



<h2 class="wp-block-heading">如何评估Java应用程序的性能并制定优化策略？</h2>



<ul class="wp-block-list">
<li>性能测试：执行性能测试，包括负载测试、并发测试和压力测试，以了解应用程序的性能瓶颈。</li>



<li>监控和分析：使用性能监控工具来监视应用程序的运行情况，包括CPU使用率、内存占用、线程情况和垃圾回收行为。</li>



<li>识别瓶颈：根据监控数据和性能测试结果，确定性能瓶颈的位置，可能是数据库访问、网络通信、CPU密集型任务等。</li>



<li>优化代码：根据性能瓶颈，优化应用程序的代码，例如改进算法、减少资源消耗等。</li>



<li>调整配置：根据性能需求，调整JVM参数、数据库连接池配置等。</li>



<li>重复测试：进行多轮测试和优化，以确保性能得到改善。</li>



<li>持续监控：在生产环境中持续监控应用程序的性能，及时发现和解决性能问题。</li>
</ul>



<h2 class="wp-block-heading" id="akh9v">说一下 JVM 的主要组成部分？及其作用？</h2>



<ul class="wp-block-list">
<li>类加载器（ClassLoader）</li>



<li>运行时数据区（Runtime Data Area）</li>



<li>执行引擎（Execution Engine）</li>



<li>本地库接口（Native Interface）</li>
</ul>



<p><strong>「组件的作用：」</strong>&nbsp;首先通过类加载器（ClassLoader）会把 Java 代码转换成字节码，运行时数据区（Runtime Data Area）再把字节码加载到内存中，而字节码文件只是 JVM 的一套指令集规范，并不能直接交给底层操作系统去执行，因此需要特定的命令解析器执行引擎（Execution Engine），将字节码翻译成底层系统指令，再交由 CPU 去执行，而这个过程中需要调用其他语言的本地库接口（Native Interface）来实现整个程序的功能。</p>



<h2 class="wp-block-heading" id="7dehe">说一下 JVM 运行时数据区？</h2>



<p>不同虚拟机的运行时数据区可能略微有所不同，但都会遵从 Java 虚拟机规范， Java 虚拟机规范规定的区域分为以下 5 个部分：</p>



<ul class="wp-block-list">
<li>程序计数器（Program Counter Register）：当前线程所执行的字节码的行号指示器，字节码解析器的工作是通过改变这个计数器的值，来选取下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能，都需要依赖这个计数器来完成；</li>



<li>Java 虚拟机栈（Java Virtual Machine Stacks）：用于存储局部变量表、操作数栈、动态链接、方法出口等信息；</li>



<li>本地方法栈（Native Method Stack）：与虚拟机栈的作用是一样的，只不过虚拟机栈是服务 Java 方法的，而本地方法栈是为虚拟机调用 Native 方法服务的；</li>



<li>Java 堆（Java Heap）：Java 虚拟机中内存最大的一块，是被所有线程共享的，几乎所有的对象实例都在这里分配内存；</li>



<li>方法区（Methed Area）：用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。</li>
</ul>



<h2 class="wp-block-heading" id="dniis">说一下堆栈的区别？</h2>



<ul class="wp-block-list">
<li>功能方面：堆是用来存放对象的，栈是用来执行程序的。</li>



<li>共享性：堆是线程共享的，栈是线程私有的。</li>



<li>空间大小：堆大小远远大于栈。</li>
</ul>



<h2 class="wp-block-heading" id="7disn">队列和栈是什么？有什么区别？</h2>



<p>队列和栈都是被用来预存储数据的。</p>



<p>队列允许先进先出检索元素，但也有例外的情况，Deque 接口允许从两端检索元素。</p>



<p>栈和队列很相似，但它运行对元素进行后进先出进行检索。</p>



<h2 class="wp-block-heading" id="46fio">什么是双亲委派模型？</h2>



<p>在介绍双亲委派模型之前先说下类加载器。对于任意一个类，都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性，每一个类加载器，都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存，然后再转化为 class 对象。</p>



<p>类加载器分类：</p>



<ul class="wp-block-list">
<li>启动类加载器（Bootstrap ClassLoader），是虚拟机自身的一部分，用来加载Java_HOME/lib/目录中的，或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库；</li>



<li>其他类加载器：</li>



<li>扩展类加载器（Extension ClassLoader）：负责加载&lt;java_home style=”box-sizing: border-box; outline: 0px !important;”&gt;libext目录或Java. ext. dirs系统变量指定的路径中的所有类库；</li>



<li>应用程序类加载器（Application ClassLoader）。负责加载用户类路径（classpath）上的指定类库，我们可以直接使用这个类加载器。一般情况，如果我们没有自定义类加载器默认就是用这个加载器。</li>
</ul>



<p>双亲委派模型：如果一个类加载器收到了类加载的请求，它首先不会自己去加载这个类，而是把这个请求委派给父类加载器去完成，每一层的类加载器都是如此，这样所有的加载请求都会被传送到顶层的启动类加载器中，只有当父加载无法完成加载请求（它的搜索范围中没找到所需的类）时，子加载器才会尝试去加载类。</p>



<h2 class="wp-block-heading" id="1blsj">说一下类装载的执行过程？</h2>



<p>类装载分为以下 5 个步骤：</p>



<ul class="wp-block-list">
<li>加载：根据查找路径找到相应的 class 文件然后导入；</li>



<li>检查：检查加载的 class 文件的正确性；</li>



<li>准备：给类中的静态变量分配内存空间；</li>



<li>解析：虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示，而在直接引用直接指向内存中的地址；</li>



<li>初始化：对静态变量和静态代码块执行初始化工作。</li>
</ul>



<h2 class="wp-block-heading" id="627l2">怎么判断对象是否可以被回收？</h2>



<p>一般有两种方法来判断：</p>



<ul class="wp-block-list">
<li>引用计数器：为每个对象创建一个引用计数，有对象引用时计数器 +1，引用被释放时计数 -1，当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题；</li>



<li>可达性分析：从 GC Roots 开始向下搜索，搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时，则证明此对象是可以被回收的。</li>
</ul>



<h2 class="wp-block-heading" id="3bujo">Java 中都有哪些引用类型？</h2>



<ul class="wp-block-list">
<li>强引用：发生 gc 的时候不会被回收。</li>



<li>软引用：有用但不是必须的对象，在发生内存溢出之前会被回收。</li>



<li>弱引用：有用但不是必须的对象，在下一次GC时会被回收。</li>



<li>虚引用（幽灵引用/幻影引用）：无法通过虚引用获得对象，用 PhantomReference 实现虚引用，虚引用的用途是在 gc 时返回一个通知。</li>
</ul>



<h2 class="wp-block-heading" id="aar5s">说一下 JVM 有哪些垃圾回收算法？</h2>



<ul class="wp-block-list">
<li>标记-清除算法：标记无用对象，然后进行清除回收。缺点：效率不高，无法清除垃圾碎片。</li>



<li>标记-整理算法：标记无用对象，让所有存活的对象都向一端移动，然后直接清除掉端边界以外的内存。</li>



<li>复制算法：按照容量划分二个大小相等的内存区域，当一块用完的时候将活着的对象复制到另一块上，然后再把已使用的内存空间一次清理掉。缺点：内存使用率不高，只有原来的一半。</li>



<li>分代算法：根据对象存活周期的不同将内存划分为几块，一般是新生代和老年代，新生代基本采用复制算法，老年代采用标记整理算法。</li>
</ul>



<h2 class="wp-block-heading" id="4klmv">说一下 JVM 有哪些垃圾回收器？</h2>



<ul class="wp-block-list">
<li>Serial：最早的单线程串行垃圾回收器。</li>



<li>Serial Old：Serial 垃圾回收器的老年版本，同样也是单线程的，可以作为 CMS 垃圾回收器的备选预案。</li>



<li>ParNew：是 Serial 的多线程版本。</li>



<li>Parallel 和 ParNew 收集器类似是多线程的，但 Parallel 是吞吐量优先的收集器，可以牺牲等待时间换取系统的吞吐量。</li>



<li>Parallel Old 是 Parallel 老生代版本，Parallel 使用的是复制的内存回收算法，Parallel Old 使用的是标记-整理的内存回收算法。</li>



<li>CMS：一种以获得最短停顿时间为目标的收集器，非常适用 B/S 系统。</li>



<li>G1：一种兼顾吞吐量和停顿时间的 GC 实现，是 JDK 9 以后的默认 GC 选项。</li>
</ul>



<h2 class="wp-block-heading" id="6qo0i">详细介绍一下 CMS 垃圾回收器？</h2>



<p>CMS 是英文 Concurrent Mark-Sweep 的简称，是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上，这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。</p>



<p>CMS 使用的是标记-清除的算法实现的，所以在 gc 的时候回产生大量的内存碎片，当剩余内存不能满足程序运行要求时，系统将会出现 Concurrent Mode Failure，临时 CMS 会采用 Serial Old 回收器进行垃圾清除，此时的性能将会被降低。</p>



<h2 class="wp-block-heading" id="ado7f">新生代垃圾回收器和老生代垃圾回收器都有哪些？有什么区别？</h2>



<ul class="wp-block-list">
<li>新生代回收器：Serial、ParNew、Parallel Scavenge</li>



<li>老年代回收器：Serial Old、Parallel Old、CMS</li>



<li>整堆回收器：G1</li>
</ul>



<p>新生代垃圾回收器一般采用的是复制算法，复制算法的优点是效率高，缺点是内存利用率低；老年代回收器一般采用的是标记-整理的算法进行垃圾回收。</p>



<h2 class="wp-block-heading" id="4lfn">简述分代垃圾回收器是怎么工作的？</h2>



<p>分代回收器有两个分区：老生代和新生代，新生代默认的空间占比总空间的 1/3，老生代的默认占比是 2/3。</p>



<p>新生代使用的是复制算法，新生代里有 3 个分区：Eden、To Survivor、From Survivor，它们的默认占比是 8:1:1，它的执行流程如下：</p>



<ul class="wp-block-list">
<li>把 Eden + From Survivor 存活的对象放入 To Survivor 区；</li>



<li>清空 Eden 和 From Survivor 分区；</li>



<li>From Survivor 和 To Survivor 分区交换，From Survivor 变 To Survivor，To Survivor 变 From Survivor。</li>
</ul>



<p>每次在 From Survivor 到 To Survivor 移动时都存活的对象，年龄就 +1，当年龄到达 15（默认配置是 15）时，升级为老生代。大对象也会直接进入老生代。</p>



<p>老生代当空间占用到达某个值之后就会触发全局垃圾收回，一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。</p>



<h2 class="wp-block-heading" id="79s9k">说一下 JVM 调优的工具？</h2>



<p>JDK 自带了很多监控工具，都位于 JDK 的 bin 目录下，其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。</p>



<ul class="wp-block-list">
<li>jconsole：用于对 JVM 中的内存、线程和类等进行监控；</li>



<li>jvisualvm：JDK 自带的全能分析工具，可以分析：内存快照、线程快照、程序死锁、监控内存的变化、gc 变化等。</li>
</ul>



<h2 class="wp-block-heading" id="908om">常用的 JVM 调优的参数都有哪些？</h2>



<ul class="wp-block-list">
<li>-Xms2g：初始化推大小为 2g；</li>



<li>-Xmx2g：堆最大内存为 2g；</li>



<li>-XX:NewRatio=4：设置年轻的和老年代的内存比例为 1:4；</li>



<li>-XX:SurvivorRatio=8：设置新生代 Eden 和 Survivor 比例为 8:2；</li>



<li>–XX:+UseParNewGC：指定使用 ParNew + Serial Old 垃圾回收器组合；</li>



<li>-XX:+UseParallelOldGC：指定使用 ParNew + ParNew Old 垃圾回收器组合；</li>



<li>-XX:+UseConcMarkSweepGC：指定使用 CMS + Serial Old 垃圾回收器组合；</li>



<li>-XX:+PrintGC：开启打印 gc 信息；</li>



<li>-XX:+PrintGCDetails：打印 gc 详细信息。</li>
</ul>



<h2 class="wp-block-heading">java中过滤器和拦截器有什么区别</h2>



<p>在 Spring Boot 中，过滤器（Filter）和拦截器（Interceptor）都用于处理请求和响应，但它们在实现机制、作用范围和使用场景上有明显区别，主要体现在以下几个方面：</p>



<ol class="wp-block-list">
<li><strong>技术本质不同</strong><br><strong>过滤器（Filter）</strong><br>是 Java Servlet 规范定义的组件，属于 Servlet 容器层面的技术，不依赖 Spring 框架。<br>基于函数回调实现，工作在 Web 容器的请求处理链最外层，可以拦截所有进入容器的请求（包括静态资源、JSP 等）。<br><strong>拦截器（Interceptor）</strong><br>是 Spring 框架自身定义的组件，依赖 Spring 容器，属于 Spring MVC 层面的技术。<br>基于 Java 反射（AOP 思想）实现，仅能拦截 Spring MVC 处理的请求（即通过 DispatcherServlet 分发的请求）。</li>



<li><strong>执行时机不同</strong><br>请求处理流程中，两者的执行顺序如下：<br>请求进入容器 → Filter 拦截 → DispatcherServlet 分发 → Interceptor 拦截 → Controller 处理 → Interceptor 后置处理 → Filter 后置处理 → 响应返回<br><img loading="lazy" decoding="async" width="300" height="833" srcset="https://blog.lhyshome.com/wp-content/uploads/2025/08/338192f004384190a9e2f74adbb9620b.png 330w, https://blog.lhyshome.com/wp-content/uploads/2025/08/338192f004384190a9e2f74adbb9620b-108x300.png 108w" src="https://blog.lhyshome.com/wp-content/uploads/2025/08/338192f004384190a9e2f74adbb9620b.png" alt=""><br><strong>Filter：</strong>在请求到达 DispatcherServlet 之前执行，且在响应返回客户端前最后执行。<br><strong>Interceptor：</strong>在 DispatcherServlet 之后、Controller 之前执行，且在 Controller 处理完成后、视图渲染前后还有对应的回调方法。</li>



<li><strong>拦截范围不同</strong><br><strong>Filter：</strong>可拦截所有请求（如 .html、.css、.js 等静态资源，以及非 Spring MVC 处理的请求）。<br><strong>Interceptor：</strong>仅拦截 Spring MVC 管理的请求（即通过 @Controller 或 @RestController 定义的接口），对静态资源、直接访问的 JSP 等不生效。</li>



<li><strong>注入能力不同</strong><br><strong>Filter：</strong>由 Servlet 容器管理生命周期，默认情况下无法直接注入 Spring 容器中的 Bean（需通过特殊配置才能获取 Spring 上下文）。<br><strong>Interceptor：</strong>由 Spring 容器管理，可直接注入 Spring 中的 Bean（如 Service、Repository 等），便于使用 Spring 的功能。</li>



<li><strong>方法回调不同</strong><br><strong>Filter：</strong>核心方法是 doFilter(ServletRequest, ServletResponse, FilterChain)，通过 FilterChain.doFilter() 放行请求，仅有请求前和响应后两个处理时机。<br><strong>Interceptor：</strong>提供三个核心方法：<br>preHandle()：Controller 执行前调用（返回 true 放行，false 拦截）。<br>postHandle()：Controller 执行后、视图渲染前调用。<br>afterCompletion()：视图渲染完成后、响应返回前调用（无论是否异常都会执行）。</li>



<li><strong>使用场景不同</strong><br><strong>Filter：</strong>适合处理与 Servlet 容器相关的通用逻辑，如：<br>编码转换（如统一设置 UTF-8）。<br>跨域请求处理（CORS 配置）。<br>敏感字符过滤、请求日志记录（包括静态资源）。<br>身份验证（如基于 Session 的登录校验）。<br><strong>Interceptor：</strong>适合处理与 Spring MVC 相关的业务逻辑，如：<br>权限精细校验（结合 Spring Security 或自定义权限逻辑）。<br>业务日志记录（仅针对接口请求）。<br>接口性能监控（记录请求处理时间）。<br>事务管理相关的预处理/后处理。</li>
</ol>



<p><strong>总结</strong></p>



<ul class="wp-block-list">
<li>过滤器是 Servlet 级别的“全局拦截”，适合处理容器层面的通用逻辑，不依赖 Spring。</li>



<li>拦截器是 Spring MVC 级别的“业务拦截”，适合处理与 Spring 集成的业务逻辑，功能更灵活。</li>
</ul>



<p>实际开发中，两者可配合使用（如 Filter 处理编码和跨域，Interceptor 处理接口权限）。</p>



<h2 class="wp-block-heading">可以自定义一个&nbsp;<code>java.lang.String</code>&nbsp;吗？</h2>



<p><strong>可以定义，但无法被正常加载执行</strong>。根据双亲委派模型，加载&nbsp;<code>java.lang.String</code>&nbsp;时会先交给启动类加载器，从 rt.jar 中加载真正的&nbsp;<code>String</code>，自定义的类永远不会被使用。若强行覆盖核心类，还会违反安全机制。</p>



<h2 class="wp-block-heading">什么是 JVM 内存模型？</h2>



<p>JMM（Java Memory Model）定义了一套规则，用于规范多线程环境下<strong>共享变量的可见性、原子性和有序性</strong>。它通过主内存与工作内存的概念，以及&nbsp;<code>happens-before</code>&nbsp;原则，来屏蔽不同硬件和操作系统的内存访问差异。</p>



<h2 class="wp-block-heading">JVM 内存模型和 JVM 内存结构的区别？</h2>



<ul class="wp-block-list">
<li><strong>JVM 内存结构</strong>：指 JVM 运行时数据区域的划分（堆、栈、方法区等），是物理/逻辑分区。</li>



<li><strong>JVM 内存模型</strong>：指 JMM，是一套并发编程的规范/抽象概念，不描述具体内存布局，而是描述线程与内存之间的交互规则。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2025/03/09/283/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">283</post-id>	</item>
		<item>
		<title>docker创建proxy-client镜像（设置 -e 参数配置）</title>
		<link>https://blog.lhyshome.com/2024/12/30/257/</link>
					<comments>https://blog.lhyshome.com/2024/12/30/257/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Mon, 30 Dec 2024 03:04:33 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[小技巧]]></category>
		<category><![CDATA[脚本]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[harbor]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[proxy-client]]></category>
		<category><![CDATA[内网穿透]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=257</guid>

					<description><![CDATA[文件夹目录 dockerFile ep.sh config.template docker构建及上传 进入pr… <span class="read-more"><a href="https://blog.lhyshome.com/2024/12/30/257/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">文件夹目录</h2>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="321" height="392" src="https://blog.lhyshome.com/wp-content/uploads/2024/12/image-2.png" alt="" class="wp-image-259" srcset="https://blog.lhyshome.com/wp-content/uploads/2024/12/image-2.png 321w, https://blog.lhyshome.com/wp-content/uploads/2024/12/image-2-246x300.png 246w" sizes="auto, (max-width: 321px) 100vw, 321px" /></figure>



<h2 class="wp-block-heading">dockerFile</h2>



<pre class="wp-block-code"><code>FROM {harbor}/test/openjdk:8-jre
LABEL authors="lhyshome_pc"
ENV TZ=Asia/Shanghai
ADD ./proxy-client /proxy-client
ADD ./ep.sh /ep.sh
RUN apt-get update &amp;&amp; apt-get install -y procps
RUN apt-get update &amp;&amp; apt-get install -y dos2unix
RUN dos2unix /ep.sh &amp;&amp; chmod +x /ep.sh
ENTRYPOINT &#91;"/ep.sh"]</code></pre>



<h2 class="wp-block-heading">ep.sh </h2>



<pre class="wp-block-code"><code>#!/bin/bash

echo "entrypoint.sh start"
# 定义目标配置文件路径
CONFIG_FILE=proxy-client/conf/config.properties

# 替换占位符为环境变量的值
sed -e "s|{{CLIENT_KEY}}|${CLIENT_KEY:-CLIENT_KEY}|g" \
    -e "s|{{SERVER_HOST}}|${SERVER_HOST:-127.0.0.1}|g" \
    -e "s|{{SERVER_PORT}}|${SERVER_PORT:-80}|g" \
    proxy-client/conf/config.template > $CONFIG_FILE

echo "Generated config file:"
cat $CONFIG_FILE

# 启动主程序（根据需要替换为实际启动命令）
exec "proxy-client/bin/startup.sh"</code></pre>



<h2 class="wp-block-heading">config.template</h2>



<pre class="wp-block-code"><code>client.key={{CLIENT_KEY}}
server.host={{SERVER_HOST}}
server.port={{SERVER_PORT}}</code></pre>



<h2 class="wp-block-heading">docker构建及上传</h2>



<p>进入proxy-client的上级目录</p>



<pre class="wp-block-code"><code>docker build -t {harbor}/test/proxy-client:v1.0.0 ./
docker push {harbor}/test/proxy-client:v1.0.0
docker image rm {harbor}/test/proxy-client:v1.0.0</code></pre>



<h2 class="wp-block-heading">docker部署命令</h2>



<p>docker run -e CLIENT_KEY=xxxxx -e SERVER_HOST=xxx.xxx.xxx.xxx -e SERVER_PORT=xxxx &#8211;name proxy-client &#8211;privileged=true &#8211;restart=always -d {harbor}/test/proxy-client:v1.0.0</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2024/12/30/257/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">257</post-id>	</item>
		<item>
		<title>接口响应Long转String注解</title>
		<link>https://blog.lhyshome.com/2024/08/20/219/</link>
					<comments>https://blog.lhyshome.com/2024/08/20/219/#respond</comments>
		
		<dc:creator><![CDATA[lhy]]></dc:creator>
		<pubDate>Tue, 20 Aug 2024 10:32:33 +0000</pubDate>
				<category><![CDATA[java]]></category>
		<category><![CDATA[小技巧]]></category>
		<guid isPermaLink="false">https://blog.lhyshome.com/?p=219</guid>

					<description><![CDATA[在接口响应对象的字段上添加@JsonSerialize(using = ToStringSerializer.… <span class="read-more"><a href="https://blog.lhyshome.com/2024/08/20/219/">Read More &#187;</a></span>]]></description>
										<content:encoded><![CDATA[
<p>在接口响应对象的字段上添加@JsonSerialize(using = ToStringSerializer.class)注解可在响应的JSON中将Long型的字段转为字符串类型</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="525" height="158" src="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-5.png" alt="" class="wp-image-220" srcset="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-5.png 525w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-5-300x90.png 300w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-5-500x150.png 500w" sizes="auto, (max-width: 525px) 100vw, 525px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="702" height="314" src="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-6.png" alt="" class="wp-image-221" srcset="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-6.png 702w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-6-300x134.png 300w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-6-500x224.png 500w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-6-660x295.png 660w" sizes="auto, (max-width: 702px) 100vw, 702px" /></figure>



<p>结果：</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="356" height="194" src="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-7.png" alt="" class="wp-image-222" srcset="https://blog.lhyshome.com/wp-content/uploads/2024/08/image-7.png 356w, https://blog.lhyshome.com/wp-content/uploads/2024/08/image-7-300x163.png 300w" sizes="auto, (max-width: 356px) 100vw, 356px" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.lhyshome.com/2024/08/20/219/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">219</post-id>	</item>
	</channel>
</rss>
