《架构师训练营》之JVM & 搜索引擎
极客时间《架构师训练营》第九周学习笔记
JVM
JVM(java 虚拟机)就是一层用软件实现的物理机。Java 当年主打的核心特征就是:Write Once Run Anywhere;通俗来说就是编译器将 Java 文件编译成.class
,通过 JVM 加载并执行这些.class
文件;这种执行文件放在所有平台的 JVM 上都拥有相同的产出。
JVM 架构
我们先看看 JVM 的整体架构:
JVM架构如上图所示,JVM 主要由三部分组成:
-
类加载器(Class Loader Sub-system)
负责动态加载类:在运行时,加载、链接、初始化一个类。
-
运行时数据区(Runtime Data Areas)
又可以再细分五个区域:
- 方法区:为共享区,所有的类级别的数据(包括静态变量)都会存储到这里
- 堆:也是共享区,所有的对象,以及它对应的实例变量和数组都被存储在这个区域
- 栈:每个线程将创建单独的运行时栈区。对于每个方法调用,将在栈存储器中产生一个条目,称为栈帧,所有局部变量都创建于栈内存。栈区不共享资源,所以是线程安全的。
- 程序计数寄存器:每个线程都有单独的 PC 寄存器,用于保存当前执行指令的地址。一旦执行指令,PC 寄存器将被下一条指令更新。
- Native 方法栈:Native 方法栈保存本地方法信息。Native 方法指的是 Java 通过 JNI 直接调用本地 C/C++ 库,相当于 C/C++ 暴露给 Java 的一个接口。
-
执行引擎(Execution Engine)
上述过程生成的字节码将由执行引擎读取字节码并逐个执行。这里又包括解释器,JIT 编译器和垃圾收集器。(垃圾回收相关见作业一)
此外,还有两个 Native 方法相关模块:
- JNI:与本机方法库进行交互,并提供执行引擎所需的本机库。
- Native 方法库:执行引擎所需的本地方法库。
性能工具
-
JPS:用来输出 JVM 中运行的进程状态信息
-
Jstack:用来查看某个 Java 进程内的线程堆栈信息
-
Jmap & Jhat:用来查看堆内存使用状况
-
Jstat:JVM 统计监测工具
-
hprof:展现 CPU 使用率,统计堆内存使用情况
-
Jconsole:bin 目录下的工具,支持远程连接,可以查看 JVM 的概述,内存,线程等详细情况
Java 代码优化
线程安全
线程安全问题:在多个线程并发环境下,多个线程共同访问同一共享内存资源时,在其中一个线程对资源进行写操作的途中,其他线程对这个写了一半的资源进⾏了读操作,或者对这个写了一半的资源进⾏了写操作,最后导致此资源出现数据错误的情况。
Java 提供了一系列的关键字和类来保证线程安全:
-
Synchronized
:被 Synchronized 关键字描述的方法或代码块在多线程环境下同一时间只能由一个线程进行访问,保证方法或代码块操作的原子性。 -
Volatile
:相对 Synchronized 来说 Volatile 更加轻量一些,只对基本类型的赋值操作和对象的引用赋值操作有效。 -
java.util.concurrent.atomic
:这个包里包含了一些基于CAS(CompareAndSwap)
原理实现的类,如AtomicBoolean
、AtomicInteger
、AtomicLong
等,通过使用这些类声明可以保证对其操作具有原子性。 -
Lock:是一系列
java.util.concurrent
包内锁的统称,主要有ReentrantLock
、ReadLock
、WriteLock
等锁类,与 Synchronized 关键字不同, Lock 提供了获取锁和释放锁等相关接口,使用上更加灵活,同时也可以做更加复杂的操作。 -
ThreadLocal
:ThreadLocal 提供了线程的局部变量,每个线程都可以通过set()
和get()
来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离。
内存泄漏
内存泄漏:当应用程序不再使用对象时发生的情况,但是垃圾回收器无法将其从工作内存中删除,因为它们仍在被引用。因此,应用程序会消耗越来越多的资源,最终导致致命的 OutOfMemoryError。
从下图可以看到,GC 可达性标记后,所有引用被分为两种:引用和未引用。垃圾回收器只会删除未被引用的对象;有些对象出于某些原因不再被应用程序使用,但依旧被可达性标记了,这时 GC 将不会去回收这部分内存。
内存泄漏列举几个常见的 Java 内存泄漏场景:
- 静态引用:方法区里的静态资源引用了一个巨大的对象
- String.intern():该方法吧字符串对象存储在了 PermGen 空间中,这个空间中的对象不会被回收。不过,Java8 以后,PermGen 空间被 MetaSpace 替换了😅
- 在长生命周期的操作内,忘记关闭 Stream 或是未关闭连接
查找泄漏:
- 启用详细的垃圾回收:将 GC 参数添加到 JVM 配置中,就可以启用非常详细的 GC 跟踪
- 性能分析:VisualVM 等工具分析性能和调优
- 静态分析:定期代码审查,利用静态分析工具来帮助你了解代码和系统的状况
其他
- 合理使用线程池和对象池
- 使用合适的 JDK 容器类
- 缩短对象生命周期,加速垃圾回收
- 使用 I/O buffer 及 NIO
- 优先使用组合代替继承
- 合理使用单例模式
- 虚拟化所有层次
搜索引擎
Inverted index(倒排索引)
倒排索引,也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。
一般数据库通常基于 Forward Index,即以文档 ID 作为索引,以文档内容作为记录。而 Inverted index 指的是将单词或记录作为索引,将文档 ID 作为记录,这样便可以方便地通过单词或记录查找到其所在的文档。
倒排索引Lucene 架构
Lucene 是一套用于全文检索和搜索的开放源码程序库,由 Apache 软件基金会支持和提供。Lucene 提供了一个简单却强大的应用程序接口,能够做全文索引和搜索。
LuceneElasticSearch 架构
Elasticsearch 是一个基于 Lucene 库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎,具有 HTTP Web 接口和无模式 JSON 文档。
Elasticsearch 是分布式的,这意味着索引可以被分成分片,每个分片可以有 0 个或多个副本。每个节点托管一个或多个分片,并充当协调器将操作委托给正确的分片。再平衡和路由是自动完成的。相关数据通常存储在同一个索引中,该索引由一个或多个主分片和零个或多个复制分片组成。
ElasticSearch秒杀系统
见作业第二题
小结
这周的重点是 JVM 相关,说实在我已经好久没写过 Java 了,当年看到 JVM 调优就头晕。回想起来还是畏缩情绪作怪,根本就没学过相关知识就被吓退了。幸好这章有操作系统的线程知识做铺垫,JVM 架构触类旁通,也很快理解了。现在想想,科班知识才是一切技术基础,重中之重,只可惜当年没有悟到。