训练营第九周作业 2
作业二:
根据当周学习情况,完成一篇学习总结
JVM
JVM(java 虚拟机)就是一层用软件实现的物理机。Java 当年主打的核心特征就是:Write Once Run Anywhere;通俗来说就是编译器将 Java 文件编译成.class
,通过 JVM 加载并执行这些.class
文件;这种执行文件放在所有平台的 JVM 上都拥有相同的产出。
JVM 架构
我们先看看 JVM 的整体架构:
![](https://static001.geekbang.org/infoq/3a/3a7cdd4dc8c109737a7a90c95eadd546.png)
JVM 架构
如上图所示,JVM 主要由三部分组成:
类加载器(Class Loader Sub-system)
运行时数据区(Runtime Data Areas)
执行引擎(Execution Engine)
此外,还有两个 Native 方法相关模块:
JNI:与本机方法库进行交互,并提供执行引擎所需的本机库。
Native 方法库:执行引擎所需的本地方法库。
字节码执行流程
![](https://static001.geekbang.org/infoq/e3/e35a791c770aa27889cbb49603b5ce61.png)
字节码无法直接交给硬件执行需要虚拟机翻译成机器码才能执行,“翻译”的策略有两种:解释执行和编译执行又称即时编译(JIT)。
解释执行是每执行一句字节码的时候把字节码翻译成机器码并执行,优点是启动效率快,缺点是整体的执行速度较慢。编译执行预先把所有机器码编译成字节码并一起执行,其特点与解释执行相反,启动较慢执行较快。
在 JVM 中是两者混合出现。如上图所示,如果该方法未被编译,将方法的计数器+1,判断该方法被调用的次数是否达到指定阈值?如果达到,说明该方法是热点方法,把该方法用编译执行的策略编译好缓存,下次执行的时候直接调用;如果尚未达到则使用解释执行。
类加载器的双亲委派
![](https://static001.geekbang.org/infoq/fa/fa30c28170e7b3a143d059cd30b53eb0.png)
优先级:Bootstrap ClassLoader > Platform ClassLoader > Application ClassLoader > User ClassLoader
JVM 内存区域分工
![](https://static001.geekbang.org/infoq/3b/3b505bdc4932099d2ea8da858708af56.png)
JAVA 运行环境
![](https://static001.geekbang.org/infoq/6e/6e9af8d83daba9ee4cb852da3f10bcd2.png)
垃圾回收器
垃圾回收器一直在发展,一共经历了四个阶段:
Serial(串行)收集器
Parallel(并行)收集器
CMS(并发)收集器
G1 收集器
主要说说 G1 收集器。G1 之前的 JVM 堆模型如上面分代收集算法提到的,分为新生代、老年代和永久代。G1 改变了这个模型,堆被分为多个大小连续的区域,这些区域被标记为 E、S、O 和 H,分别对应 Eden,Survivor,Old 区和巨型区。
![](https://static001.geekbang.org/infoq/ee/ee8b822a6767d61a2a44590cbe3e3bba.png)
G1 收集器
G1 也有两种收集模式:
Young GC: 当 Eden 区达到一定阈值时触发;会将 Eden 和 Survivor 清理,并复制到 Old 区以及一部分 Survivor 区。
MixedGC:Old 区也达到一定阈值时,回收所有 Eden 区、Survivor 区,以及部分 Old 区的内存。
性能工具
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 将不会去回收这部分内存。
![](https://static001.geekbang.org/infoq/4f/4f997049809f1c494d911a70b8ea818a.jpeg?x-oss-process=image/resize,p_80/auto-orient,1)
内存泄漏
列举几个常见的 Java 内存泄漏场景:
静态引用:方法区里的静态资源引用了一个巨大的对象
String.intern():该方法吧字符串对象存储在了 PermGen 空间中,这个空间中的对象不会被回收。不过,Java8 以后,PermGen 空间被 MetaSpace 替换了😅
在长生命周期的操作内,忘记关闭 Stream 或是未关闭连接
查找泄漏:
启用详细的垃圾回收:将 GC 参数添加到 JVM 配置中,就可以启用非常详细的 GC 跟踪
性能分析:VisualVM 等工具分析性能和调优
静态分析:定期代码审查,利用静态分析工具来帮助你了解代码和系统的状况
其他
合理使用线程池和对象池
使用合适的 JDK 容器类
缩短对象生命周期,加速垃圾回收
使用 I/O buffer 及 NIO
优先使用组合代替继承
合理使用单例模式
虚拟化所有层次
高性能秒杀
挑战
秒杀系统的核心挑战就是高并发:在极短时间内有大量请求达到,可能导致全站奔溃。其他的问题还包括:
超卖
恶意请求
链接暴露
解决方案
核心问题是高并发,所以解决方案的中心思想就是流量控制和性能优化。
服务单一职责
扩容
静态化资源
加盐:通俗来说就是秒杀请求的 URL 必需带一个加密的参数,比如添加一个动态生成的 token,经后端校验请求才能通过。这个主要是防止事先暴露 URL。
限流
库存预热
![](https://static001.geekbang.org/infoq/a0/a0ff472a74495359e2442f50460b7d70.png)
评论