Week9 学习总结(JVM 虚拟机原理、Java 代码优化、秒杀系统)

用户头像
星河寒水
关注
发布于: 2020 年 08 月 01 日
Week9学习总结(JVM虚拟机原理、Java代码优化、秒杀系统)

JVM虚拟机原理

JVM组成架构

Java是一种跨平台的语言,JVM屏蔽了底层系统的不同,为Java字节码文件构造了一个统一的运行环境。

Java 字节码文件

Java 如何实现在不同操作系统、不同硬件平台上,都可以不用修改代码就能顺畅地执行?

计算机领域的任何问题都可以通过增加个中间层(虚拟层)来解决

Java所有的指令有200个左右,一个字节(8位)可以存储256种不同的指令信息,一个这样的字节称为字节码(Bytecode)。在代码的执行过中,JVM将字节码解释执行屏蔽对底层操作系统的依赖,JVM也可以将字节码编译执行,如果是热点代码,会通过JIT动态地编译为机器码,提高执行效率

字节码执行流程

Java字节码文件编译过程

类加载器的双亲委托模型

低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类。如果低层次的类加载器想加载一个未知类,需要上级类加载器确认,只有当上级类加载器没有加载过这个类,也允许加载的时候,才让当前类加载器加载这个未知类。

自定义类加载器

  • 隔离加载类:同一个JVM中不同组件加载同一个类的不同版本。

  • 扩展加载源:从网络、数据库等处加载字节码。

  • 字节码加密:加载自定义的加密字节码,在ClassLoader中解密。

堆&栈

  • 堆:每个JVM实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。

  • 堆栈:JVM为每个新创建的线程都分配一个堆栈。也就是说,对于一个 Java程序来说,它的运行就是通过对堆栈的操作来完成的。

Java 中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已。

方法区&程序计数器

方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的,当JVM进入启动类的main方法的时候,就会为应用程序创建一个主线程,main方法里的代码就会被这个主线程执行,每个线程有自己的Java栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器。

package com.bjsxt.test;
public class Demo01 {
public static void main(String[] args)
A a = new A();
System.out.println(A.width);
}
}
class A {
public static int width=100;
static{
System.out.println("静态初始化类A");
width=300;
}
public A(){
System.out.println("创建A类的对象");
}
}



Java(线程)栈

所有在方法内定义的基本类型变量,都会被每个运行这个方法的线程放入自己的栈中,线程的栈彼此隔离,所以这些变量一定是线程安全的。

线程工作内存 & volatile

Java内存模型规定在多线程情况下,线程操作主内存变量,需要通过线程独有的工作内存拷贝主内存变量副本来进行。

一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  • 禁止进行指令重排序。

Java运行环境

JVM的垃圾回收

JVM垃圾回收就是将JVM堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。

JVM通过一种可达性分析算法进行垃圾对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。

进行完标记以后,JVM就会对垃圾对象占用的内存进行回收,回收主要有三种方法

  • 清理:将垃圾对象占据的内存清理掉,其实JVM并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。

  • 压缩:从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。

  • 复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。

JVM分代垃圾回收

JVM垃圾回收器算法

G1垃圾回收内存管理机制

-XX:MaxGCPauseMillis

Java启动参数

标准参数,所有的JVM实现都必须实现这些参数的功能,而且向后兼容

  • 运行模式-server,-client

  • 类加载路径-cp,-classpath

  • 运行调试--verbose

  • 系统变量--D

非标准参数,默认jvm实现这些参数,但不保证所有jvm实现都实现,且不保证向后兼容

·-Xms初始堆大小

·-Xmx最大堆大小

·-Xmn新生代大小

·-Xss线程堆栈大小

非Stable参数,此类参数各个jvm实现会有所不同,将来可能会随时取消

·-XX:-UseConcMarkSweepGC 启用CMS垃圾回收

JVM 性能诊断工具

基本工具:JPS,JSTAT,JMAF

集成工具:JConsole,JVisualVM

JPS

JPS 用来查看 host 上运行的所有 java 进程的 pid (jvmid),一般情况下使用这个工具的目的只是为了找出运行的jvm进程ID,即lvmid,然后可以进一步使用其它的工具来监控和分析JVM。

常用的几个参数:

  • -l 输出java应用程序的main class的完整包

  • -q 仅显示pid,不显示其它任何相关信息

  • -m 输出传递给main方法的参数

  • -v 输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置

JSTAT

JSTAT("Java Virtual Machine statistics monitoring tool")是JDK自带的一个轻量级小工具。主要对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

语法结构如下:jstat [Options] vmid [interval] [count]

  • Options -- 选项,我们一般使用 - gcutil查看gc情况

  • vmid -- VM的进程号,即当前运行的java进程号

  • interval -- 间隔时间,单位为毫秒

  • count -- 打印次数,如果缺省则打印无数次

S0 -- Heap上的 Survivor space 0 区已使用空间的百分比

S1 -- Heap上的 Survivor space 1区已使用空间的百分比

E -- Heap上的 Eden space 区已使用空间的百分比

O -- Heap上的 Old space 区已使用空间的百分比

YGC -- 从应用程序启动到采样时发生 Young GC 的次数

YGCT -- 从应用程序启动到采样时 Young GC 所用的时间(单位秒)

FGC -- 从应用程序启动到采样时发生 Full GC 的次数

FGCT -- 从应用程序启动到采样时 Full GC 所用的时间(单位秒)

GCT -- 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

JMAP

JMAP是一个可以输出所有内存中对象的工具,甚至可以将VM中的heap,以二进制输出成文本。

使用方法

  • jmap-histo pid>a.log可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以对比出GC回收了哪些对象。

  • jmap-dump:format=b,file=f1 PID可以将该PID进程的内存heap输出出来到f1文件里。

JConsole

JVisualVM

Java代码优化

合理并谨慎使用多线程

使用场景(1/0阻塞,多CPU并发)

资源争用与同步问题

java.util.concurrent

  • 最佳启动线程数和CPU内核数量成正比,和IO阻塞时间成反比。如果任务都是CPU计算型任务,那么线程数最多不超过CPU内核数,因为启动再多线程,CPU也来不及调度;相反如果是任务需要等待磁盘操作,网络响应,那么多启动线程有助于提高任务并发度,提高系统吞吐能力,改善系统性能。

竞态条件与临界区

在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。

在临界区中使用适当的同步就可以避免竞态条件。

Java线程安全

允许被多个线程安全执行的代码称作线程安全的代码。

局部变量

  • 局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。

局部的对象引用

  • 如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。

对象成员

  • 对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。

Java Web应用的多线程从哪儿来的?

  • 来自于容器

Servlet是线程安全的吗?

  • 不安全。Servlet是单实例的,所有的用户请求线程访问的是同一个Servlet,而Servlet里的成员变量就有可能被多线程同时修改、同时访问到。

  • 安全。一个Servlet安全不安全,Servlet本身是不知道的,你有多线程共享的变量你就是不安全的,没有共享变量的话就是安全的。Servlet本身是安全的,但是你写了一个不安全的类就会导致它不安全。

ThreadLocal

创建一个 Threadloca|变量

  • private ThreadLocal myThreadLocal = new ThreadLocal();

存储此对象的值

  • myThreadLocal.set("A thread local value");

读取一个 Threadlocal对象的值:

  • String threadLocalValue=(String) myThreadLocal.get();

public void set(T value){
Thread t = Thread.currentThread();
ThreadLocalMap map=getMap(t);
if (map != null)
map.set(this,value);
else
createMap(t,value);
}
void createMap (Thread t, T firstValue) {
t.threadlocals=new ThreadLocalMap(this,firstValue);
}

Java内存泄漏

Java内存泄漏是由于开发人员的错误引起的。

如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存。

  • 长生命周期对象

  • 静态容器

  • 缓存

合理使用线程池和对象池

  • 复用线程或对象资源,避免在程序的生命期中创建和删除大量对象

  • 池管理算法(记录哪些对象是空闲的,哪些对象正在使用)

  • 对象内容清除(ThreadLocal的清空)

使用合适的JDK容器类(顺序表,链表,Hash)

  • LinkList和ArrayList的区别及适用场景

  • HashMap的算法实现及应用场景

  • 使用concurrent包,ConcurrentHashMap和HashMap的线程安全特性有什么不同?

缩短对象生命周期,加速垃圾回收

  • 减少对象驻留内存的时间

  • 在使用时创建对象,用完释放

  • 创建对象的步骤(静态代码段-静态变量-父类构造函数-子类构造函数)

使用I/O buffer及NIO

  • 延迟写与提前读策略

  • 异步无阻塞IO通信

优先使用组合代替继承

  • 减少对象藕合

  • 避免太深的继承层次带来的对象创建性能损失

合理使用单例模式

  • 无状态对象

  • 线程安全

虚拟化所有层次

  • 计算机的任何问题都可以通过间接层解决

  • 一致性hash算法的虚拟化实现

  • 面向接口编程

  • 7层网络协议

秒杀

XXXX 性能现状

XXXX网站的正常流量情况

  • 并发(单台),高峰期<10;

  • 吞吐量(TPS,单台)高峰期,<60;

  • CPU负载Load高峰期,<2,大部分服务器<1;

  • CPU使用率,一般只占1颗核,平均60%左右;

  • 服务器平均响应时间高峰期,<150ms;

  • 图片总流量带宽1.8G(各网站总合)。



高并发下的风险

  • 网络带宽耗尽

  • 服务器Load飘高,停止响应。

  • 数据库瘫疾



高并发下的事故

  • 事故:网站运营推广页面弹出1兆大图片导致带宽耗尽

  • 增加审核机制:运营推广增加的图片流量不能超过现有流量的30%

  • 合作媒体推广:迅雷,暴风影音浮出广告,导致ZZ集群Crash。



秒杀

  • XXXX.com开业88小时不间断秒杀活动

高并发对网站性能的影响

  • 并发数对吞吐量的影响

  • 并发数对用户平均请求等待时间的影响

  • 并发数对服务器平均请求响应时间的影响

高并发实例:XXXX.com开业秒杀活动

商业需求

  • 为庆祝XXXX.com开业推出88小时不间断秒杀活动。

  • 每小时整点推出8款商品......

  • 每款商品供168件,每人限批3件,成交人数56人。

  • CCTV黄金广告时间,各种网络,平面媒体轰炸,总广告费:1.5亿。

  • 接到运营通知,距秒杀开始仅仅5天时间。

技术挑战

  • 瞬间高并发

  • 8000并发:预估秒杀在线人数可达8000人。

  • 风险:带宽耗尽。

  • 服务器:崩溃,可以理解成自己给自己准备的D.D.O.S攻击。

  • 秒杀器

  • 第一种:秒杀前不断刷新秒杀页面,直到秒杀开始,抢着下单。

  • 第二种:跳过秒杀页面,直接进入下单页面,下单。

XXXX.com 秒杀系统:服务器和网络准备

服务器准备(距秒杀开始仅五天时间来不及采购)

  • style服务器(Lighttpd集群):5台

  • 图片服务器(Nginx集群):5台

  • 静态服务器(Apache集群):10台

  • 交易服务器(JBoss动态集群):10台

带宽准备

  • 图片出口带宽上限:2.5G(出口带宽支持10G,但图片服务器集群的处理能力:图片服务集群最大并发处理能力 * 网站平均图片大小=2.5G)

  • CDN准备:Chinacache沟通;借用CCCC CDN

XXXX.com 秒杀系统:架构目标

1.图片网络带宽:1.0G

新增图片带宽:必须控制在1.0G左右

每件商品秒杀页面的图片总大小不得超过:1000000/(1000*8)=125K/每商品

2.网站并发:

单件商品并发:1000【来自运营的预估】

总并发:8(件商品)*1000(人/商品)=8000

XXXX秒杀系统:组成

简单系统:

三个页面组成:秒杀商品列表,秒杀商品介绍,下单

XXXX.com 秒杀系统:设计原则

静态化

  • 采用JS自动更新技术将动态页面转化为静态页面

并发控制,防秒杀器

  • 设置阀门,只放最前面的一部分人进入秒杀系统

简化流程

  • 砍掉不重要的分支流程,如下单页面的所有数据库查询

  • 以下单成功作为秒杀成功标志。支付流程只要在1天内完成即可。

前端优化

  • 采用YSLOW原则提升页面响应速度

XXXX秒杀系统:静态化(1)

秒杀商品list和Detail是静态HTML页面



XXXX秒杀系统:静态化(2)

秒杀商品列表/秒杀商品介绍页面,如何判断秒杀开始否。

答案:valid-offer.js

var valid_offerIds=23624364,53778658,35885833,46999696,5006797057

三道阀门的设计

阀门:基于TT的计数器



秒杀器的预防

秒杀 Detail 页面

  • URL:随机

  • 秒杀前2秒放出,脚本生成,秒杀前。

  • 1000次访问上限控制【每件商品只能放入1000人浏览】。

下单页面:

  • 订单ID,随机。

  • 不能直接跳过秒杀Detail页面进入。

  • 每个秒杀商品,带预先生成的随机Token作URL参数。

  • 如果秒杀过,直接跳到秒杀结束页面。

  • 100次访问上限控制【每件商品只能放入100人下单】。

Web Server 调优-Apache调优

  • KeepAlive 相关参数调优

  • 其他参数调优

  • HostnameLookups设为off,对allowfromdomain等后的域名不进行正向和反向的dns解析。

  • 关闭cookies-log日志

  • 打开 Linux sendfile()

  • 关闭无用的module

  • mod_Gzip

  • (秒杀页面,非图片HTML文本所占流量比重可忽略不计,zip意义不大)

  • mod_Beacon

  • mod_hummock(等待反应过来,秒杀已经over了)

Web Server 调优-JBoss调优

Mod-jk worker 调优

JkWorkerProperty worker.list=localnode
JkWorkerProperty worker.localnode.port=7011
JkworkerProperty worker.localnode.host=localhost
JkWorkerProperty worker.localnode.type=ajp13
JkWorkerProperty worker.localnode.ibfactor=1
JkWorkerProperty worker.localnode.socket_keepalive=True
JkWorkerProperty worker.localnode.socket_timeout=20
JkWorkerProperty worker.localnode.connection_pool_minsize=25
JkWorkerProperty worker.localnode.connection_pool_timeout=600

JBoss AJP Connector

Tomcat APR 设定

秒杀静态页面优化

图片合并

  • 8张图片合并成1张,CSS偏移展示。

  • 减少HTTP请求数,减少请求等待数。

  • 减少发送Cookies的量。

HTML内容压缩

图片压缩:图片 Bytes < 长*宽/2250

HTML Header Cache-Control 设置

CSS, JS精简

  • CSS,JS精简到极致,部分直接写在页面中,减少HTTP请求次数。

下单页面优化

数据库操作:全部砍掉

  • 原下单页面要访问8次数据库,全部砍掉。

秒杀流程精简

  • 砍掉填写或选择收货地址,放在秒杀成功后填写。

  • 砍掉调用是否开通支付接口,秒杀首页文案提示必须开通。

采用内存缓存

  • 秒杀Offer数据,支付相关信息,缓存。

交易系统性能优化

  • 交易系统调优目标:

  • 关闭KeepAlive(分析交易系统 accesslog,用户在短时间内连续点击概率很低)

  • JVM优化

  • 优化CMS垃圾回收器的参数

  • 消灭Top10Bottlenecks

  • Velocity参数调优

  • 采用DBCP1.4替换C3P0

  • Offer产品参数的XML解析

二跳页面的优化

XXXX.com其他页面

  • 前端优化:Yslow规则调优

  • 减少HTTP请求,合并JS,CSS,图片,充分利用浏览器缓存。

  • 图片压缩,公式: 图片 Bytes < 长*宽/2250

  • 避免发送Cookies

交易系统优化

  • 普通订单管理列表和XXXX秒批订单管理列表分离

  • 禁止用模糊查询功能

应急预案

  • 域名分离,独立域名,不影响XXXX原有业务。

>Style集群:style.XXXX.china.XXXX.com

>图片服务器集群:img.XXXX.china.XXXX.com

>静态页面集群:page.XXXX.china.XXXX.com

>出问题直接把XXXX相关域名卡掉,所有请求跳到万能出错页面。

  • 机动服务器10台,备用。

  • 拆东培补西墙战略

>5天时间来不及采购服务器,因此SA待命,随时准备将非核心应用集群的兄余服务器下线,加入到秒杀集群。

  • 壁虎断尾策略

>所有办法均失效的情况下,例如流量耗尽。

>非核心应用集群统统停止服务,如资讯,论坛,博客等社区系统。

>保住首页,Offer Detail,旺铺页面等核心应用的可用性。

  • 万能出错页面:秒杀活动已经结束

>任何出错都302跳转到此页面

>位于另外集群

  • 万幸:最终所有的预案都没有用上

秒杀活动结果

88小时秒杀,坚守阵地,大获成功。

秒杀还是被秒杀?终于有了答案

三道阀门设计非常有效,拦住了秒杀器。

  • XXXX.com 静态集群总并发情况(首页,秒杀列表,秒杀商品页面)

  • 交易系统集群总并发情况(下单页面)

改进一:采用更轻量/快速的服务器(1)

采用 Lighttpd 替代 Apache 杀手铜(AIO)

Lighttpd 1.5 VS Apache2.2.4

小页面性能(100K)

  • http_load-verbose-timeout 40-parallel 100-fetches 500 http-load.10M.urls-100M

大页面性能(10M)

性能关键:Web Server的高性能I/O

XXXX应用服务器升级项目:

Apache2.2+Mod-Proxy+Jetty7.1.5 与XXXX现有架构性能对比:

性能大幅提升,XXXX全站下线1/3应用服务器(100台,明年不用采购新机器)

架构更轻量,配置更简单

应用更无状态化,开发和维护的福音

更加安全

改进二:前端优化自动化

  • XXXX服务器响应时间<150ms,但Offer Detail 页面用户等待时间5s,大部分时间耗在路上(资源请求和网络传输)

  • 图片自动压缩(CMS自动压缩)

  • Cookies服务化(控制Cookies的大小)

  • XXXX前端延迟加载框架SmartLoad(只加载首屏数据)

  • Google mod pagespeed module

  • 自动压缩图片,静态资源,智能浏览器缓存技术

  • Google Diffable(增量下载静态资源技术)

改进三:架设镜像站组建山寨CDN

XXXX 青岛镜像站项目



改进四:采用反向代理加速核心页面

在Offer集群前部署 Squid反向代理集群

  • Offer Detail的Squid反向代理改造

  • 基于消息的Squid缓存更新机制

改进五:海量数据的透明垂直切分

XXXX 性能优化领域立项中

搜索引擎

  • 互联网搜索引擎整体架构

  • 爬虫系统架构



  • 爬虫禁爬协议

  • User-agent: GoogleBot

  • Disallow: /tmp/

  • Disallow: /cgi-bin/

  • Disallow: /users/paranoid/

  • 文档矩阵与倒排索引

  • 文档与倒排索引

  • 带词频的倒排索引

  • 带词频与位置的倒排索引

  • Lucene架构



  • Lucene倒排索引



  • Lucene索引文件准实时更新

  • 索引有更新,需重新全量创建一个索引来替换原来的索引,这种方式在数据量大时效率很低,并且由于创建一次索引的成本很高,性能也很差

  • Lucene中引入了段的概念,将一个索引文件拆分为多个子文件,每个子文件叫做段,每个段都是一个独立的可被搜索的数据集,索引的修改针对段进行操作

  • 新增:新数据需要创建索引,选择新建一个段来存储新增的数据

  • 删除:当需要删除数据时,在索引文件新增一个.del的文件,用来专门存储被删除的数据ID

  • 更新:是删除与新增的组合

  • 为了控制索引里段的数量,必须定期进行段合并操作

  • ElasticSearch架构

(Lucene缺点是不支持分布式,数据量很大时考虑ElasticSearch)

  • 索引分片,实现分布式

  • 索引备份,实现高可用

  • API更简单、更高级

  • ES分片预分配与集群扩容

  • shard = hash(routing)%number_of_primary_shards

  • 网页排名算法 PageRank

PageRank,网页排名,又称网页级别,Google左侧排名或佩奇排名,是一种由搜索引擎根据网页之间相互的超链接计算的技,而作为网页排名的要素之一,以Google公司创辨人拉里·佩奇(Larry Page)之姓來命名。

  • PageRank 让链接来 [投票]

PageRank通过网络浩瀚的超链接開像来确定一个页面的等级。Google把从A页面到B页面的链接解释为A页面给B页面投票,Google根据投票来源(甚至来源的来源,即链接到A页面的页面)和投票目标的等级来决定新的等级。简单的说,一个高等级的页面可以使其他低等级页面的等级提升。

一个页面的「得票数」由所有链向它的页面的重要性来决定,到一个页面的超链接相当于对该页投一票。一个页面的PageRank是由所有链向它的页面([链入页面])的重要性经过递归算法得到的。一个有较多链入的页面会有较高的等级,相反如果一个页面没有任何链入页面,那么它没有等级。

  • PageRank算法

假设一个由4个页面组成的小团体:A,B,C和D。如果所有页面都链向A,那么A的PR(PageRank)值将是B,C及D的Pagerank总和。





用户头像

星河寒水

关注

还未添加个人签名 2018.09.17 加入

还未添加个人简介

评论

发布
暂无评论
Week9学习总结(JVM虚拟机原理、Java代码优化、秒杀系统)