架构师训练营 - 学习笔记 - 第九周
由于之前没了解过 JVM 的运行原理, 这节 JVM 课听的还是比较费劲的。看来我对 Java 的理解还停留在表面。。。
思考与感悟
扩大你的影响力
不要让你的影响力只限于你的小组内,要做跨部门的沟通和分享,在企业内要解决企业内的问题,做出NB的工具、框架、软件,让别人依赖你的产品。
程序员扩大影响力的方法有:出书、写/创作(博客/开源/公司代码)、演讲、帮助人。
要做低风险,高回报的事
做新的东西(新系统、接口、模块等)是低风险,高回报的事情,因为是从无到有,别人都知道是你做的。而且相对来说设计、开发、验证时间比较充裕。
但维护老的系统是高风险,低回报的,因为维护的好,别人会认为是应该的。维护的不好,那么所有的矛头也都会指向你,一般维护这种项目通常维护时间较为紧迫、沟通成本较高(可能老系统牵扯到各方的利益)。
”不要再让我讲干货“
虽然智慧老师没说上面的话,但给我感觉已经是要脱口而出了。
有2种不同的教学风格:
第一种,不断地讲干货,然后通过一个实战项目练习学到的知识。
第二种,讲具体知识,但更多的是讲自己经历过的项目、经验、思路、解决问题方法等(像智慧老师这种)。
我比较喜欢智慧老师的这种教学风格,因为我觉得干货很有可能解决不了你具体场景中的具体问题(甚至你根本就不需要用干货去优化架构),况且如果你不去思考架构背后的设计哲学,它要解决的问题,那你很难用好它,所以还是要理解你要解决的问题是什么。
所以当智慧老师说:”我不是一个专门教书的老师,我只是把我自己经历过的项目、经验和大家分享“,
我感觉隔着屏幕都能够深深地感受到智慧老师的不被理解与无奈!
至少我还是很喜欢智慧老师的这种教学风格的。
抓住问题的关键点 —— 找到问题
解决问题很多时候并不需要高大上的解决方案和架构,尤其是在有限的时间内,往往都是简单、实用的方法。所以最关键的还是能否抓住问题的关键点?找到隐藏在问题表象下真正的问题。一旦抓住了关键点,很多问题就迎刃而解了——因为我相信解决方法还是比问题要多得多的。
衡量一个架构师的好坏与否是对问题的洞察
我们学了那么多知识,是为了在关键时刻解决问题,正所谓厚积薄发。就像我以前大学数据结构老师说的,”我知识的广度与深度不如别人,但遇到问题时,我能快速给出解决方案,正所谓一招先。”
【技术人,千万别把 1 年经验用 10 年】
智慧老师说:“不要谈工作经验,几年工作经验这种说法没有意义,重要的是附带的价值是什么。”
周三参加了刘超老师的线上直播【技术人,千万别把 1 年经验用 10 年】(貌似 B 站的视频还没放出来),标题和智慧老师的理念还是很相似的。以下是摘录:
职业生涯的第二阶段,明确方向之后的深耕
技术前景 —— 看市场需求
打造个人竞争力 —— 参与核心项目
积淀中台能力 —— 有意识的构建可复用能力
JVM - 2020/7/31 - 周四
JVM 组成架构
类文件
类加载器
运行期数据区
方法区(特殊的堆)
堆
Java 栈
程序计数寄存器
执行引擎
不同 OS 不相同
Java 字节码文件
计算机领域的任何问题都可以通过增加个中间层(虚拟层)来解决。
Java 所有指令 200 个左右,一个字节 8 位,存放 256种指令。
JVM 编译执行 VS 解释执行
热点代码
JIT 动态编译位机器码,提高执行效率
字节码执行流程
类加载器的双亲委托模型
低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,才让当前类加载器加载这个未知类。
自定义类加载器
隔离加载类:同一个 JVM 种不同组件加载同一个类的不同版本。
扩展加载源:从网络、数据库等处加载字节码。
字节码加密:加载自定义的加密字节码,在 ClassLoader 中解密。
堆 & 栈
堆:每个 JVM 实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。
堆栈:JVM 为每个新创建的线程都分配一个堆栈。
Java 中所有对象的存储空间都是在堆中分配的,但是这个对象的引用确是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已。
方法区 & 程序计数器
方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的,当 JVM 进入启动类的 main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程都有自己的 Java 栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器。
Java (线程)栈
所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
线程工作内存 & volatile
volatile 关键字,什么意思?
每次都从主内存中读取变量值,而不是从 CPU 的寄存器缓存中。
Java 内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行。
一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
禁止进行指令重排序。
Java 运行环境
JVM 的垃圾回收
JVM 垃圾回收就是将 JVM 堆中不再被使用的对象清理掉,释放宝贵的内存资源。
JVM 通过一种可达性分析算法进行垃圾对象的识别并标记,它对线程栈中的局部变量或者静态变量出发,将这些变量引用的对象做标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。
回收主要有三种方法:
清理
将原来垃圾对象内存空间标记为空闲。
压缩
从堆空间头部开始,将存活的对象拷贝到一段连续的内存空间中。
复制
将堆空间分成两部分,当一部分用完时,将标记过的对象复制到另一个空间中。
分代回收:将堆分为新生代、老年代。新生代又分为 Eden 区、Survivors(From 区、To 区)。
新生代
Eden
Survivors
From
To
老年代
新创建的对象一开始处于新生代区中,当新生代区满时,将存活的对象复制到 From 区,等到下次新生代再次满时,新生代和 From 区中的存活对象会被一起复制到 To 区。如果一个对象经过多轮的 From 区和To 区之间的复制后仍然存活,那么它会被复制到老年代。新生代的空间远小于老年代,空间小在垃圾回收时效率更高。
JVM 会记录 Survivor 区中的对象一共被来回复制了几次。如果一个对象被复制的次数达到一个值(对应虚拟机参数 -XX:+MaxTenuringThreshold),该对象会被promo 至老年代。如果带个 Survivor 区被占用 50%(对应虚拟机参数 -XX:TargetSurvivorRatio),较高复制次数的对象也会被 promo 至老年代。
回收算法:
串行回收器
并行回收器
并发回收器 CMS
G1 回收器
在回收过程中会产生 stop-the-world 现象。
JVM 性能诊断工具
基本工具:JPS, JSTAT, JMAP
集成工具:JConsole, JVisualVM
JPS
用来查看 host 上运行的所有 java 进程的 pid(jvmid), 只是为了找出运行 jvm 的进程 ID, 即lvmid
JSTAT
Java Virtual Machine statistics monitoring tool, JDK 自带的一个轻量级小工具。主要对 Java 应用程序的资源和性能进行实时的命令行的监控。Heap size, 垃圾回收状况的监控。
JConsole
JVisualVM
Java 代码优化
合理并谨慎使用多线程
使用场景(I/O 阻塞,多 CPU 并发)
资源争用与同步问题
java.util.concurrent
启动线程数 = [任务执行时间 / (任务执行时间 - IO 等待时间)] * CPU 内核数
和 CPU 内核数量成正比,和 IO 阻塞时间成反比。
竞态条件与临界区
在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。
导致竞态条件发生的代码区称作临界区。
在临界区中使用适当的同步就可以避免竞态条件。
Java 线程安全
允许被多个线程安全执行的代码称作线程安全的代码。
安全
基础类型的局部变量
局部的对象引用
不安全
对象成员
对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
ThreadLocal
Java 内存泄露
Java 内存泄漏是由于开发人员的错误引起的。
如果程序保留对永远不再使用的对象的引用,这些对象会占用并耗尽内存。
长生命周期对象
静态容器
缓存
合理使用线程池和对象池
复用线程或对象资源,避免在程序的生命期中创建和删除大量的对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal 的清空)
使用合适的 JDK 容器类(顺序表,链表,Hash)
LinkList 和 ArrayList 的区别适用场景
HashMap 的算法实现即应用场景
使用 concurrent 包,ConcurrentHashMap 和 HashMap 的线程安全特性有什么不同?
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段 -> 静态变量 -> 父类构造函数 -> 子类构造函数)
使用 IO buffer 及 NIO
延迟写与提前读策略
异步无阻塞 IO 通信
有限使用组合代替继承
减少对象耦合
避免太深的继承层次带来的对象创建性能损失
合理使用单例模式
无状态对象
线程安全
虚拟化所有层次
计算机的任何问题都可以通过间接层解决
一致性 hash 算法的虚拟化实现
面向接口编程
7 层网络协议
高性能案例秒杀 - 2020/8/1 - 周六
参考我写的“架构师训练营 - 作业 - 第九周”
https://xie.infoq.cn/article/7262e012fec6ae144c7706cbf
参考连接
https://xie.infoq.cn/article/d000de5b600f0b2bf17af7282
评论