面试收藏-开发篇

面试题

2019-08-20  本文已影响0人  ElevenKing

怎么保证异步操作 在事务提交之后执行

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            System.out.println("send email after transaction commit...");
        }
    });

TransactionSynchronizatonManger.registerSynchorinization()的方法,传入一个TransactionSynchornizationAdapter抽象类的实现,实现他的 afterCommit钩子方法。
里面还有beforeCommit()方法

怎么保证多数据源 事务一致性

事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。

TCC方案让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。 当然TCC方案也有不足之处,集中表现在以下两个方面:

1.对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
2.实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等。

上述原因导致TCC方案大多被研发实力较强、有迫切需求的大公司所采用

\color{red}{微服务倡导服务的轻量化、易部署,而TCC方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大。}

重点来了!!!

消息方案从本质上讲是将分布式事务转换为两个本地事务,然后依靠下游业务的重试机制达到最终一致性。基于消息的最终一致性方案对应用侵入性也很高,应用需要进行大量业务改造,成本较高。

事务的隔离级别、传播机制

ACID
原子性:事务是一个不可分割的执行单元,事务中的所有操作要么全都执行,要么全都不执行。
隔离性:一致性要求,事务在开始前和结束后,数据库的完整性约束没有被破坏。
一致性:事务的执行是相互独立的,它们不会相互干扰,一个事务不会看到另一个正在运行过程中的事务的数据。
持久性:持久性要求,一个事务完成之后,事务的执行结果必须是持久化保存的。即使数据库发生崩溃,在数据库恢复后事务提交的结果仍然不会丢失。

谈到隔离级别,我们不妨先聊一下 事务并发的时候可能会出现的问题

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

image.png

mysql的默认隔离级别是 可重复读

我们来看看在不同的隔离级别下,事务A会有哪些不同的返回结果,也就是图里面V1、V2、V3的返回值分别是什么。

隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。

线程池七大参数含义


ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

*newFixedThreadPool 定长线程池,可控制线程的最大并发数,超出的线程会在队列中等待。
*newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
如果没有现有的线程可用,那么就创建新的线程并添加到池中,线程没有使用60秒的时间被终止并从线程池里移除缓存。
*newScheduledThreadPool (四该球的)创建一个定长任务线程池,支持定时及周期性任务执行。

索引失效的场景

哪些字段应该建立索引

哪些字段不应该建立索引

sql执行计划 参数含义

可以告诉我们,sql如何使用索引,查询的执行顺序,扫描的数据行数


explain

常用的设计模式以及描述

redis memche mongdb 比较

JUC里的核心类

image.png image.png image.png

AtomicInteger 以及相关原子类

Unsafe + CAS
更新的时候,传入预期值,和内存偏移量 根据内存拿到实际值,跟预期值不符就继续循环遍历 知道更新成功为止

举例说明: 就像我们git提交代码一样(提交之前不pull,本地版本跟远程仓库版本一致方可提交),我们总是乐观的认为在我之前没人改过,提交的时候发现 别人已经抢先提交,那我只能重新pull一下保持跟远程康库一致然后继续尝试提交,又倒霉的发现 有人抢先提交了,那我只能再pull一次 保持跟仓库一致然后继续提交。 提高CAS无锁算法的 效率 可以从减少循环次数 下文章,例如,我先等一会,再pull然后提交。

Synchroniz和读写锁原理

springboot的run方法里都干了啥、

image.png

public ConfigurableApplicationContext run(String... args) {
        //开启计时
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        //初始化监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //发布ApplicationStartingEvent
        listeners.starting();
 
        try {
            //装配参数和环境
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //发布ApplicationEnvironmentPreparedEvent
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            //创建ApplicationContext,并装配
            context = this.createApplicationContext();
            this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //发布ApplicationPreparedEvent
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            
            //发布ApplicationStartedEvent
            listeners.started(context);
            //执行Spring中@Bean下的一些操作,如静态方法等
            this.callRunners(context, applicationArguments);
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, exceptionReporters, var9);
            throw new IllegalStateException(var9);
        }
 
        //发布ApplicationReadyEvent
        listeners.running(context);
        return context;

入口 SpringApplication.run(BeautyApplication.class, args);
首先记录整个Spring Application的加载时间!

1.初始化监听器
2.发布ApplicationStartingEvent
3.发布ApplicationEnvironmentPreparedEvent
4.打印banner
5.创建ApplicationContext,并装配
6.发布ApplicationPreparedEvent
7.refreshContext(context); //IOC容器加载过程 这里面的onRefresh() 创建了web容器
8.refreshContext(context);

  1. this.afterRefresh(context, applicationArguments);

springboot核心注解

@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

这玩意是个组合注解,主要包含以下几个

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

那么我们来总结下@SpringbootApplication:就是说,他已经把很多东西准备好,具体是否使用取决于我们的程序或者说配置,那我们到底用不用?那我们继续来看一行代码

public static void main(String[] args)
{
    SpringApplication.run(StartEurekaApplication.class, args);
}

那们来看下在执行run方法到底有没有用到哪些自动配置的东西,比如说内置的Tomcat,那我们来找找内置Tomcat,我们点进run

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }

然后他调用又一个run方法,我们点进来看

public ConfigurableApplicationContext run(String... args) {
   //计时器
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   //监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      //准备上下文
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
         //预刷新context
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
     //刷新context
      refreshContext(context);
     //刷新之后的context
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

那我们关注的就是 refreshContext(context); 刷新context,我们点进来看

private void refreshContext(ConfigurableApplicationContext context) {
   refresh(context);
   if (this.registerShutdownHook) {
      try {
         context.registerShutdownHook();
      }
      catch (AccessControlException ex) {
         // Not allowed in some environments.
      }
   }
}

我们继续点进refresh(context);

protected void refresh(ApplicationContext applicationContext) {
   Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
   ((AbstractApplicationContext) applicationContext).refresh();
}

他会调用 ((AbstractApplicationContext) applicationContext).refresh();方法,我们点进来看

// 完成IoC容器的创建及初始化工作
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      //   1: 刷新前的准备工作。
      prepareRefresh();

       // 告诉子类刷新内部bean 工厂。
      //  2:创建IoC容器(DefaultListableBeanFactory),加载解析XML文件(最终存储到Document对象中)
      // 读取Document对象,并完成BeanDefinition的加载和注册工作
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      //  3: 对IoC容器进行一些预处理(设置一些公共属性)
      prepareBeanFactory(beanFactory);

      try {
         //  4:  允许在上下文子类中对bean工厂进行后处理。
         postProcessBeanFactory(beanFactory);

         //  5: 调用BeanFactoryPostProcessor后置处理器对BeanDefinition处理
         invokeBeanFactoryPostProcessors(beanFactory);

        //  6: 注册BeanPostProcessor后置处理器
         registerBeanPostProcessors(beanFactory);

         //  7: 初始化一些消息源(比如处理国际化的i18n等消息源)
         initMessageSource();

          //  8: 初始化应用事件多播器
         initApplicationEventMulticaster();

        //  9: 初始化一些特殊的bean   例如 tomcat  容器。。。
         onRefresh();

         // 10: 注册一些监听器      用户自己写的监听器
         registerListeners();

         //  11: 实例化剩余的单例bean(非懒加载方式)
      //      注意事项:Bean的IoC、DI和AOP都是发生在此步骤
         finishBeanFactoryInitialization(beanFactory);

         //12: 完成刷新时,需要发布对应的事件
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         //  销毁已经创建的单例避免占用资源
         destroyBeans();

         //  重置'active' 标签。
         cancelRefresh(ex);

         //传播异常给调用者
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

这点代码似曾相识啊 没错,就是一个spring的bean的加载过程我在,解析springIOC加载过程的时候介绍过这里面的方法,如果你看过Spring源码的话 ,应该知道这些方法都是做什么的。现在我们不关心其他的,我们来看一个方法叫做 onRefresh()方法

protected void onRefresh() throws BeansException {
   // For subclasses: do nothing by default.
}
image.png

我们既然要找Tomcat那就肯定跟web有关,我们可以看到有个ServletWebServerApplicationContext

@Override
protected void onRefresh() {
   super.onRefresh();
   try {
      createWebServer();
   }
   catch (Throwable ex) {
      throw new ApplicationContextException("Unable to start web server", ex);
   }
}

我们可以看到有一个createWebServer();方法他是创建web容器的,而Tomcat不就是web容器,那他是怎么创建的呢,我们继续看

private void createWebServer() {
   WebServer webServer = this.webServer;
   ServletContext servletContext = getServletContext();
   if (webServer == null && servletContext == null) {
      ServletWebServerFactory factory = getWebServerFactory();
      this.webServer = factory.getWebServer(getSelfInitializer());
   }
   else if (servletContext != null) {
      try {
         getSelfInitializer().onStartup(servletContext);
      }
      catch (ServletException ex) {
         throw new ApplicationContextException("Cannot initialize servlet context",
               ex);
      }
   }
   initPropertySources();
}

factory.getWebServer(getSelfInitializer());他是通过工厂的方式创建的

public interface ServletWebServerFactory {

   WebServer getWebServer(ServletContextInitializer... initializers);

}

可以看到 它是一个接口,为什么会是接口。因为我们不止是Tomcat一种web容器。


image.png

我们看到还有Jetty,那我们来看TomcatServletWebServerFactory

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
   Tomcat tomcat = new Tomcat();
   File baseDir = (this.baseDirectory != null) ? this.baseDirectory
         : createTempDir("tomcat");
   tomcat.setBaseDir(baseDir.getAbsolutePath());
   Connector connector = new Connector(this.protocol);
   tomcat.getService().addConnector(connector);
   customizeConnector(connector);
   tomcat.setConnector(connector);
   tomcat.getHost().setAutoDeploy(false);
   configureEngine(tomcat.getEngine());
   for (Connector additionalConnector : this.additionalTomcatConnectors) {
      tomcat.getService().addConnector(additionalConnector);
   }
   prepareContext(tomcat.getHost(), initializers);
   return getTomcatWebServer(tomcat);
}

那这块代码,就是我们要寻找的内置Tomcat,在这个过程当中,我们可以看到创建Tomcat的一个流程。因为run方法里面加载的东西很多,所以今天就浅谈到这里。如果不明白的话, 我们在用另一种方式来理解下,

https://github.com/zgw1469039806/gwspringbootsrater

spring核心类

zk场景

*dubbo的服务注册发布(自动),公司自研的rpc服务管理平台(手动)

zk通知机制 选举机制

一次触发,触发一次就失效,想继续监听,需要客户端重新设置 Watcher。因此如果你得到一个 watch 事件且想在 将来的变化得到通知,必须新设置另一个 watch。

JVM运行时数据区

image.png

双亲委派,沙箱安全

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成
> 1.避免重复加载类 2.考虑安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类。。。防止核心API被篡改

类加载

加载 – 连接 – 初始化 – 使用 – 卸载
源代码(.java文件)经过编译后会变成字节码(.class文件)
类加载,指的是将类的.class文件中的二进制数据读入到内存中,把它放进运行时数据区的方法区内(Perm区)。
然后在堆区创建一个java.lang.Class对象,封装这个类在自身的方法区内的数据结构。
注意,这是仍旧没有生成针对该类的对象。后续对类的实例化,会使用堆内存中的Class对象生成具体的实例对象。

确定垃圾

哪些对象可以视为GC ROOT

GC过程

对象何时进入老年代

调优参数

调优工具

image.png

线上问题排查

1.通过top命令查看当前机器的CPU使用情况,看看是哪个进程CPU高

  1. ps-ef或者jps 进一步定位,得知是怎么样一个后台程序惹事了
  2. 定位到具体的线程 ps-mp 进程id -o THREAD,tid,time
  3. 将有问题IDE线程ID转换为16进制格式 printf "%x/n" 有问题的线程ID
  4. jstack 进程ID | grep 16进制线程id 查看线程堆栈,看看带公司代码的那几行

为什么产生死锁,如何避免?

是指多个线程在运行过程中因争夺资源而造成的一种僵局。

  1. 超时放弃
    当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

2.JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。


类加载

是java程序"一次编译,到处运行"的关键,java程序编译时,不是直接编译成目标机器的机器码,而是编译成.class的二级制的字节码文件,再由目标机器上的JVM虚拟机把.class文件翻译为对应机器的机器码执行.

"加载"就是将.class文件读到内存中

1.加载2.验证3.准备4.解析5.初始化 6.使用
验证 准备 解析 又成为连接
1.加载2.连接。3初始化

什么时候需要对类进行初始化?

1.使用new该类实例化对象的时候;
2.读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
3.调用类静态方法的时候
4.使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
5.初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
6.被标明为启动类的类(即包含main()方法的类)要初始化;


java反射获取私有属性,改变值

Field age = clazz.getDeclaredField("age");
age.setAccessible(true);
age.set(wdc,66);
或者获取所有的属性,去遍历


数据库乐观锁使用

增加一个数字类型的 “version”,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

update task set value = newValue, version = versionValue + 1 where version = versionValue;

如何开启慢查询日志

slow_query_log 设置为ON
设置满查询日志的存放位置
set global slow_query_log_file='/var/lib/mysql/test-10-226-slow.log';
设置超过几秒就记录
set global long_query_time=1;

如何设计一个线程安全的HashMap

1.将所有public方法都加上synchronized: 相当于设置了一把全局锁,所有操作都需要先获取锁(比如直接使用Collections.synchronizedMap()方法对其进行加锁操作),java.util.HashTable就是这么做的,性能很低

  1. 由于每个桶在逻辑上是相互独立的,将每个桶都加一把锁,如果两个线程各自访问不同的桶,就不需要争抢同一把锁了。这个方案的并发性比单个全局锁的性能要好,不过锁的个数太多,也有很大的开销。
    3.锁分离(Lock Stripping)技术:第2个方法把锁的压力分散到了多个桶,理论上是可行的的,但是假设有1万个桶,就要新建1万个ReentrantLock实例,开销很大。可以将所有的桶均匀的划分为16个部分,每一部分成为一个段(Segment),每个段上有一把锁,这样锁的数量就降低到了16个,JDK 7里的java.util.concurrent.ConcurrentHashMap就是这个思路。
    4.在JDK8里,ConcurrentHashMap的实现又有了很大变化,它在锁分离的基础上,大量利用了了CAS指令。并且底层存储有一个小优化,当链表长度太长(默认超过8)时,链表就转换为红黑树。链表太长时,增删查改的效率比较低,改为红黑树可以提高性能

快排基本思想

1.先从数列中取出一个数作为基准数
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
3.再对左右区间重复第二步,直到各区间只有一个数

垃圾回收机制


项目中查看垃圾回收


synchronized 修饰静态 方法和普通方法的区别,获取类锁之后还能获取对象锁吗?

synchronized修饰不加static的方法,锁是加在单个对象上,不同的对象没有竞争关系
修饰加了static的方法,锁是加载类上,这个类所有的对象竞争一把锁。


如何将数据分布在不同的redis

redis3.0之后哈西槽
一致性哈希算法


springAOP的实现


浏览器输入网址全过程,结合springmvc


数据库的默认隔离级别,一定会产生幻读吗?怎么解决?


负载均衡算法


SpringBean的默认范围


如何确保多台机器不重复消费


paxos算法


springMVC流程

image.png
image.png

第一步:发起请求到前端控制器(DispatcherServlet)

第二步:前端控制器请求HandlerMapping查找 Handler (可以根据xml配置、注解进行查找)

第三步:处理器映射器HandlerMapping向前端控制器返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),通过这种策略模式,很容易添加新的映射策略

第四步:前端控制器调用处理器适配器去执行Handler

第五步:处理器适配器HandlerAdapter将会根据适配的结果去执行Handler

第六步:Handler执行完成给适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)

第八步:前端控制器请求视图解析器去进行视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可

第九步:视图解析器向前端控制器返回View

第十步:前端控制器进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)

第十一步:前端控制器向用户响应结果


左连接右连接的区别

左连接是返回主表的所有信息,从表只返回满足条件的
右连接 是返回主表满足条件的信息,从表全部返回
内连接 只满足返回条件的


JVM有哪些垃圾回收器,如何查看默认垃圾回收器,怎么配置垃圾回收器

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器那么垃圾收集器就是内存回收的具体实现。
GC回收算法是内存回收的方法论,总要有落地的实现,
4种主要的垃圾收集器:Serial(塞锐呕)串行回收、parallel(拍锐咯)并行回收、CMS并发、G1

image.png

1.8之前主要是这三种垃圾回收器

1.8可以用G1了, 1.8默认是并行垃圾回收,1.9默认开始是G1 java11 是更高的版本 ZGC

怎么查看默认的垃圾回收器? 生产上如何配置垃圾收集器? 谈谈对垃圾收集器的理解?

串行回收 -xx:+userSerialGC
并行回收 -xx:+UserParallelGC
并发回收 CMS(ConcMarkSweep)
G1
查看默认的垃圾回收


image.png

-xx:+printCommandLineFlags


image.png

如何认为修改默认垃圾回收

========================
七种垃圾回收(实际用六种,有一种废弃了,底层源码目前六种)

  1. Serial 收集器

Serial收集器是最基本、历史最悠久的垃圾收集器。它是一个单线程收集器,“单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是 它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。 它会在用户不可见的情况下把用户正常工作的线程全部停掉。想象一下,当你结束一天的工作回到家中,喝着冰阔乐刷着副本正要打Boss,突然你的电脑说他要休息5分钟,你会是什么感觉?
存在即合理,当然Serial 收集器也有优于其他垃圾收集器的地方,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
它的 新生代采用复制算法,老年代采用标记整理算法。

  1. ParNew 收集器

ParNew 收集器是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为(控制参数、收集算法、分配规则、回收策略等等)和 Serial 收集器完全一样。
除了支持多线程收集,ParNew 相对 Serial 似乎并没有太多改进的地方。但是它却是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。ParNew单核状态下不如Serial,多核线程下才有优势。
新生代采用复制算法,老年代采用标记整理算法。

  1. Parallel Scavenge 收集器

Parallel Scavenge 收集器是一个新生代收集器,也是采用复制算法+并行。听起来和ParNew差不多对不对,那么它有什么特别之处呢?
Parallel Scavenge 收集器关注点是吞吐量(CPU运行代码的时间与CPU总消耗时间的比值)。 而CMS 等垃圾收集器的关注点更多的是缩短用户线程的停顿时间(提高用户体验)。停顿时间越短就越适合和用户进行交互(响应速度快,可以优化用户体验),而高吞吐量则可以高效的利用CPU时间,尽快完成用户的计算任务。
Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

  1. Serial Old 收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
新生代采用复制算法,老年代采用标记整理算法。

  1. Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

  1. CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常重视服务的响应速度,以期给用户最好的体验。。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。
CMS一款优秀的垃圾收集器,
主要优点:并发收集、低停顿。
但是它有下面三个明显的缺点:
1.对 CPU 资源敏感;
2.无法处理浮动垃圾;

  1. 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间 碎片产生。
  1. G1 收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,开发人员希望在未来可以换掉CMS收集器,它有如下特点
 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
 空间整合:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。这就意味着不会产生大量的内存碎片
 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

image.png jvm源码 image.png
垃圾回收链接

insert a,b,c value

线上排查问题

生产环境变慢,诊断思路,性能评估
整机: top
cpu: vmstat
内存: free
磁盘: df -h
硬盘IO:
网络IO:
以上都会导致性能变慢


top

右上角,load average 有三个参数,五分钟负载,十分钟负载,十五分钟负载,这三个值相加 除以3 乘 百分百,如果高于百分之 60 就说明负载很高 或者 uptime 只看 load average

jinfo java配置相关
jmap 内存映像工具
jstat 统计信息监视

*常用调优参数
boolean 类型
-XX:[+-]<name> 表示是否启用jvm的某个参数
非boolean类型
XX:<name> = <value> 表示name属性的值为value
Xms:初始堆内存大小(-XX:initialHeapSize)
-Xmx:最大堆内存大小(-XX:MaxHeapSize)
-XX:NewRatio 新生代老年代之间的比例
-XX:MetaSpaceSize MetaSpace大小
打印参数相关的
-XX:+PrintGCDetails 显示gc详细信息
-XX:+PrintHeapAtGC 发生gc时打印出堆栈信息
-XX:+PrintTenuringDistribution 打印出对象的分布情况

gc常用参数

jps:查看java的相关进程
jinfo:产看正在运行的jvm的参数
jstat:查看jvm的统计信息(包括类加载信息,垃圾收集信息,jit编译信息)

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

设定堆内存大小-Xmx:堆内存最大限制。设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代-XX:NewSize:新生代大小-XX:NewRatio 新生代和老生代占比-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

-Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn:新生代的内存空间大小,注意:此处的大小是(eden+ 2 survivor space)。与jmap -heap中显示的New gen是不同的。整个堆大小=新生代大小 + 老生代大小 + 永久代大小。在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-XX:SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8。两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。
-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。一般小的应用, 如果栈不是很深, 应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"-Xss is translated in a VM flag named ThreadStackSize”一般设置这个值就可以了。
-XX:PermSize:设置永久代(perm gen)初始值。默认值为物理内存的1/64。
-XX:MaxPermSize:设置持久代最大值。物理内存的1/4。

什么是堆外内存

JDK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请,底层通过unsafe.allocateMemory(size)实现。
对内 内存 就是我们常说的 堆 新生代+老年代

内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。使用未公开的Unsafe和NIO包下ByteBuffer来创建堆外内存。
优点:
1.减少了垃圾回收,使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。

  1. 提升复制速度(io效率)
    堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。

http 和 hppts关系

http是超文本传输协议,信息是明文传输(内容可能被窃听),https则是具有安全协议的ssl加密传输协议。
https协议需要到ca申请证书,一般免费的证书很少,需要交费。 https更安全
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443.
http + 加密 + 认证 + 完整性保护 = https

上一篇 下一篇

猜你喜欢

热点阅读