架构师训练营第九周 - 总结
1.JVM虚拟机原理与编程优化
2.秒杀
JVM虚拟机原理与编程优化
JVM组成结构
java是一种跨平台的语言,JVM屏蔽了底层系统的不同,为Java字节码文件构造了一个统一的运行环境。
类加载器
运行期数据区
方法区
Java栈
堆
程序计数寄存器
执行引擎
Java字节码文件
Java在不同操作系统,不同硬件上运行的基础
Java虚拟机的指令码有200个左右,一个字节可以表示256种信息。Java通过一个字节表示了其所有指令信息
字节码文件前四个字节为cafe babe,是一个魔数,展现了Java字节码文件的特点,同时反应了Java发明者们的程序员幽默
字节码执行流程
方法调用
判断方法是否已被编译为机器码
编译为机器码的话,从Code Cache中取出编译后的机器码,直接调用
未编译为机器码,则方法调用计数器加1
判断计数是否超过阈值,未超过阈值,解释执行
方法调用计数器超过阈值,提交编译请求,由编译器异步编译,编译后,放入Code Cache中
本次调用依然使用解释执行
Java字节码文件编译过程
Java源文件
词法分析
语法分析
语义分析
生成字节码
类加载器的双亲委托模型
Bootstrap ClassLoader
Platform ClassLoader
Application ClassLoader
1-3,从父到子
类加载器在加载一个类之前,会请求父类代为加载,父类加载不了的,且允许子类加载的,当前类加载器才加载。
不同类加载器加载相同类到内存中,生成的Class对象是不同的,new出来的类实例也不可相互赋值
自定义类加载器
隔离加载类
同一个JVM中不同组件加载同一个类的不同版本
扩展加载源
从网络、数据库等处加载字节码
字节码加密
加载自定义的加密字节码,在ClassLoader中解密
运行期数据区
堆&堆栈
类实例和数组对象等是创建在堆中,所有线程共享,可能有并发问题。堆栈是线程独享的。
方法区&程序计数器
方法区中存储着加载的类信息,程序计数器记录着线程中方法运行到的代码行数。
Java(线程)栈
方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。
线程工作内存&volatile
Java内存模型规定,在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行。
一个共享变量(类的成员变量,类的静态成员变量)被volatile修饰之后,那么他就具备了两层含义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的
禁止进行指令重排序
Java运行环境
Java编译环境
源代码
Java编译器
Java字节码
Java平台运行期环境
类装载器
Java解释器与即时编译器
运行期系统
操作系统
硬件
JVM的垃圾回收
做垃圾回收需要解决的问题
什么样内存空间是垃圾
怎么处理垃圾
以什么样的方式确定和清理
什么时间清理
对于第一个问题,Java内存空间中,线程栈中的内存会随着栈帧入栈出栈而自动处理,方法区中的类信息占用内存小,也不需要处理。要处理的事堆中的内存。
怎么确定哪个是内存呢,通过可达性分析,判断哪些对象不再使用,进行清理。
可达性分析是通过栈帧中的对象引用,类信息中的静态成员对象引用,以及这些引用指向的对象的成员变量,逐级往下来找到所有可用的对象。则堆中没有在可达性中的对象都是可回收对象,可以进行垃圾回收
对于第二个问题,可以采用清理、压缩、复制的方式处理垃圾。
清理,就是标记对象内存空间为空闲,放入空闲列表,当应用程序需要创建新对象时,就从空闲空间列表中找一段空闲内存分配给这个新对象
压缩,从堆空间的头部开始,将活的空间拷贝到一段连续的内存空间中,那么其余的空间就是连续的空闲空间
复制,将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。
JVM分代垃圾回收
新生代
Eden区
From区
To区
老年代
JVM垃圾回收期算法
回收期种类
串行回收器
一个线程标记与清除垃圾
并行回收器
多个线程标记与清除垃圾
并发回收器
初始化标记、并发标记、重标记、并发清理
G1回收器
G1把内存分为很多块,这些块的类型有Eden、Survivor、Old、Humongous,每次选取一些块进行垃圾收集,尽量减少STW(Stop The World)的时间
以上四种垃圾回收期都会经历STW,区别是,尽量减少STW的时间,减小对应用程序的影响
JVM性能诊断工具
基本工具
JPS
查看host上所有JAVA进程ID
JSTAT
JVM提供的小工具,用以对JAVA应用程序的资源和性能进行实时的命令行监控,包括了对HeapSize和垃圾回收状况的监控
JMAP
可以输出所有内存中对象的工具,也可以把堆以二进制文本的方式输出
集成工具
JConsole
JVisualVM
集成工具,可以监控cpu,内存,类,线程等资源
Java代码优化
合理并谨慎使用多线程
使用场景,I/O阻塞、多CPU并发
启动线程数=【任务执行时间/(任务执行时间-IO等待时间)】* CPU内存数
竞态条件与临界区
多个线程竞争同一资源,如果对资源的访问顺序敏感,就称存在竞态条件。
导致竞态条件发生的代码区被称为临界区
在临界区中使用适当的同步就可以避免竞态条件
Java线程安全
允许被多个线程安全执行的代码称作线程安全的代码
局部变量
基本类型的局部变量,存储在栈中,是线程安全的
局部对象引用
局部对象引用,引用不逃逸出对象,则是线程安全的
对象成员
对象成员存储在堆中,可以被多线程访问,不是线程安全的
ThreadLocal
ThreadLocal中存储的值,与线程相关,每个线程取出的都是每个线程自己放入的值,那么是怎么做到的呢?
首先,要明白一点,Thread也是一个对象,那么,其,也是可以有成员变量的,Thread有一个成员变量threadLocals,类型是ThreadLocalMap。每次threadLocal调用get时,会去thread中查找这个值,存储的key是这个ThreadLocal对象引用,值是在这个线程中设置的value。不同的线程,有不同的value。
这个设计很精巧,缺点是存储的对象不删除的话,会造成Thread对象中的threadLocals成员变量指向的对象中存储的值得不到释放,会越积越多,造成内存泄漏。
Java内存泄漏
Java内存泄露是由于开发人员的错误引起的
如果程序保存对永远不再使用的对象的引用,这些对象将会占用并耗尽内存
长生命周期对象
静态容器
缓存
合理使用线程池和对象池
使用线程和对象资源,避免在程序的生命周期中,大量创建和删除对象
池管理算法(记录哪些对象是空闲的,哪些是正在使用的)
对象内容清除(ThreadLocal的清空)
使用合适的JDK容器类
LinkedList和ArrayList
HashMap
ConcurrentHashMap
缩短对象生命周期,加快垃圾回收
减少对象驻留内存的时间
在使用时,创建对象,用完释放
创建对象的步骤(静态代码段->静态变量->父类构造器->子类构造器)
使用I/O buffer及NIO
延迟写与提前读策略
异步无阻塞I/O通信
优先使用组合代替继承
减少对象耦合
避免太深的继承层次带来的对象创建性能损失
合理使用单例模式
无状态对象
线程安全
虚拟化所有层次
计算机的任何问题都可以通过间接层解决
一致性hash算法的虚拟化实现
面向接口编程
7层网络协议
秒杀系统
秒杀活动
秒杀是指在很短的时间内,多人抢购一件或几件商品,通常在一秒内抢购完成,所以称为秒杀,通常表示极短时间内活动时间很短。
秒杀系统特点
高并发
到点(秒杀时间点)才可以下单
秒杀面对的问题
高并发下的系统处理压力(应用、数据库)
高并发下的系统带宽压力
对原有业务系统的影响
解决问题的思路
秒杀系统单独立为一个系统,域名和原业务系统域名最好也做一个区分
秒杀申请独立的带宽,可以租用CDN
动态页面静态化,放在CDN中
业务信息,如商品,等存放在缓存中,加快读效率
在尽量保证公平公正的前提下,设置阈值,按照访问服务器层级,逐级减小后续服务器请求数
选用I/O处理优秀的应用服务器
页面尽量简洁
到点生成动态访问URL,存入js文件中,推送到js文件服务器,用户秒杀时,读取到最新的js文件,页面秒杀按钮可点击,服务器可受理。动态生成的URL,要无规律,不可猜出,这样才能保证秒杀时这个URL大家公平访问,秒杀前都不可访问,保证公平
秒杀中出现任何问题,服务端返回秒杀结束页面,减少产生纠纷
秒杀时,只关注必要流程,如下单。下单后,付款流程可在一定时间完成,如一小时内。
评论