0%

一、Spring设计模式

1.1 简单工厂模式

Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。

1.2 工厂模式

实质: 实现了FactoryBean接口的bean是一类叫做factory的bean 。

实现方式: FactoryBean

实现原理:因为实现了FactoryBean接口,所以返回的不是SqlSessionFactoryBean的实例,而是它的 SqlSessionFactoryBean.getObject()的返回值。

1.3 单例模式

实质:保证一个类仅有一个实例,并提供一个访问它的全局访问点 (多线程访问,加锁 syncronized)

Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。

1.4 适配器模式

实现场景:SpringMVC中的适配器HandlerAdatper

实现原理:HandlerAdatper根据Handler规则执行不同的Handler

1.5 装饰器模式

实现方式:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。

1.6 代理模式

实现方式:AOP底层,就是动态代理模式的实现。

Spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。

1.7 观察者模式

Spring中Observer模式常用的地方是listener的实现。如ApplicationListener。

1.8 策略模式

在SimpleInstantiationStrategy中有如下代码说明了策略模式的使用情况:

1.9 模板模式

实质:是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式。Spring几乎所有的外接扩展都采用这种模式。

Template Method模式一般是需要继承的。这里想要探讨另一种对Template Method的理解。Spring中的JdbcTemplate,在用这个类时并不想去继承这个类,因为这个类的方法太多,但是我们还是想用到JdbcTemplate已有的稳定的、公用的数据库连接,那么我们怎么办呢?我们可以把变化的东西抽出来作为一个参数传入JdbcTemplate的方法中。但是变化的东西是一段代码,而且这段代码会用到JdbcTemplate中的变量。怎么办?那我们就用回调对象吧。在这个回调对象中定义一个操纵JdbcTemplate中变量的方法,我们去实现这个方法,就把变化的东西集中到这里了。然后我们再传入这个回调对象到JdbcTemplate,从而完成了调用。这可能是Template Method不需要继承的另一种实现方式吧。

一、Ribbon

1.1 概述

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。Ribbon目前进入维护模式

LB负载均衡(Load Balance):将用户的请求平摊的分配到多个服务上,从而达到系统的高可用(HA)

常见的负载均衡的软件有Nginx、LVS

Ribbon与Nginx的区别:

  • Ribbon是本地负载均衡客户端,在调用服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
  • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的

进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

Ribbon服务调用=负载均衡+RestTemplate调用

1.2 接口实现

  • eureka-client的依赖中已经包含了Ribbon
  • 消费侧的Controller接口如下(使用RestTemplate的getForEntity方法):
1
2
3
4
5
6
7
8
9
10
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}
else{
return new CommonResult(444,"操作失败");
}
}

1.3 Ribbon负载规则

  • RoundRobinRule:轮询
  • RandomRule:随机
  • RetryRule:先按照RoundRobinRule的策略获取服务,若获取失败则在指定时间内进行重试,获取可用的服务
  • WeigbtedBesponseTimeBule:RoundBobinRule扩展,响应速度越快的实例选择权更越大,越容易被选择.
  • BestAvailableRule:会先过婆填臾于多次访问故赜呃处天断路嚣跳灵球态的服务、然反选择一个并发曩悬少鲍服务
  • AvailabilityFileteringRule:先过滤掉故障实例,在选择并发较小的实例
  • ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器

1.4 负载规则替换

  • 原先的消费者端,其主启动类在com.atguigu.springcloud包下,而Ribbon修改负载规则要求不能放在同一个包下,所以需要在当前工程下创建一个新包com.atguigu.myrule,然后在创建自己的规则配置类
1
2
3
4
5
6
7
@Configuration
public class MySelfRule{
@Bean
public IRule myRule(){
return new RandomRule(); // 定义为随机
}
}
  • 配置类创建完成后,还需要在项目的主启动类上添加注解进行配置
1
2
3
4
5
6
7
8
@SpringBootApplilcation
@EnableEurekaClient
@RibbonClient(name="CLOUD-PAYMNET-SERVICE",configuration=MySelfRule.class)
public class OrderMain80{
public static void main(String[] args){
SpringApplication.run(OrderMain80.class, args);
}
}
  • 替换完成以后,服务调用的效果就可以通过接口调用观察出来

1.5 算法原理

轮询算法: rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。每次服务重启后会重新从1开始计算请求次数

1.6 手写算法

二、OpenFeign

2.1 使用

Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需要创建一个接口,并在接口上添加注解即可

首先还是老样子,新建一个maven工程cloud-consumer-feign-order80,然后配置service层和主启动类

  • 引入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 添加接口注解@FeignClient,接口中的方法对应了服务提供者的接口方法
1
2
3
4
5
6
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") // value为微服务名称
public interface PaymentFeignService{
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
}
  • 添加著启动类注解@EnableFeignClients
1
2
3
4
5
6
7
@SpringBootApplilcation
@EnableFeignClients
public class OrderMain80{
public static void main(String[] args){
SpringApplication.run(OrderMain80.class, args);
}
}
  • 最后Controller层调用接口方法
1
2
3
4
5
6
7
8
9
public class OrderFeignController{
@Resource
private PaymentFeignService paymentFeignService; // 调用本项目的接口方法

@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}

2.2 超时控制

消费者在访问服务提供者时,存在一个等待时间,OpenFeign默认等待1秒钟,超过后则报错,以下模拟超时情况,在服务提供者端添加一个请求超时接口

1
2
3
4
5
6
7
8
9
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
try{
TimeUnit.SECONDS.sleep(3); // 等待3秒
}catch(InterruptedException e){
e.printStackTrace();
}
return serverPort;
}

在消费者端调用此接口时则会出现报错,可以在yml中配置Feign的超时时间

1
2
3
4
5
6
# 设置feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
# 指的是建立连接后所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000 # 单位ms
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

2.3 日志功能

OpenFeign提供了日志打印的功能

日志级别:

  • NONE:默认的,不显示任何日志
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间
  • HEADERS:除了BASIC中定义的信息外,还有请求和响应的头信息
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据

在消费者侧的config包下新建FeignConfig类

1
2
3
4
5
6
7
@Configuration
public class FeignConfig{
@Bean
Logger.level feignLoggerLevel(){
return Logger.level.FULL;
}
}

在yml中开启Feign日志功能

1
2
3
4
logging:
level:
# feign日志以配置的日志级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug

一、垃圾回收算法

1.1 标记清楚算法

  • 标记:遍历内存区域,对需要回收的对象打上标记
  • 清除:再次遍历内存,对已经标记过的内存进行回收

  • 缺点:

    • 效率问题;遍历了两次内存空间(第一次标记,第二次清除)
    • 空间问题:容易产生大量内存碎片,当再需要一块比较大的内存时,无法找到一块满足要求的,因而不得不再次出发GC

1.2 标记复制算法

将内存划分为等大的两块,每次只使用其中的一块。当一块用完了,触发GC时,将该块中存活的对象复制到另一块区域,然后一次性清理掉这块没有用的内存。下次触发GC时将那块中存活的的又复制到这块,然后抹掉那块,循环往复。

  • 优点:
    • 相对于标记–清理算法解决了内存的碎片化问题
    • 效率更高(清理内存时,记住首尾地址,一次性抹掉)
  • 缺点:
    • 内存利用率不高

改进:研究表明,新生代中的对象大都是“朝生夕死”的,即生命周期非常短而且对象活得越久则越难被回收。在发生GC时,需要回收的对象特别多,存活的特别少,因此需要搬移到另一块内存的对象非常少,所以不需要1:1划分内存空间。而是将整个新生代按照8 : 1 : 1的比例划分为三块,最大的称为Eden(伊甸园)区,较小的两块分别称为To Survivor和From Survivor。

首次GC时,只需要将Eden存活的对象复制到To。然后将Eden区整体回收。再次GC时,将Eden和To存活的复制到From,循环往复这个过程。这样每次新生代中可用的内存就占整个新生代的90%,大大提高了内存利用率。

但不能保证每次存活的对象就永远少于新生代整体的10%,此时复制过去是存不下的,因此这里会用到另一块内存,称为老年代,进行分配担保,将对象存储到老年代。若还不够,就会抛出OOM。

老年代:存放新生代中经过多次回收仍然存活的对象(默认15次)。

1.3 标记整理(压缩)算法

因为前面的复制算法当对象的存活率比较高时,这样一直复制过来,复制过去,没啥意义,且浪费时间。所以针对老年代提出了“标记整理”算法。

  • 标记:对需要回收的进行标记
  • 整理:让存活的对象,向内存的一端移动,然后直接清理掉没有用的内存。

1.4 分代收集算法

当前大多商用虚拟机都采用这种分代收集算法,这个算法并没有新的内容,只是根据对象的存活的时间的长短,将内存分为了新生代和老年代,这样就可以针对不同的区域,采取对应的算法。如:

  • 新生代,每次都有大量对象死亡,有老年代作为内存担保,采取复制算法。
  • 老年代,对象存活时间长,采用标记整理,或者标记清理算法都可。

二、垃圾收集器

2.1 Serial和SerialOld

Serial是新生代,SerialOld是老年代的回收期,串行化执行,最简单的单线程的收集器,会STW。
CMS收集器如果空间不够无法进行FULL-GC,就会用SerialOld进行回收。
Serial使用标记复制,SerialOld使用标记整理算法

2.2 Parallel Scavenge收集器 Parallel Old

使用多线程,其他的和Serial相同,关心吞吐量,会自动调整参数设置吞吐量的大小,停顿的时间,提供最优的吞吐量。JDK8默认的收集器
新生代采用复制算法,老年代使用标记整理算法。

2.3 parNew收集器

使用多线程,其他的和Serial相同。使用复制算法
新生代的收集器,可以搭配CMS收集器使用,

2.4 CMS收集器

为了提高用户的体验,提供最短的STW,并发收集,可以让垃圾回收线程和用户线程同时使用,标记清除算法,默认情况下是老年代内存达到92%会执行FullGC。
分为以下阶段:

  • 初始标记:会STW,但是速度非常快,标记GCROOT能引用的对象,可以用-XX:+CMSParallellnitialMarkEnabled参数开启多线程执行。
  • 并发标记:根据GCROOT遍历所有对象,过程比较慢,不会STW,会和用户线程一起执行。
  • 重新标记:因为并发标记中垃圾回收线程会和用户线程一同执行,可能会出现,被标记为垃圾对象的现在不是垃圾对象了会对产生变动的重新进行标记。会STW,比初始标记时间长,并发标记时间短。可以用 -XX:+CMSParallelRemarkEnabled 参数开启多线程重新标记。(存活的对象现在是垃圾对象,可达变不可达,是不会被重新标记的,这个是浮动垃圾)
  • 并发清理:和用户线程一同执行,对未标记的对象清理,这个阶段如果有新添加的对象,会被标记为黑色。
  • 并发重置:重置本次标记的对象,与用户线程一同运行。

优点:并发执行,低停顿,用户体验较好

缺点:

  • 会产生浮动垃圾只能等待下一次回收
  • 占用CPU资源
  • 标记清除会产生空间碎片,可以通过开启参数,做完发FullGC自动整理碎片( -XX:+UseCMSCompactAtFullCollection),可以通过参数设置多少次FullGC整理一次内存碎片( -XX:CMSFullGCsBeforeCompaction)

2.5 G1收集器

G1收集器(Garbage-First Garbage Collector) 整堆收集。
G1收集器是一款在server端运行的垃圾收集器,专门针对于拥有多核处理器和大内存的机器,在JDK 7u4版本发行时被正式推出,在JDK9中更被指定为默认GC收集器。它满足高吞吐量的同时满足GC停顿的时间尽可能短。

  • 因为G1是一个并行/并发回收器,它把堆内存分割为很多不相关的区域(Region) (物理上 不连续的)。使用不同的Region来表示Eden、幸存者0(S0)区,幸存者(S1)1区,老年代等。
  • 由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First) 。
  • G1 GC有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1跟踪各个Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
  • G1收集器使用的是 整体上使用标记整理 两个Region 之间 标记复制算法。

2.5.1 G1收集器分区划分

  1. 使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。2048MB 每个独立区间1
  2. 可以通过-XXG1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
  3. 虽然还保留着新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑_上的连续。

一、JVM调优

性能调优包含多个方面:架构调优、代码调优、JVM调优、数据库调优、操作系统调优等。其中架构调优和代码调优是基础,架构的调优对系统影响最大。性能调优基本步骤:

  1. 明确优化目标
  2. 发现性能瓶颈
  3. 性能调优
  4. 监控及数据统计
  5. 确认是否达到目标

1.1 调优时机

  • 应用系统慢(响应性能下降,吞吐量下降)、卡顿(GC停顿时间长、次数频繁)
  • 应用出现OOM等内存异常(使用的堆内存过大、本地缓存过大;会发生OOM的区域:堆、元空间、虚拟机栈、本地方法栈、直接内存)

1.2 调优原则

JVM调优是一种手段,但并不一定所有问题都需要通过JVM调优解决,最有效的优化手段是架构和代码层面的优化。所以JVM优化是最后不得已的手段,在架构调优和代码调优后对服务器配置的最后一次”压榨”。

所以JVM调优应遵守的原则:

  • 上线之前应先将机器的JVM参数设置到最优;
  • 大多数的Java应用不需要进行JVM优化;
  • 大多数导致GC问题的原因是代码层面的问题导致的(代码层面);
  • 减少创建对象的数量、减少使用全局变量和大对象(代码层面);
  • 优先架构调优和代码调优,JVM优化是不得已的手段(代码、架构层面);
  • 分析GC情况优化代码比优化JVM参数更好(代码层面);

1.3 调优目的

  1. 吞吐量:用户代码运行时间 / (用户代码运行时间 + GC时间)
  2. 响应时间:STW越短,响应时间越好

二、JVM命令参数

  • 标准参数:- 开头,所有的Hot Spot都支持;
  • 非标准参数:-X 开头,特定版本的Hot Spot支持的特定命令;
  • 不稳定参数:-XX 开头,下个版本可能取消;

2.1 常用参数

  • 标准参数:- 开头,所有的Hot Spot都支持;
  • 非标准参数:-X 开头,特定版本的Hot Spot支持的特定命令;
  • 不稳定参数:-XX 开头,下个版本可能取消;

输入:Java -XX:+PrintFlagsInitial 查看默认参数及值

输入:Java -XX:+PrintFlagsFinal 查看最终生效参数及值

输入:Java -XX:+PrintCommandLineFlags 查看启动时的命令行参数

2.2 GC常用参数

  • -Xms:设置堆的初识内存大小,包含年轻代和老年代,JVM内存设置默认单位为Byte,也可以用k/K、m/M、g/G来声明其他单位。
  • -Xmx:设置堆的最大内存大小,-Xms和-Xmx一样时,可以避免内存不够时动态调整内存带来的内存波动
  • -Xmn:设置年轻代大小,包括Eden区和
  • -Xss:设置线程最大栈空间,JDK5以后每个线程堆栈大小为1M,直接决定了函数可调用的最大深度。在相同物理内存下,减小这个值能生成更多的线程,但一个进程内的线程是有限的,也不是越多效率越高,经验值在3000~5000左右。
  • -XX:MetaspaceSize:设置方法区(元空间)初始值,可动态扩展,如果没有设置元空间的上限那么他可以扩大到整个内存。64位的JVM元空间默认大小是21M,达到该值就会FGC,同时收集器会对该值进行调整,如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,提升该值,但最到不超过-XX:MaxMetaspaceSize设置的值。
  • -XX:MaxMetaspaceSize:设置方法区(元空间)最大值,如图默认为18446744073709547520接近无限大,也有说默认是-1,即不限制,受限于本地内存。8G内存的机器,初始和最大值一般都设置为256M或者512M。
  • -XX:+UseTLAB:打开本地线程分配缓存区(Thread Local Allocation Buffer),用于线程上新对象的分配,默认是打开的
  • -XX:+PrintTLAB:打印TLAB的使用情况
  • -XX:TLABSize:设置TLAB的大小-XX:TLABSize=256k
  • -XX:+DisableExplictGC:屏蔽代码显示调用GC,如System.gc()
  • -XX:CompileThreshold:JIT热点代码编译的阀值
  • -XX:+PrintGC:打印简单的GC日志信息
  • -XX:+PrintGCDetails:打印详细的GC日志信息,不同垃圾回收器的GC信息格式不一样,同一垃圾回收器不同版本JDK也可能不一样
  • -XX:+PrintHeapAtGC:每次GC前/后堆内存的使用情况,已经FGC次数(full 0)
  • -XX:+PrintGCTimeStamps:打印进程启动到现在GC所运行的时间
  • -XX:+PrintGCApplicationConcurrentTime:打印上次GC后停顿到现在过去了多少时间,当GC后时间置为0
  • -XX:+PrintGCApplicationStoppedTime:打印GC时应用停顿的时间
  • -XX:+PrintReferenceGC:打印强、软、弱、虚引用各个引用的数量以及时长
  • -verbose:class:打印类加载情况
  • -XX:+PrintVMOptions
  • -XX:PreBlockSpin:设置锁自旋次数
  • -XX:+UseSerialGC:新生代使用Serial,老年代使用Serial Old;
  • -XX:+UseParallelGC/-XX:UseParallelOldGC:新生代使用Parallel Scavenge,老年代使用Parallel Old
  • -XX:+UseParNewGC:新生代使用ParNew,老年代自动使用Serial Old;
  • -XX:+UseConMarkSweepGC:新生代使用ParNew,老年代使用CMS + Serial Old;
  • -XX:+UseG1GC:使用G1;

2.3 Parallel常用参数

Parallel GC是JVM中的并行垃圾收集器

  • -XX:SurvivorRatio=n:设置年轻代中Eden区和Survivor的比例,为Eden: S0: S1 = n:1:1
  • -XX:NewRatio=n:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为n,则年轻代与年老代的比值为1 : n;
  • -XX:PreTenureSizeThreshold:设置判定大对象的内存大小,大于该值直接进入老年代,只对Serial和ParNew两款新生代收集器有效
  • -XX:MaxTenuringThreshold:对象进入老年代的年龄,默认为15。设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。设置值大,可以让增加对象在年轻代的停留时间,增加对象在年轻代回收的概率。
  • -XX:ParallelGCThreads=n:配置并行收集器的垃圾收集线程数,即:同时多少个线程一起进行垃圾回收。
  • -XX:+UseAdaptiveSizePolicy:自动选择堆内各区大小的比例

2.4 CMS常用参数

  • -XX:UseConcMarkSweepGC
  • -XX:ParallelCMSThreads:CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction:老年代使用内存回收阈值,超过该阀值后开始CMS收集,默认是68%。
  • -XX:+UseCMSCompactAtFullCollection:在FGC时压缩整理内存
  • -XX:CMSFullGCsBeforeCompaction:多少次FGC之后进行压缩整理
  • -XX:+CMSClassUnloadingEnabled:使用CMS回收Perm区需要卸载的类
  • -XX:CMSInitiatingPermOccupancyFraction:达到什么比例时进行Perm回收
  • -XX:GCTimeRatio:设置GC时间占用程序运行时间的百分比,建议比例,CMS会根据这个值调整堆空间
  • -XX:MaxGCPauseMillis:GC停顿建议时间,GC会尝试各种手段达到这个时间,比如减少年轻代

2.5 G1常用参数

  • -XX:+UseG1GC:使用G1垃圾收集器
  • -XX:ParallelGCThreads:知道垃圾收集线程数
  • -XX:+G1HeapRegionSize:指定分区大小(1M~32M,必须是2的N次幂),建议逐渐增大该值。随着size增加,垃圾存活时间更长,GC间隔更长,每次GC时间也会变长
  • -XX:MaxGCPauseMillis:最大GC停顿时间建议值,G1会尝试调整Young区的块数来达到这个值,
  • -XX:GCPauseIntervalMillis:GC的间隔时间
  • -XX:G1NewSizePercent:新生代初识内存空间比例,默认堆的5%
  • -XX:G1MaxNewSizePercent:新生代最大内存空间比例,默认60%
  • -XX:GCTimeRatio:设置GC时间占用程序运行时间的百分比,建议比例,G1会根据这个值调整堆空间
  • -XX:ConcGCThreads:并发线程数
  • -XX:InitiatingHeapOccupancyPercent:设置触发GC标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%

三、JVM调优工具

3.1 软件工具

  • Jconsole
  • JVisualVM
  • Jprofiler
  • GCViewer
  • GC Easy
  • Java Flight Recorder

3.2 命令行工具

  • top:linux命令,可以观察哪个进程PID和内存过高
  • jps [options] [hostid]
    • jps -p:禁止输出main方法的类或者jar文件的名称和参数
    • jps -m:输出main方法的参数,JVM自带参数不会输出
    • jps -l:输出应用主类的完整包路径或者jar文件的全路径名称
    • jps -v:输出对象进程的JVM的参数
  • jinfo [option] [pid]
    • jinfo -flags pod:输出JVM的全部参数
    • jinfo -sysprops pid:输出系统的全部参数
  • jstate [generalOption | outputOptions pid [interval[s|ms] [count]]]
    • jstat -class:类加载行为的统计信息
    • jstat -compiler:即时编译热点行为的统计
    • jstat -gc:JVM中堆的垃圾收集情况的统计
    • jstat -gccapacity:显示各代的容量以及使用情况
  • jstack [option] pid
    • jstack pid:列出当前进程的各个线程信息
    • jstack pid > /Users/shaotuo/threadDump.txt:生成Thread Dump文件
  • jmap:列表指定java进程的堆内存信息,对象的统计信息、ClassLoader的信息,可以使用jmap生成Heap dump文件

四、相关问题

4.1 CPU占用高

4.2 OOM日志错误排查

  • java.lang.OutOfMemoryError:…java heap space…
    • 堆栈溢出,代码问题可能性很大;
    • 线程池运用不当OOM,不断往list加对象,导致回收不了;
    • 重写finalize引发频繁GC:finalize方法内会拯救对象,导致对象不被回收;重写finalize但不拯救 ,也会将对象推到F-Queue队列,等待下次GC才回收;
  • java.lang.OutOfMemoryError:GC over head limit exceeded
    • 系统处于高频GC状态,且回收效果作用不大;有可能内存不够导致,某引用使用不当,导致不能被回收
  • java.lang.OutOfMemoryError: Direct buffer memory
    • 直接内存不够,JVM不会回收直接内存,查看是否是使用ByteBuffer.allocateDirect方法,没有clear导致;一般为使用NIO的问题
  • java.lang.OutOfMemoryError: unable to create new native thread
    • 堆外内存不够,导致无法为线程分配内存
  • java.lang.OutOfMemoryError: request {} byte for {} out of swap
    • 地址空间不够
  • java.lang.StackOverflowError
    • 线程栈溢出,由-Xss控制线程栈大小,一般是代码里面循环调用的问题

一、基础

1.1 简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (!strs.size()) {
return "";
}
int length = strs[0].size();
int count = strs.size();
for (int i = 0; i < length; ++i) {
char c = strs[0][i];
for (int j = 1; j < count; ++j) {
if (i == strs[j].size() || strs[j][i] != c) {
return strs[0].substr(0, i);
}
}
}
return strs[0];
}
};

1.2 指针

1.2.1 原理

指针地址的原理如下:

1.2.2 指针与数组的关系

指针与一维数组的关系:

指针与二维数组的关系:

1.2.3 注意事项

  • a与&a[0]等价
  • a[0]与*a、&a[0][0]等价
  • pa指向地址array[0][0]
  • (pa+i)地址指向array[i][0]
  • (*(pa+i)+j)地址指向array[i][j]
  • 二维数组的一维访问形式a[i][j]=*(a[0]+n*i+j)=*(*a+n*i+j)

1.3 字符串

在C/C++中的字符串均以char[]类型进行存储,char是一个字符类型 C++的内置类型,C/C++中的string实为一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// char数组初始化、修改与输出
char a[10] = "123";
a[0] = 'a';
*(a+1) = 'b'; // 由于指针和数组等价,所有可以通过指针修改数组内容
cout << *(a+1); // 输出数组的第1个字符2

// 字符串常量
const char *str1 = "abcde"; // 只读常量
str1 = "efghi"; // 目前来开是可以修改的,应该是修改了指针的指向,跟这个"abcde"常量无关
cout << str1;

// 打印输出string字符容器
string a("abc");
printf("%s\n", a.c_str());

1.4 容器

1.4.1 容器分类

序列式容器:

  • 序列式容器:每个元素都有固定位置--取决于插入时机和地点,和元素值无关
  • Vector:将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快速。但是在中部或头部安插元素比较费时
  • Deque:是“double-ended queue”的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素都非常快速。但是在中部或头部安插元素比较费时
  • List:双向链表,不提供随机存取(按顺序走到需存取的元素,O(n)),在任何位置上执行插入或删除动作都非常迅速,内部只需调整一下指针

关联式容器:

  • 关联式容器:元素位置取决于特定的排序准则,和插入顺序无关
  • Set/Multiset:内部的元素依据其值自动排序,Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素,内部由二叉树实现,便于查找;
    • unordered_map
  • Map/Multimap:Map的元素是成对的键值/实值,内部的元素依据其值自动排序,Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素,内部由二叉树实现,便于查找
    • unordered_map

其他结构容器:

  • priority_queue:维护一个堆结构
  • stack:维护一个栈

1.5 函数

二、面向对象

2.1、面向对象的特性

2.1.1 封装

  • 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机的结合,形成“类”,其中的数据和函数都是类的成员

2.1.2 继承

  • 根据现实中的事物之间的关系,抽象出了继承的概念
  • C++语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明

2.1.3 多态

  • 多态性是指程序能够处理多种类型对象的能力

2.2、类

2.2.1 类的定义

1
2
3
4
5
6
7
class Clock{
public:
void setTime(int newH,int newM,int newS);
void showTime();
private:
int hour,minute,second;
};

2.2.2 类的成员函数

1
2
3
4
5
类外实现
返回值类型 类名::函数成员名(参数表)
{
函数体
}

2.2.3 类的访问控制权限

访问限制

访问权限 类内成员函数 类对象 友元函数
public YES YES YES
protected YES NO YES
private YES NO YES

继承的权限变化

基类权限 public继承 protected继承 private继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

2.2.4 构造函数

  • 构造函数的作用是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态
  • 构造函数在对象被创建的时候被自动调用
  • 构造函数的调用顺序:基类构造函数->对象成员构造函数->派生类本身的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Clock{
public:
Clock(){} //无参构造
Clock(int newH,int newM,int newS); //带参数的构造函数
//或者
//Clock(int newH,int newM,int newS):hour(newH),minute(newM),second(newS){}
void setTime(int newH,int newM,int newS);
void showTime();
private:
int hour,minute,second;
};

Clock::Clock(int newH,int newM,int newS){
hour = newH;
minute = newM;
second = newS;
}

2.2.5 拷贝构造函数

  • 拷贝构造函数是一种特殊的构造函数,它在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象,拷贝构造函数通常的步骤:
    • 通过使用另一个同类型的对象来初始化新创建的对象。
    • 复制对象把它作为参数传递给函数
    • 复制对象,并从函数返回这个对象

语法

  • 如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下
  • 在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的
    1
    2
    3
    classname (const classname &obj) {
    // 构造函数的主体
    }

2.2.6 析构函数

  • 析构函数用来完成对象被删除前的一些清理工作
  • 析构函数是在对象的生产期即将结束的时刻被自动调用的
  • 析构函数的调用顺序:派生类本身的析构函数->对象成员析构函数->基类析构函数(与构造顺序正好相反)
1
2
3
4
5
6
7
8
9
class Clock{
public:
Clock();
void setTime(int newH,int newM,int newS);
void showTime();
~Clock(){} // 析构函数
private:
int hour,minute,second;
}

2.2.7 类的继承

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class 基类1{

}

class 基类2{

}

class 派生类:继承方式 基类1,继承方式 基类2{
构造函数(参数列表){
基类1构造函数(参数);
基类2构造函数(参数);
派生类
}
}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
using namespace std;

// 基类 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};

// 基类 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};

// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};

int main(void)
{
Rectangle Rect;
int area;

Rect.setWidth(5);
Rect.setHeight(7);

area = Rect.getArea();

// 输出对象的面积
cout << "Total area: " << Rect.getArea() << endl;

// 输出总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;

return 0;
}

//Total area: 35
//Total paint cost: $2450

诗词百篇

一、唐诗

1.1 送杜少府之任蜀州(王勃)

1
2
3
4
城阙(què)辅三秦,风烟望五津。        平仄仄平平,平平仄仄平
与(yǔ)君离别意,同是宦游人。 仄平平平仄,平仄仄平平
海内存知己,天涯若比邻。 仄仄平平仄,平平仄仄平
无为(wéi)在歧(qí)路,儿女共沾巾。 平平仄仄仄,平仄仄平平
  • 之:到、往
  • 蜀州:今四川崇州
  • 城阙,即城楼,指唐代京师长安城
  • 辅,护卫
  • 城阙辅三秦:此句为倒装句,应是三秦辅城阙
  • 君:对人的尊称,相当于“您”
  • 宦(huàn)游:出外做官
  • 海内:四海之内,即全国各地。古代人认为我国疆土四周环海,所以称天下为四海之内
  • 天涯:天边,这里比喻极远的地方
  • 无为:无须、不必
  • 歧(qí)路:岔路。古人送行常在大路分岔处告别
  • 儿女:指代男女之情
  • 沾巾:泪沾手巾,形容落泪之多

二、宋词

2.1 毛泽东

1
2
3
4
5
6
《忆秦娥·娄山关》
西风烈,长空雁叫霜晨月。
霜晨月,马蹄声碎,喇叭声咽。

雄关漫道真如铁,而今迈步从头越。
从头越,苍山如海,残阳如血。
  • 娄山关:遵义市之北,从四川入贵州的要道。
1
2
3
4
5
6
7
8
9
10
《沁园春 雪》
北国风光,千里冰封,万里雪飘。
望长城内外,惟余莽莽;大河上下,顿失滔滔。(余 通:馀)
山舞银蛇,原驰蜡象,欲与天公试比高。(原驰 原作:原驱)
须晴日,看红装素裹,分外妖娆。(红装 一作:银装)

江山如此多娇,引无数英雄竞折腰。
惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。
一代天骄,成吉思汗,只识弯弓射大雕。
俱往矣,数风流人物,还看今朝。
  • 北国:该词源于中国古代的分裂时期,如宋称辽、金为北国,东晋称十六国等为北国,南北朝时代南方的各朝代称在北方与之对抗的各朝代为北国等。毛泽东诗中的“北国”使人在不觉中产生出一种我国疆土广大的民族自豪感。
  • 雪:这首词作于红一方面军一九三六年二月由陕北准备东渡黄河进入山西省西部的时候。作者在一九四五年十月七日给柳亚子信中说,这首词作于“初到陕北看见大雪时”。
  • 惟:只。
  • 莽莽:无边无际。
  • 俱往矣:都已经过去了。 俱,都。
1
2
3
4
5
6
7
8
9
10
《沁园春 长沙》
独立寒秋,湘江北去,橘子洲头。
看万山红遍,层林尽染;漫江碧透,百舸争流。
鹰击长空,鱼翔浅底,万类霜天竞自由。
怅寥廓,问苍茫大地,谁主沉浮?

携来百侣曾游。忆往昔峥嵘岁月稠。
恰同学少年,风华正茂;书生意气,挥斥方遒。
指点江山,激扬文字,粪土当年万户侯。
曾记否,到中流击水,浪遏飞舟?
  • 湘(xiāng)江:一名湘水,湖南省最大的河流,源出广西壮族自治区灵川县南的海洋山,长1752里,向东北流贯湖南省东部,经过长沙,北入洞庭湖。 所以说是湘江北去。
  • 橘子洲:地名,又名水陆洲,是长沙城西湘江中一个狭长小岛,西面靠近岳麓山。南北长约11里,东西最宽处约一里。毛泽东七律《答友人》中所谓长岛,指此。自唐代以来,就是游览胜地。
  • 漫江:满江。漫:满,遍。
  • 舸(gě):大船。这里泛指船只。
  • 遏(è):阻止

一、概述

货币作为社会文明逐渐发展的产物,为人们的交易带来了遍历,如原始社会的贝类,古代的铜币等等。

而货币之所以会存在,是因为原始时期的物物交换本身就有很大的弊端,比如一头羊换五条鱼,这一交换条件虽然可以在少数个体之间进行交换,但若是扩展到整个社会体系,我们不可能为了获得500条鱼而去带上100头羊,这并不便于双方之间的交易。

所以货币就作为商品交易的一般等价物而存在,但货币本身并不存在价值,它更多的可以理解为一种政府强权下的信用体系。有了货币,人们就可以方便的进行交易,而交易又是经济机器的最基本零件,同时交易的行为产生了不同的市场,市场是由买卖同一种商品的所有买房和买方组成,例如小麦市场、汽车市场、股票市场等等。市场中的商品价格的计算公式如下:

价格 = 支出总额 / 产销总量

经济动力 = 生产率提高 + 短期债务周期 + 长期债务周期

二、交易

交易无时无刻不在发生,交易也是经济发展的本质,它代表了无数交易的总和。当一个人开始消费来获取相应的服务或金融资产时,必将给另外一个人带来收入。现在来假定一个理想模型,我们对个体经济过程进行简单建模:

个体经济 = (收入,支出,生产率)

对于个体来说增加支出的方式是提高收入,而提高收入的方式则是要提高生产率,在这种情况下我们可以预料到支出的增加必然导致另一个人收入的增加,以此循环往复,经济实现了增长。

三、市场

一、教师节送礼

时间:9月6日,周二

地点:印象城、实验室

事情前后:这周是准备给老师买礼物的,毕竟快要到教师节了,今年的教师节和中秋节又是同一天,所以必须得提前准备了。我、小晴和冬梅三人从实验室出发后刚出教学楼就发现包什么的都没有带,于是跑回到实验室拿包。拿完包后从学校到印象城的路上并没有什么问题。但从吃饭到买完东西回来送礼就出现了许多问题。

问题一:时间问题,周二本是全校休息的一天,新生又没有开学,所以老师又很大可能不在学校,这个还不是很重要

问题二:安排问题,由于疫情,现在进出校都需要申请离校码和进校码,这么码都需要填许多不重要的东西,浪费时间,本来我们可以在吃饭的时候就可以申请了,但大家都忘记这回事了,出了商场才记起来。当然这个也不是很重要

问题三:购买礼物,这个花涉及到的问题就比较严重了,我们都知道花容易收到环境的影响,如果放的时间长了就很容易枯萎,所以在不了解老师状况的前提下,买了花,但他又不在实验室,这事就不太好处理了

问题四:送礼对象,这是最致命的问题,对于送礼来说最需要考虑的就是送礼的对象是否有时间去收礼,如果他本人不在或外出,就很容易竹篮打水一场空了,尤其是送的礼物有很短的保质期的情况。

思考:对于这次买礼物,存在的诸多问题,以下做个反思,思考的是如果下次还出现这种事情应该如何更好的去处理。

首先针对第一个和第二个问题这,这两个只是比较基础的问题,但也涉及到许多细节方面的东西。在我们准备了要出校和进校买礼物就应该想到:

  • 出校进校需要考虑哪些东西
    • 比如买东西要不要带包方便点
    • 比如现在疫情方面的,进校码和离校码这些

这些都是比较细节的

一、To B和To C的区别

To B 英文为To Business,指面向企业或特定用户群体,通常为面向企业,为企业提供相关的服务、产品、平台,其建设成果不会向大众公开,为企业内部所用,包括企业的内外部运营管理、数据应用及决策分析等。

To C为To Customer,即面向最终客户,它并不是面对企业那种组织,而是面向个体消费者。To C类的产品是市面上最多的产品,它们都遵循特定的规范流程,开发成果完全面向于大众,如:衣食住行各类APP等。
C端产品的本质都有一个核心的功能,例如:音乐类app有听音乐的功能,游戏类app的核心功能就是游戏。B端产品的本质是为满足用户的工作需求,而工作需求往往不是单一功能满足的,其必须是多种功能的复合和嵌套

To B与To C概念不同、定位不同,所以对应的运营厂商及被运营的主体对象也不相同,To B的运营商一般都是软件厂商,运营的主体是泛化的企业;To C的运营商一般都是IT行业的巨头,运用的主体是普通用户。