<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>白露未晞</title><description>白露未晞的港区</description><link>https://fuwari.vercel.app/</link><language>zh_CN</language><item><title>MySQL学习</title><link>https://fuwari.vercel.app/posts/interview/noteformysql/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/interview/noteformysql/</guid><description>MySQL学习</description><pubDate>Sun, 03 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;如何定位慢查询&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当发现存在慢查询的时候，首先如果系统中采用了如Skywalking之类的运维工具，那么就可以使用这些运维工具的图形化界面来监控分析接口的运行时长组成，这样就可以发现是不是SQL造成的接口访问缓慢的问题。&lt;/li&gt;
&lt;li&gt;也可以使用MySQL自带的慢查询日志，这个慢查询日志不是默认开启的，需要在MySQL的配置文件里面手动开启和设置查询超过多少时间就记录，比如说可以设置成2秒，意思就是如果一个SQL语句执行的时间超过2秒就会被记录到日志里面。不过这种方法最好只在调试阶段使用，因为开启这个日志会损耗一定的性能&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分析SQL为什么执行得慢&lt;/h3&gt;
&lt;p&gt;可以查看SQL的执行计划来帮助分析SQL语句执行慢的原因，具体操作就是在Select语句前加上“EXPLAIN”关键字来查看分析。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以关注key和key_len来检查这条语句是否命中索引或者判断索引是否失效，如果出现这些情况的话就可以添加索引或者检查SQL语句是否出错来优化语句。&lt;/li&gt;
&lt;li&gt;通过type字段来检查是不是还有优化空间，是否存在全盘扫描等情况&lt;/li&gt;
&lt;li&gt;通过extra来判断是不是出现了回表的情况，如果出现了话可以尝试添加索引或者修改返回字段来优化SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;索引是什么？&lt;/h3&gt;
&lt;p&gt;索引是可以帮助数据库高效查询的数据结构，主要用于提高数据查询的效率，降低性能开销。&lt;/p&gt;
&lt;h3&gt;索引的底层数据结构&lt;/h3&gt;
&lt;p&gt;MySQL默认存储引擎InnoDB采用的索引数据结构是B+树，原因是B+树查询的路径更短，硬盘读写的开销低，只在叶子结点存储数据，其他节点只存储指针。&lt;/p&gt;
&lt;h3&gt;B树和B+树的区别&lt;/h3&gt;
&lt;p&gt;B树在非叶子节点和叶子结点上都会存储数据，而B+树只会在叶子结点上存储数据，在查询的时候B+树的查找效率会更加稳定。
在进行范围查询的时候，因为B+树只在叶子结点存储数据的特点，而所有的叶子结点组成的是一个双向链表，所以存储的效率会更高。&lt;/p&gt;
&lt;h3&gt;什么是聚簇索引？什么是非聚簇索引？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;聚簇索引是索引和数据放在一起，B+树的叶子节点存储的是索引和数据，一个表中只能有一个聚簇索引。&lt;/li&gt;
&lt;li&gt;非聚簇索引是指索引和数据分开存储，B+树的叶子节点保存的是索引和主键，一个表可以有多个非聚簇索引。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;什么是回表查询&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;回表查询是跟聚簇索引和非聚簇索引有关系的，回表查询就是通过二级索引找到对应的主键值，然后再通过主键值找到聚集索引中所对应的整行数据，这个操作就是回表。&lt;/li&gt;
&lt;li&gt;简单来说就是使用索引查询到主键之后，为了获取完整的行数据需要再去原来的表里面通过主键来查找剩下的信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;什么是覆盖索引？&lt;/h3&gt;
&lt;p&gt;覆盖索引是指使用了索引查询的时候返回的列可以一次在全找到，满足覆盖索引的查询性能会较高，但是有可能会触发回表查询，这样的话性能就比较低了，要避免发生这种情况的话最好在编写SQL语句的时候尽量手动指定要查询的列，避免使用&lt;code&gt;select *&lt;/code&gt; 。&lt;/p&gt;
&lt;h3&gt;超大分页怎么处理？&lt;/h3&gt;
&lt;p&gt;使用覆盖索引+子查询
先分页查询获取表中的ID，并且对这个ID进行排序，就可以筛选出分页之后的ID集合，因为ID是覆盖索引，因此操作的效率会高很多。最后通过ID集合去到原来的表中再进行关联查询。&lt;/p&gt;
&lt;h3&gt;索引的创建要注意什么？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;索引一般建立在需要频繁查询的字段上，索引可以提高查询的效率，但会降低增删改的效率，因为需要动态的维护索引&lt;/li&gt;
&lt;li&gt;正因为索引需要花性能和空间去维护，所以需要控制索引的数量&lt;/li&gt;
&lt;li&gt;在单表数据超过10万这个前提才推荐去创建索引，并且最好创建联合索引，并且注意不要在低区分度的字段上创建索引。&lt;/li&gt;
&lt;li&gt;推荐有唯一性的列作为索引，并且尽量不使用无序的值作为索引&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;索引失效的情况&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;违反最左前缀法则&lt;/li&gt;
&lt;li&gt;使用范围查询时，右边的列的索引会失效&lt;/li&gt;
&lt;li&gt;在索引列上进行运算操作的时候索引会失效&lt;/li&gt;
&lt;li&gt;条件中字符串不加单引号导致发生类型转换，索引会失效&lt;/li&gt;
&lt;li&gt;以%号开头的模糊查询索引会失效，但是%号在最后面的模糊查询索引不会失效&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SQL优化的经验？&lt;/h3&gt;
&lt;p&gt;（个人觉得答两三点就够了）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先是表设计的优化，参考阿里开发手册中的数据库表设计规范来进行表设计，主要就是根据具体业务来选择合适的数据类型，比如说存储字符串，根据业务中存储字符串可能出现的长度大小来选择到底是使用varchar还是text类型。&lt;/li&gt;
&lt;li&gt;索引优化（参考索引的创建）&lt;/li&gt;
&lt;li&gt;SQL语句优化，比如说select语句尽量指明需要查询的字段，不要直接使用&lt;code&gt;select *&lt;/code&gt; ，还有就是要注意避免出现可能造成索引失效的写法，比如说聚合查询尽量使用 union all 来代替 union，因为union 会额外多一次过滤，效率会比较低。如果是表关联的话，尽量使用inner join，如果必须要使用左、右连接的话，尽量使用小表来驱动关联查询。&lt;/li&gt;
&lt;li&gt;还有就是主从复制和读写分离的方面，在生产环境中最好将生产数据库设置多个库来组成集群，提高数据库的可用性，将读、写操作分离到不同的数据库中，可以有效防止写操作阻塞读操作。&lt;/li&gt;
&lt;li&gt;分库分表，对于一些单表数据达到500万的大表，可以考虑分库分表来提高操作效率，如果数据量较小，或预估数据增长量没有达到必须要分库分表的情况下，最好不进行分库分表操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;主从同步的原理是什么？&lt;/h3&gt;
&lt;p&gt;主从同步的核心就是把二进制日志binlog中的内容进行复制&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主库在提交事务的时候，会把数据变更记录在二进制日志文件binlog中。&lt;/li&gt;
&lt;li&gt;从库读取主库的binlog，写入到从库的中继日志 Relay Log。&lt;/li&gt;
&lt;li&gt;从库重做中继日志里面的事件，这样就实现了主从库的同步操作了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;关于分库分表&lt;/h3&gt;
&lt;p&gt;当单表数据超过1000万数据或大小超过20G才会考虑进行分库分表&lt;br /&gt;
分库分表分为&lt;code&gt;垂直拆分&lt;/code&gt;和&lt;code&gt;水平拆分&lt;/code&gt;&lt;br /&gt;
其中垂直拆分分为&lt;code&gt;垂直分库&lt;/code&gt;和&lt;code&gt;垂直分表&lt;/code&gt;&lt;br /&gt;
其中水平拆分分为&lt;code&gt;水平分库&lt;/code&gt;和&lt;code&gt;水平分表&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;垂直分库：根据具体业务，按照业务的不同将不同的表拆分到不同的数据库中。&lt;/li&gt;
&lt;li&gt;垂直分表：以字段为依据，根据字段的属性将不同的字段拆分到不同表中。&lt;br /&gt;
拆分规则为：
&lt;ul&gt;
&lt;li&gt;把不常用的字段单独放在一张表中。&lt;/li&gt;
&lt;li&gt;把text，blob等大字段拆分出来放在附表中。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;水平分库：将一个库中的数据拆分到多个库中，每个库中的数据都不一样，所有库的数据加一起才是一个业务的完整的数据库。应用需要按照一定的路由规则来访问正确的数据库获取正确的信息，一般来说按照ID的范围来判定需要查找哪个数据库。&lt;/li&gt;
&lt;li&gt;水平分表：将一个表中的数据拆分到多个表中，可以在同一个库内进行操作，应用也需要通过一定的路由规则来访问正确的表以获得正确的数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;事务的特性&lt;/h3&gt;
&lt;p&gt;事务的特性有四个，ACID，也就是原子性，一致性，隔离性，持久性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原子性指一个事物不可以再分割，事务中的操作要么全部成功，要么全部失败。&lt;/li&gt;
&lt;li&gt;一致性指事务完成之后必须要让所有的数据都保持一致的状态。&lt;/li&gt;
&lt;li&gt;隔离性指事务不受数据库外界并发操作的影响，保证事务在独立环境下运行。&lt;/li&gt;
&lt;li&gt;持久性指事务一旦提交或者回滚，对数据库中数据的改变是永久的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如转账操作，A向B转账100块钱，A扣除100块钱，B增加100块钱，这两个操作要么全成功，要么全失败，体现原子性。在转账过程中，数据的一致性可以举例为A扣了100块钱，B就必须增加100块钱，而这个A向B转账的操作不能被其他事务干扰，必须是独立运行的，体现了独立性，而持久性的话，在事务提交之后，A和B的数据在数据库中都是被持久化保存下来了的。&lt;/p&gt;
&lt;h3&gt;并发事务的问题&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;脏读：一个事务读取到另外一个事务还未提交的数据。&lt;/li&gt;
&lt;li&gt;不可重复读：一个事务先后读取同一条数据，但两次读取结果的数据不同&lt;/li&gt;
&lt;li&gt;幻读：一个事务按照条件查询数据时，查找不到对应的数据，但是在插入数据时发现这一行数据已经存在。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;事务隔离级别&lt;/h3&gt;
&lt;p&gt;事务的隔离级别一共有四种&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;未提交读 (可能出现脏读、不可重复读、幻读)&lt;/li&gt;
&lt;li&gt;读已提交 (可能会出现不可重复读、幻读)&lt;/li&gt;
&lt;li&gt;可重复读 (可能会出现幻读，是MySQL默认的事务隔离级别)&lt;/li&gt;
&lt;li&gt;串行化 (不会出现并发事务问题，但性能最差)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;undo log和redo log的区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;redo log记录的是数据页的物理变化，如果服务宕机可以使用它来同步数据。&lt;/li&gt;
&lt;li&gt;undo log记录的是逻辑日志，当事务回滚的时候通过逆操作来恢复原来的数据&lt;br /&gt;
redo log保证了事务的持久性，undo log保证了事务的原子性和一致性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MVCC相关&lt;/h3&gt;
&lt;p&gt;MVCC是MySQL中的多版本并发控制，用于保证事务中的隔离性。
MVCC维护一个数据的多个版本，使得读写操作没有冲突。
MVCC主要有以下三个主要内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隐藏字段：
&lt;ol&gt;
&lt;li&gt;trx_id（事务id），用于记录每一次操作的事务id，这个字段是自增的。&lt;/li&gt;
&lt;li&gt;roll_pointer（回滚指针），这个指针指向上一个版本的事务版本记录。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;undo log：
&lt;ol&gt;
&lt;li&gt;回滚日志，用于存储老版本数据。&lt;/li&gt;
&lt;li&gt;版本链：用于在多个事务并行操作某一行记录的时候，记录不同事务修改数据的版本，通过回滚指针形成一个链表，链表尾存着的是最早的版本，链表头存着的是最新的版本。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;readView（读视图）：
&lt;ol&gt;
&lt;li&gt;根据读视图的匹配规则和当前的事务id判断应该要访问哪个版本的数据。&lt;/li&gt;
&lt;li&gt;不同的隔离级别快照读是不一样的，最终的访问结构不一样
RC：每一次执行快照读的时候生成ReadView
RR：仅在事务中第一次执行快照读的时候生成ReadView，后续复用这个ReadView。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Kubernetes笔记</title><link>https://fuwari.vercel.app/posts/kubernetesnote/kubernetesnote/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/kubernetesnote/kubernetesnote/</guid><description>K8S笔记</description><pubDate>Sun, 20 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;p&gt;Kubernetes（K8S）是一个开源的容器编排平台，主要作用是管理和维护容器。&lt;/p&gt;
&lt;h3&gt;K8S的基本组件&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Node&lt;/strong&gt;&lt;br /&gt;
节点，一个节点可以是一台虚拟机或者一台物理机，一个集群由若干个结点组成，节点上的组件包括kubelet，容器运行时，kubectl以及kube-proxy等组成。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pod&lt;/strong&gt;&lt;br /&gt;
Pod是K8S的最小调度单位，可以看成是容器的抽象，一个Pod是一个或者多个应用容器的组合，Pod可以看成一个容器运行时环境，在此环境中容器可以相互共享一些资源，如网络，存储以及运行时配置等。&lt;/p&gt;
&lt;p&gt;在一般情况下，为了更好地解耦和扩展，一个Pod只运行一个容器，但如果一些容器间存在高度耦合的情况下，比如需要共享配置文件或者对其中的容器进行日志监控等情况，一个Pod中可以运行多个容器，这些与主要容器进行资源共享的容器被称为边车容器(Sidecar)。&lt;/p&gt;
&lt;p&gt;Pod之间的通信通过集群内部的特定IP地址进行通信，每个Pod在被创建的时候都会被自动分配一个IP地址，这个IP地址只能被集群内部使用，外部应用无法访问此IP地址。&lt;/p&gt;
&lt;p&gt;Pod并不是一个稳定的实体，它有可能会被频繁地销毁和创建，比如当Pod发生了故障的时候，K8S就会把Pod销毁然后重新创建一个，每当Pod被销毁后重新创建的时候集群内部的IP地址都会被重新分配，也就是说每个Pod的集群内部IP并不是固定的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;br /&gt;
为了解决Pod有可能会被销毁和重新创建带来的IP地址变动的问题，引入了一个叫做Service的资源对象，Service可以把一组Pod封装成一组服务（Service）这个服务可以通过一个统一且固定的IP地址来访问。
如下图所示，分别把一个应用程序Pod和三个数据库Pod分别封装成两个Service，应用程序通过数据库Service的IP地址访问数据库，当数据库Service中的Pod发生故障重新创建时，该Pod的IP地址变动并不会对应用程序的使用产生影响，Service会自动地将请求转发到其他正常运行的Pod上。
&amp;lt;img src=&quot;https://p.sda1.dev/19/e3abe44c3b2f530038398ad7809963ce/k8sImage1.png&quot; &amp;gt;
&lt;strong&gt;内部服务和外部服务&lt;/strong&gt;&lt;br /&gt;
在K8S中，可以将服务分成内部服务和外部服务，内部服务即不需要暴露给外部和不能暴露给外部的服务，如数据库，缓存，消息队列等，这些服务只需要在集群内部被访问就足够了。外部服务就是需要暴露给用户使用的服务，比如后端的API接口，前端界面等。&lt;/p&gt;
&lt;p&gt;外部服务常见的类型有 ExternalName、LoadBalancer、NodePort、ClusterIP，其中 NodePort 是我们常用的类型，它会在节点上开放一个端口，然后将这个端口映射到 Service 的 IP 地址和端口上，这样就可以通过节点的 IP 地址加端口来访问 Service 了。&lt;br /&gt;
类似于 &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; 这种形式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ingress&lt;/strong&gt;&lt;br /&gt;
Ingress 是用于管理从集群外部访问集群内部服务的入口和方式，Ingress 是集群中服务的外部访问进行管理的 API 对象，使用HTTP或HTTPS的方式来访问。Ingress也可以用于配置SSL证书或者负载均衡。
&amp;lt;img src=&quot;https://p.sda1.dev/19/cd61f163a75c882c56f84c09c1b2833d/k8sImage2.png&quot; &amp;gt;&lt;/p&gt;
&lt;p&gt;Ingress 提供从集群外部到集群内部服务的 HTTP 和 HTTPS 路由，流量的路由由 Ingress 所定义的规则来控制。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/8097ebcc4c278fb98ce8d73f0e81946d/k8sImage3.png&quot; &amp;gt;&lt;/p&gt;
&lt;p&gt;通过对 Ingress 的配置，Ingress 可以为 Service 绑定一个供外部访问的 URL 并对其流量进行负载均衡，添加SSL证书等操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ConfigMap&lt;/strong&gt;&lt;br /&gt;
ConfigMap 用于将非机密数据保存到键值对中，如各种不包含敏感信息（如数据库账号密码等）的环境配置。使用ConfigMap的目的是将环境配置信息与容器镜像解耦，使得配置信息与代码分离开来，降低运维的成本和难度。&lt;br /&gt;
比如当数据库的地址或者端口等配置信息发生改变时，只需要修改ConfigMap对象中的配置信息，然后&lt;strong&gt;重新加载Pod&lt;/strong&gt;即可应用改变，不需要重新编译和部署应用程序。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secret&lt;/strong&gt;&lt;br /&gt;
正如上面所述 ConfigMap 不能包含敏感信息，因为 ConfigMap 是以明文的方式存储配置信息的，如果将账号密码等敏感信息存入到 ConfigMap 会造成安全隐患，于是K8s提供了Secret组件用于封装和存储敏感信息，但是Secret仅仅只是将配置信息进行了一次 Base64 编码，可以被轻易地还原出敏感信息，因此还需要其他手段来保证敏感信息的安全。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Volume&lt;/strong&gt;&lt;br /&gt;
Pod并不是一个稳定的实体，当 Pod 被销毁或重启时数据也会跟着消失，这对于需要持久化存储的应用程序比如数据库等应用来说肯定是不行的，K8s 提供了 Volume 组件，它可以将一些持久化存储的资源挂载到集群中的本地磁盘上，或者挂载到集群外部的远程存储上，比如说远程文件服务器，对象存储服务器等。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;持续补全中。。。。。。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>JVM笔记</title><link>https://fuwari.vercel.app/posts/jvmnote/jvm%E7%AC%94%E8%AE%B0/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/jvmnote/jvm%E7%AC%94%E8%AE%B0/</guid><description>jvm笔记，貌似还有漏的，迟点补上</description><pubDate>Sat, 19 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JVM：java虚拟机，是java实现跨平台的基石。&lt;/p&gt;
&lt;p&gt;执行java程序的过程：源代码编译成class字节码，程序执行时由jvm解释翻译成对应平台的机器指令并运行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;jvm内存区域的划分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;jvm内存区域可分为5个部分，分别是虚拟机栈，堆，方法区，本地方法栈和程序计数器。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/be629beea0ac5ce2ddaf34dec5f7e8e5/Pasted&quot;&amp;gt;
其中方法区和堆是所有线程共享的区域，生命周期是随着虚拟机的创建而创建，随着虚拟机的结束而销毁。
虚拟机栈，本地方法栈，程序计数器在线程之间相互隔离，每个线程都有一块自己的区域，生命周期是随着线程的启动而创建，随着线程的结束而销毁。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;程序计数器&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;用于记录代码运行到什么位置，在字节码解析工作进行的时候会改变这个值来指定下一条即将执行的指令。&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;虚拟机栈&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;当每个方法被执行的时候，JVM会同步创建一个栈帧，栈帧中包含当前方法的信息，如局部变量表，操作数栈，动态链接和方法出口等。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/d2c41e9032f904a66abe37132ec8ded4/Pasted&quot;&amp;gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/cd19fe387abe795057e9a462dacc7b34/Pasted&quot;&amp;gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/c1865c6a2ae535cdef9a80e3c9a275f3/Pasted&quot;&amp;gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/7a8cccb7e2dfe42ce0fc7c2b17d223ac/Pasted&quot;&amp;gt;
&lt;strong&gt;本地方法栈&lt;/strong&gt;：与虚拟机栈差不多，是一个为本地方法（Native Method)服务的栈。&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;堆&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;是jvm中占内存最大的一块区域，在虚拟机启动的时候被创建，这块内存的唯一目的就是存放和管理对象，当java堆被填满且无法进行垃圾回收的时候就会抛出OutOfMemoryError异常，堆是垃圾收集器管理的主要区域。&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;方法区&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;与堆一样，是线程共享的区域，用于存储被虚拟机加载的类信息、常量、静态变量、即时编译的代码等。在JDK 7
之后，字符串常量从方法区移动到了堆中。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/585dd9b7a08db5e3474dfd3b1a5ed2b7/Pasted&quot;&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;直接内存&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;又称堆外内存，是一块不受jvm管控的内存区域，这个区域的内存需要手动申请和释放，不会受到堆内存容量的限制，但仍然会受限于计算机的实际内存。在JDK1.4
引入了NIO类，是一种基于通道与缓冲区的I/O方式，可以直接使用Native函数库来分配堆外内存，之后通过一个存储在Java中的DirectByteBuffer
对象作为对这块内存的引用来进行操作，这样可以在某些场景中显著地提高性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;（线程独有）程序计数器：存储当前程序的执行位置。&lt;/li&gt;
&lt;li&gt;（线程独有）虚拟机栈：通过栈帧来维持方法调用顺序，帮助控制程序的有序运行。&lt;/li&gt;
&lt;li&gt;（线程独有）本地方法栈：作用通虚拟机栈，只用于本地方法（Native Method）。&lt;/li&gt;
&lt;li&gt;堆：所有的对象都在这里保存。&lt;/li&gt;
&lt;li&gt;方法区：保存类信息、即时编译器的代码缓存、运行时常量池。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;垃圾回收机制&lt;/strong&gt;&lt;/h3&gt;
&lt;h6&gt;&lt;strong&gt;可达性分析算法&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;用于判断对象是否存活，此算法使用了类似于树结构的搜索机制，如果某个对象无法到达任何GC Roots，则说明这个对象是不可能再被使用的。&lt;/p&gt;
&lt;p&gt;每个对象都有机会成为树的根节点（GC Roots），被选定为根节点的条件如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;位于虚拟机栈的栈帧中的本地变量表中所引用到的对象（方法中的局部变量）同样也包括本地方法栈中JNI引用的对象。&lt;/li&gt;
&lt;li&gt;类的静态成员变量引用的对象。&lt;/li&gt;
&lt;li&gt;方法区中，常量池里面引用的对象。&lt;/li&gt;
&lt;li&gt;被加了锁的对象&lt;/li&gt;
&lt;li&gt;虚拟机内部需要使用到的对象。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/942cc7cc61477625b63fdd7fc5104771/Pasted&quot;&amp;gt;&lt;br /&gt;
一旦已经存在的根节点不满足存在的条件时，那么根节点与对象之间的连接将被断开。此时虽然对象1仍存在对其他对象的引用，但是由于其没有任何根节点引用，所以此对象即可被判定为不再使用。比如某个方法中的局部变量引用，在方法执行完成返回之后：&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/e1cd774878f95a297171739b746eca1b/Pasted&quot;&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h6&gt;&lt;strong&gt;最终判定&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;在经历了可达性分析算法后可以判定哪些对象可以被回收，但是并不代表对象一定会被回收，可以在最终判定阶段对将被回收的对象进行挽留。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected void finalize() throws Throwable { }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个方法是最终判定方法，如果子类重写了这个方法，那么子类对象在被判定为可回收的时候，会进行二次确认，即执行finalize()
方法，如果在这个方法中，对象重新建立了与GC Roots的引用，那么该对象将不会被回收。&lt;br /&gt;
finalize() 方法并不是在主线程中调用的，jvm会自动创建一个优先级较低的Finalizer线程来进行处理，且同一个对象的finalize()
方法只能被调用一次，如果第二次被判定为可回收的时候，该对象一定会被回收。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/7068807f4bced0f3430aaf8d51216dd6/Pasted&quot;&amp;gt;&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;垃圾回收算法&lt;/strong&gt;&lt;/h6&gt;
&lt;h6&gt;&lt;strong&gt;分代收集机制&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;堆内存的划分：新生代、老年代、永久代（JDK8之前）；元空间（JDK8）之后
JVM将堆内存划分为新生代、老年代和永久代（永久代是HotSpot虚拟机特有的概念，JDK8之前的方法区就是采用永久代来实现，在JDK8之后，方法区由元空间实现，且使用的是本地内存）&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/ae5e8ff218fa511e190fed990a7c847f/Pasted&quot;&amp;gt;&lt;br /&gt;
所有新创建的对象一开始都会进入新生代的Eden区，如果是大对象则会直接进入老年代，在对新生代区域进行垃圾回收时，首先会对新生代的对象进行扫描，并且会对不再使用的对象进行回收。&lt;/p&gt;
&lt;p&gt;在一次垃圾回收之后，Eden区没有被回收的对象会进入Survivor区域的From区，最后From区和To区会进行一次交换。&lt;/p&gt;
&lt;p&gt;在下一次垃圾回收的时候，由于From区已经存在对象，在GC之后Eden区存活的对象会直接复制到From区，随后所有To区中的对象会进行一次年龄判定（每经历一轮GC，年龄+1），如果对象的年龄大于15，则会直接进入老年代，否则移动到From区，最后再交换一次To区和From区。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;GC的分类&lt;/strong&gt;&lt;/h6&gt;
&lt;ul&gt;
&lt;li&gt;Minor GC：次要垃圾回收，主要进行新生代区域的GC。
-触发条件：新生代Eden区容量已满。&lt;/li&gt;
&lt;li&gt;Major GC：主要垃圾回收，主要对老年代进行GC。&lt;/li&gt;
&lt;li&gt;Full GC：完全垃圾回收，对整个Java堆内存和方法区进行GC。
&lt;ul&gt;
&lt;li&gt;触发条件1：每次晋升到老年代的对象平均大小大于老年代剩余的空间。&lt;/li&gt;
&lt;li&gt;触发条件2：Minor GC后存活的对象超过了老年代剩余的空间。&lt;/li&gt;
&lt;li&gt;触发条件3：永久代内存不足（JDK8之前，JDK8之后永久代使用的是本地内存）&lt;/li&gt;
&lt;li&gt;触发条件4：手动调用System.gc() 方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;GC流程图&lt;/strong&gt;&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/b2e6d2dcfb34729dfdc37ca43e49e9b6/Pasted&quot;&amp;gt;&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;空间分配担保&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;在一次GC后，新生代的Eden区仍然存在大量的对象，但此时Eden区中存活的对象所需的容量已经超出Survivor区的容量。此时，就会把Survivor区无法容纳的对象直接存到老年代中，让老年代进行空间分配担保，这样新生代就能腾出更多的空间来容纳更多的对象。如果此时老年代判断到剩余的容量也无法装下新生代的数据，就会进行一次Full
GC来尝试腾出空间，之后再次进行判断是否有空间存放新生代的数据，如果依然无法容纳，则会直接抛出OOM（OutOfMemoryError）错误。&lt;/p&gt;
&lt;h5&gt;&lt;strong&gt;垃圾回收算法&lt;/strong&gt;&lt;/h5&gt;
&lt;h6&gt;&lt;strong&gt;标记-清除算法&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;标记-清除算法是最古老的垃圾回收算法，通过标记出需要回收的对象，然后依次回收被标记的对象，或者标记出不需要回收的对象，回收掉没有被标记的对象来进行垃圾回收。&lt;/p&gt;
&lt;p&gt;优点是非常易于理解，十分简单。&lt;/p&gt;
&lt;p&gt;缺点是如果存在大量的对象需要回收，就有可能存在大量的标记并进行大规模垃圾回收，而且在一次垃圾回收之后，连续的内存空间内有可能出现大量空隙，使得内存空间碎片化，降低了连续内存空间的利用率。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;标记-复制算法&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;标记-复制算法将内存区域划分成两块同样大小的区域，每次只会使用一块内存区域，每当垃圾回收完毕之后就会将所有存活的对象复制到另一半空闲的内存区域中，并且清空之前被GC的内存区域，虽然多了一步复制所有对象的操作，但是解决了大规模GC后带来的内存空间碎片化的问题，提高了内存的利用率。&lt;/p&gt;
&lt;p&gt;此算法适合用于新生代垃圾回收，因为新生代在GC后一般不会留下太多对象，根据这个特性可以防止出现复制对象占用大量时间的问题。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/8b8751e43a59cc691ad3dcf119163809/Pasted&quot;&amp;gt;&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;标记-整理算法&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;老年代中的对象都是经过多次GC才会进入的，因此针对老年代的GC可能进行过之后依然会剩下大量对象，而标记-复制算法会在GC后完整地复制整个内存区域中的内容，并且会划分出一半区域用于复制对象，显然不符合老年代GC的需求。&lt;/p&gt;
&lt;p&gt;标记-整理算法是标记所有待回收的对象后，先不进行GC操作，而是先把所有被标记需要GC的对象整齐地排列在一片连续的内存空间中，此时老年代中的对象就分为两堆，一堆是不需要进行垃圾回收的对象，一堆是需要进行垃圾回收的对象，此时只需要回收那堆被标记需要回收的对象即可，解决了内存碎片的问题。&lt;/p&gt;
&lt;p&gt;此算法的优点是能保证内存空间的充分利用，并且实现的繁杂程度比标记-复制算法来说要低。
缺点是效率低，要修改对象在内存中的位置，此时程序必须要暂停才能进行修改，在某些极端情况下会导致整个程序发生停顿。&lt;/p&gt;
&lt;p&gt;因此，将标记-清除算法和标记-整理算法混合起来使用会是一种不错的解决方案，在内存空间不那么凌乱的时候使用标记-清除算法，当内存凌乱到一定程度的时候使用标记-整理算法。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/37662aa969f27c11e776114114665ccd/Pasted&quot;&amp;gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;垃圾收集器的实现&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Serial收集器&lt;/strong&gt;&lt;br /&gt;
Serial是最古老的一个垃圾收集器，在JDK1.3.1之前这个垃圾收集器是唯一选择，它是一个单线程垃圾收集器，在进行GC的时候会暂停所有的线程到GC工作结束。新生代垃圾收集算法是用的是标记复制算法，老年代垃圾收集算法采用的是标记整理算法。&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/f6cc9047f306e95dda5f2f5dedf7ffe7/jvmImage.png&quot; &amp;gt;&lt;br /&gt;
由图可以看到，进行GC的时候所有的用户线程都必须要等待GC线程完成工作，此垃圾回收器最大的缺点就是这个。&lt;br /&gt;
Serial收集器的优点是设计简单，在用户桌面应用场景中，使用内存量一般不会很大，因此可以在较短的时间内完成垃圾收集，在GC时因为用户线程暂停而造成的卡顿影响就会小得多，只要GC不频繁发生，使用Serial收集器是可以接受的。&lt;br /&gt;
在终端中输入 &lt;code&gt;java -version&lt;/code&gt;可以查看当前模式是否为客户端模式，如果是Client VM，那么在客户端模式下的新生代默认垃圾收集器至今依然是Serial收集器。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java version &quot;1.8.0_421&quot;
Java(TM) SE Runtime Environment (build 1.8.0_421-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.421-b09, mixed mode)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在jvm.cfg文件中切换JRE使用Server VM还是Client VM，文件的默认路径为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JDK安装目录/jre/lib/jvm.cfg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在此文件中把参数改成如下所示，就是客户端模式了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-server IGNORE
-client KNOWN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ParNew收集器&lt;/strong&gt;&lt;br /&gt;
这个收集器相当于Serial收集器的多线程版本，使用的垃圾回收算法是一样的，区别只是在于支持多线程垃圾收集：&lt;br /&gt;
&amp;lt;img src=&quot;https://p.sda1.dev/19/2ac1567240c09d65696d9e56c4c480c9/jvmImage2.png&quot; &amp;gt;&lt;br /&gt;
目前某些JVM默认的服务端模式的新生代垃圾收集器使用的就是此收集器。&lt;/p&gt;
&lt;p&gt;未完持续。。。。。。&lt;/p&gt;
</content:encoded></item></channel></rss>