## gof 设计模式 ### 1) singleton 单例模式 ###### 定义 * 1,只能有一个实例 * 2,单例必须自己创建自己的唯一实例 * 3,单例必须给所有其他对象提供这一实例 ###### 优点 * 1,在内存里只有一个实例,减少内存开销,尤其是频繁的创建和销毁实例 * 2,避免对资源的多重占用 ###### 应用实景 * 1,网站的计数器 * 2,日志应用 * 3,数据库连接池 * 4,多线程连接池 ### 2) ovserver 观察者模式 ##### 定义 对象之间存在一对多或者一对一依赖,当一个对象改变状态,依赖它的对象会收到通知并自动更新。 MQ其实就属于一种观察者模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。 ##### 优点 * 1、观察者和被观察者是抽象耦合的。 * 2、建立一套触发机制。 ##### 缺点 * 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 * 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 ##### Spring 观察者模式 > 对象说明 * 1 ApplicationContext 容器对象 * 2 ApplicationEvent 事件对象 ( ContentRefreshedRvent 容器刷新事件 ) * 3 ApplicationListener 事件监听对象 > 应用实景: ### 3) 代理模式 ##### 定义 给某一个对象提供一个代理,通过代理对象可以访问该对象的功能。主要解决通过代理去访问【不能访问的对象】, 比如租房中介,中介可以称为代理 ##### 优点 * 1, 职责清晰 * 2, 高扩展性 * 3, 智能化 ##### 缺点 * 1, 在客户端喝真是主题直接增加了代理对象,造成处理速度变慢 * 2, 实现代理模式,需要额外的工作量 ##### 代理实现: ###### 基于接口的动态代理: * 提供者: JDK的官方 Proxy * 要求: 被代理类最少实现一个接口 ###### 基于子类的动态代理 * 提供者: 第三方的CGLib,如果报asmxxx异常,需要导入asm.jar * 要求:被代理的类不能用final修饰类 ### 4) 工厂模式 ##### 定义 工厂模式(Factory Pattern) 是Java中最常用的设计模式之一。这种类型的设计模式属于创建模式,它提供了一种创建对象的最佳方式。 它负责实现创建所有实力的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。 ##### 优点 * 1,一个调用者像创建一个对象,只要知道其名称就可以了 * 2,屏蔽产品的具体实现,调用者只关心产品的接口 * 3,降低了耦合度 ### 5) 适配器模式 ##### 定义 将一个类的接口转换成客户希望的另一个接口,是的原本由于接口不兼容二不能一起工作的哪些类能一起工作。 ##### 优点 * 1,可以让两个没有关联的类一起运行 * 2,提高了类的服用 * 3,灵活性好 ##### 缺点 过多的试用适配器,会让系统变得非常凌乱,不易整理进行把我。比如,命名看到调用的是A,其实内部被适配成了B的。如果出现太多这种情况,无异于一场灾难。 > spring 案例 spring aop 适配器+代理模式案例 使用 MethodBeforeAdvice 前置通知,还有后置通知,异常通知 使用 ProxyFactoryBean 创建代理 ### 6) 享元模式 ##### 定义 运用共享技术来有效的支持大量的细粒度对象的服用。它通过共享已经存在的对象来大幅度减少需要创建的对象,避免大量类似的开销,从而提高系统资源的利用率。 ##### 享元与单例的区别 享元是范围内共享,单列是全局共享 ##### 应用实景 > 权限 token 验证 ### 7) 装饰者模式 ##### 定义 动态的向一个现有的对象添加新功能,同事又不改变其结构。属于结构型模式。 ##### 优点 > 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式。装饰模式可以动态的扩展一个实现类的功能。 ##### 缺点 > 多层装饰比较复杂 ##### 应用实景 > 结账:结算价格计算,根据不同的价格嵌套运算 ### 8) 策略模式 ###### 定义 对算法的包装,吧算法的使用跟算法本身分开,委派给不同的对象管理。策略模式通常把一系列的算法包装到一系列策略类(策略工厂)里面,作为一个抽象策略的子类。(策略尽量不要与类耦合) ###### 优点 * 1,算法可以自由切换 * 2,避免使用多重条件判断 * 3,扩展性好 ###### 缺点 * 1,策略类会增多 * 2,所有策略类都需要对外暴露 ##### 应用实景 > 多判断时候使用 ## JVM ### 定义 #### 主流的三个虚拟机 * HotSpot(热点)--官方 * jRockit * J9(IBM) #### jvm运行架构 class文件 → 类加载子系统 (Class loader subsystem)
→
运行时数据区(runtime data area)
(方法区)
(堆区)
(栈区)
(PC寄存器>>计数器)
(本地方法栈)
→
执行引擎
(解释器)
(编译器)
(垃圾回收器GC)
本地方法接口
本地方法库 ### 程序计数器 ##### 定义 > 程序计数器是一个记录着当前线程所执行的字节码的行号指示器。
JAVA代码编译后的字节码在未经过JIT(实时编译器)编译前,其执行方式是通过“字节码解释器”进行解释执行。简单的工作原理为解释器读取装载入内存的字节码,按照顺序读取字节码指令。读取一个指令后,将该指令“翻译”成固定的 *** 作,并根据这些 *** 作进行分支、循环、跳转等流程。
从上面的描述中,可能会产生程序计数器是否是多余的疑问。因为沿着指令的顺序执行下去,即使是分支跳转这样的流程,跳转到指定的指令处按顺序继续执行是完全能够保证程序的执行顺序的。假设程序永远只有一个线程,这个疑问没有任何问题,也就是说并不需要程序计数器。但实际上程序是通过多个线程协同合作执行的。
首先我们要搞清楚JVM的多线程实现方式。JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性,也就是说,每个线程工作时都有属于自己的独立计数器。 ##### 特点 * 线程隔离性,每个线程工作时都有属于自己的独立计数器。 * 执行java方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址(参考上一小节的描述)。 * 执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。 ### 栈 ##### 定义 > 与程序计数器以用,栈是线程私有的,他的生命周期与线程相同。
java虚拟机栈描述的是java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会创建一个栈,用于存储局部变量表 *** 作数栈动态链接方法出口等信息。没一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 ##### 栈帧 * 局部变量 * *** 作数栈 * 动态链接 * 方法出口 ### 堆 ##### 定义 > 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。所有的对象实例都在这里分配。java虚拟机规范并没有对堆进行划分,所有讲解的以最多的hotspot虚拟机为例。 ##### java1.7 ##### 年轻代 young > young被划分为三部分,eden区和两个大小严格相同的survivor区,其中survivor区间中,某一时刻只有其中一个是被使用的,另外一个做垃圾手机时复制对象用,在eden区间变满的时候,GC就会将存活的对象移到空闲的survivor区间中,根据jvm策略,经过几次垃圾收集后,任然存货与survivor的对象将被移动到tenured中 ##### 老年代 tenured > 保存生命周期长的是对象,一般是一些老的对象,当一些对象在young复制转移一定次数以后,对象会被转移到tenured中,一般如果系统中使用了application级别缓存,缓存中的对象往往会被转移到这里。 ##### 永久代 perm > 主要保存 class,method,filed对象,这部分的空间一般不会溢出,除非一次性加载很多类,不过在设计到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError: PermGen space的错误,造成这个错误的原因就是可能每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重启应用服务器可以解决 ##### virtual区 > 最大内存和初始内存的差值,就是virtual区 ##### 空间分配 > 在8之后,未指定堆内存大小,初始堆的内存为物理机内存的1/64,最大堆内存为物理机内存的1/4或者1G。
年轻代 1/3 内存 ( eden 8/10, from 1/10, to 1/10 )
老年代 2/3 内存
在java8之后,元空间(java7之前的永久代)直接使用物理机内存
###### 为什么要放弃1.7的永久区? > 移除永久代是为融合 HotSpot JVM 与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。 ### 方法区 #### 定义 > 方法区 与java堆一样,是各个线程共享的内存区域。他用于存储以被虚拟机加载的类,常量,静态变量,及时编译器编译后的代码缓存等数据
java8 之前将HotSpot虚拟机把收集器的粉黛设计扩展至方法区,所以可以将永久代看作是方法区,java8之后废弃永久代,用元空间来代替。 ### 运行参数 #### jvm的参数类型为三类,分别是: ##### 标准参数 -help, -version ##### 非标准 -Xint, -Xcomp ##### 使用率较高 -XX:newSize, -XX:+UseSerialGC ###### boolean类型 > -XX:[+-]表示开启或者禁用,+代表启动 -代表禁用
例如: -XX:+DisableExplicitGC 表示禁用手动调用GC *** 作,调用Systemc.gc() 无效 ###### 非boolean类型 > -XX:= 表示 =
例如:-XX:NewRatio=4 表示新生代和老年代的比例为 1:4 ##### -Xms 与 -Xmx > 分别是设置jvm堆内存的初始和最大大小
-Xmx2048m == -XX:MaxHeapSize == 设置jvm最大堆内存为2048M
-Xms512M == -XX:InitialHeapSize == 设置jvm初始的内存大小为512M ##### 查看正在运行的jvm参数 > 如果要查看jvm就需要借助jinfo命令查看
先使用jps -l 查询运行的进程id,然后使用jinfo -flags <进程id> 查看jvm参数
查看某一参数的值:使用jinfo -flag MaxHeapSize <进程id> ##### jstat > 查看堆内存各部分使用量,以及加载类的数量命令:
jstat [-命令选项] [vmid] [时间间隔] [查询次数] ##### mat > 使用-XX:+HeapDumponOutOfMemoryError 当发生内存溢出时,把内存做一个快照
使用mat工具查看快照 (主要使用这两个功能定位问题) * Histogram 使用频率 * Dominator Tree 占用大小 ### 死锁 ##### 定义 > 有时候我们需要查看jvm中线程执行情况,比如发现服务器的cpu负载突然增高了,出现了死锁,死循环。由于程序是正常运行日志方面也看不出来问题,需要借助 jstack 命令 * 命令 jstack <进程id> ### 什么样的对象需要回收 > 两种算法: 引用计数法,可达性分析算法 ##### 引用计数法(java并未使用) > 假设有对象A,任何对象对A进行引用,那么对象的引用计数器+1,计数器0时,对象回收 ##### 可达性算法 > 没有GC root 关联,被判定为回收对象。 ###### 可被当作GC root的对象包括: * 在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等。 * 在方法区中类静态属性引用的对象,比如java类的引用类型静态变量 * 在方法区中常量引用的对象,比如字符串常量池(String table)里的引用 * java虚拟机内部的引用,比如基本数据类型对应的class对象,一些常驻的异常对象(比如NullPointExcepiton,OutOfMemoryError)等,海颖系统类加载器 * 所有被同步锁(synchronized关键字)持有对象 * 反映java虚拟机内部情况的JMXBean,JVMTI中注册回调,本地代码缓存等。 ### 垃圾清除算法 * 标记清除法 * 标记压缩算法 * 标记复制算法 ### 垃圾回收器 #### 日志参数 -XX:+PrintGC 输出GC日志 -XX:+PrintGCDetails 输出GC的详细日志 -XX:+PrintGCTimeStamps 输出GC的时间戳 -XX:+PrintGCDateStamps 输出GC的时间 以日期格式 -XX:+PrintHeapAtGC 在进行GC的前后打印出堆信息 -Xloggc: ../logs/gc.log 日志文件输出路径 #### 串行垃圾回收器 * -XX:+UseSerialGC 指定年轻代和老年代都使用串行收集器 * -XX:+PrintGCDetails 打印垃圾回收详细信息 ##### GC日志信息解读: * DefNew :表示串行垃圾回收器 * 4416K->512K (4928K): 表示,年轻代GC前,占有4416k内存,GC后占用512k内存,总大小为4928k * 0.0046102 secs: 表示,GC所用的实见,单位为毫秒 * 4416K -> 1973K (15872K): 表示GC前,堆占用4416K内存,GC后,占用1973K,总大小为15872K * Full Gc: 表示内存空间全部GC #### 并行垃圾回收器 * -XX:+UseParNewGC 设置年轻代使用parnew回收器,老年代依旧使用串行收集器 > 查看老年代回收器方法 jps -l -> jmap -heap <进程id> * Serial old (Mark Sweep Compact) 是一种stop-the-world,使用单个线程进行mark-sweep-compact(标志-清扫-压缩)收集。
* Parallel Old(PS Mark Sweep) 是一种使用多个GC线程压缩收集。
* ConcurrentMarkSweep(CMS) 是并行,低暂停的收集器。
cms过程 初始标记(stw) -> 并发标记 -> 预清理 -> 重新标记(stw) -> 并发清除 -> 调整堆大小 #### G1垃圾回收器 ###### G1 开启流程 * 开启G1垃圾收集器 -XX:+UseG1GC -XX:G1HeapRegionSize 区块大小,取值范围 1MB~32MB,逻辑分块大小设置 -XX:ParallelGCThreads=n 设置stw工作线程数值,将n设置为逻辑处理器数量,n的值与逻辑处理器的数量相同,最多为8 -XX:ConcGCThreads=n 设置并行标记的线程数,将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右 --XX:InitiatingHeapOccupancyPercent=n 出发MixedGC,触发时,回收整个年轻代,以及部分老年代,默认45% = 当老年代大小占整个堆百分比达到45%。 * 设置最大内存 * 设置最大停顿时间 -XX:MaxGCPauseMillis 最大GC停顿时间指标(尽力实现,但部保证达到),默认200 #### ZGC垃圾回收器 ZGC是在JDK11中新加入的具有实验性质的低延迟垃圾收集器,由Oracle公司研发。 ZGC的目标是在尽可能对吞吐量影响不大的前提下,实现任意堆内存大小下都可以把垃圾收集的停顿时间限制在10毫秒以内。 采用Region的堆内存布局,但是ZGC叫做Page。具有动态性,动态创建和销毁,以及动态的区域容量大小。 ZGC在“弱项”吞吐量方面,也是超越G1,Parallel的。 使用ZGC,在linux下jdk11,在window下使用jdk14。 ##### Page * 小型页面 2MB,用于存放小于256k的小对象 * 中兴页面 32MB 用于防止大于256k小于4MB的对象 * 大型页面 容量不固定,可以动态变化,但必须是2MB的整数倍,用于存放4MB以上的对象。 ##### 启动参数 -XX:+UnlockExperimentalVMOptions 解锁实验参数,在jdk11中,ZGC还属于实验 -XX:+UseZGC 启用ZGC垃圾收集器 -Xlog:gc*=info 设置答应GC日志信息,与JDK8日志配置不同 -XX:ConcGCThreads 设置并行线程数,一般默认 #### 染色指针技术 染色指针是一种直接将少量额外的信息存储在指针上的技术,可是为什么指针本身也可以存储额外信息呢?在64位系统中,理论可以访问的内存高达16EB(2的64次幂)字节。 实际上,基于需求(用不到那么多内存)、性能(地址越宽在做地址转换时需要的页表级数越多)和成本(消耗更多晶 体管)的考虑,在AMD64架构中只支持到52位(4PB) 的地址总线和48位(256TB)的虚拟地址空 间,所以目前64位的硬件实际能够支持的最大内存只有256TB。此外, *** 作系统一侧也还会施加自己的约束,64位的Linux则分 别支持47位(128TB)的进程虚拟地址空间和46位(64TB)的物理地址空 间,64位的Windows系统甚至只支持44位(16TB)的物理地址空间。 ##### 优点 * 在移动Page(GC)时,不需要把所引用对象的引用指针都修改后在移动对象,只需要修改引用指针即可,引用指针移动记录后,对象之后引用通过内存屏障引用新地址 ##### 工作节点 * 并发标记 (STW) * 并发预备分配 * 并发重分配 * 并发重映射 ##### 内存屏障(Memory Barrier) 的目的是为了指令不因编译优化、CPU执行优化等原因而导致乱序执行,它也是可以细分为仅确保读 *** 作顺序正确性 和仅确保写 *** 作顺序正确性的内存屏障的。 #### GC可视化工具 https://gceasy.io ### JVM调优实战 #### tomcat调优 * 禁用AJP服务,一般是使用nginx+tomcat的架构,所以用不着AJP协议,所以把AJP链接禁用* 设置线程池,并且调整最大并发线程数 参数说明: maxThreads= 最大并发数,默认200,一般建议在500-1000,根据硬件判断 minSpareThreads= 初始化时创建的线程数,最小空闲线程数,默认25 parestartminSpareThreads= 在初始化的时候初始化 minSpareThreads 参数值,如果不等于true,minSpareThreads值没有效果 maxQueueSize= 最大等待列数,超过则拒绝请求 将线程池配置在执行器里面: * 设置tomcat执行模式为nio2 tomcat运行模式有三种:bio,nio,apr。其中nio2是nio的升级版,在tomcat8中才支持的,建议采用nio2 bio性能最差,阻塞io nio性能较好,非阻塞io apr配置很繁琐 ### 国内开发的调优 https://opts.console.perfma.com/ ## 类加载 加载(Loading) 》 验证(Verification) 》 准备(Preparation) 》 解析(Resolution) 》 初始化(Initialization)》 使用(Using) 》 卸载(Unloading) ### 加载过程 #### 加载 1,通过一个类的全限定名来获取定义此类的二进制字节流。 2,将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。 3,在内存中生成一个代表这个类的java.lang.Class对象,作为方法区的这个类的各种数据的访问入口。 #### 验证 确保Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后 不会危害虚拟机自身安全。 1,文件格式验: 证验证字节流是否符合Class文件格式的规范,并且能够被当前的版本虚拟机处理,验证点: * 是否以魔数0xCAfebabe开头 * 主,次版本号是否在当前虚拟机处理范围内 * 常量池的常量中是否有不被支持的常量类型 * Class文件中各个部分及文件本身是否有被删除的或者附加的其他信息等 2,元素据验证: 对字节码描述的信息进行语义分析,保证其描述的信息符合java语言规范的要求: * 这个类是否有父类 * 这个类的父类是否继承了不允许被继承的类 * 这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法 3,字节码验证: 通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的 4,符号引用验证: * 校验发生在虚拟机将符号引用转化为直接引用的时候,这个转换动作将在链接的第三阶段--解析阶段中发生。 * 符号引用验证可以看作是对类自身以外(常量池中各种符号引用)的各类信息进行匹配性校验,该类是否缺 少或者被禁止访问它依赖的某些外部类,方法,字段等资源。 #### 准备 正式为类变量分配内存并且设置初始值的动作 #### 解析 虚拟机将常量池内的符号引用替换为直接引用的过程 #### 初始化 类的初始化阶段是类加载过程中的最后一步,前面的类加载过程中,处理在加载阶段用户应用程序可以通过自定义 类加载器参与之外,其余动作完全是由虚拟机主导和控制的。 到了初始化阶段,才真正开始执行类中定义的Java程序代码 ### 类加载器 #### 双亲委派模型 如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader) ,所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。 #### 类加载器 只存在两种不同的类加载器: 1,BootstrapClassLoader,这个类加载器是由C++实现,是虚拟机自身的一部分 2,其他类加载器,都是由Java实现,独立于虚拟机外部,全部继承自抽象类java.lang.ClassLoader ##### 启动类加载器(Bootstrap ClassLoader): 负责将存放在 lib 目录中的类库加载到虚拟机内存中。启动类加载器无法被java程序直接 引用,用户在编写自定义类加载器时候,如果需要把加载请求外派给启动类,可以直接使用null代替。 ##### 扩展类加载器(Extension ClassLoader): 这个类加载器由sun.misc.Launcher$ExtClassLoader实现,他负责加载 libeext目录中, 或者被java.ext.dires系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。 ##### 应用程序加载器(Application ClassLoader): 这个类加载器由sun.misc.Launcher$App-ClassLoader实现。getSystemClassLoader()方法返回的就是 这个类加载器,被成为系统类加载器。他负责加载用户类路径(CLassPath)上指定的类库。开发者可以直接使用 这个加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下就是这个程序中默认的类加载器。 #### 后端编译器优化 * 方法内连 * 逃逸分析 * 公共子表达式消除 * 数组边界检查消除
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)