Java名企面试题1

1. 多个线程同时读写,读线程的数量远远大于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?

读写分离,对读逻辑加读锁,写逻辑加写锁。

  ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。

  读写锁内部维护了两个锁,一个用于读操作,一个用于写操作。所有 ReadWriteLock实现都必须保证 writeLock操作的内存同步效果也要保持与相关 readLock的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

  ReentrantReadWriteLock支持以下功能:

    1)支持公平和非公平的获取锁的方式;

    2)支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;

    3)还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;

    4)读取锁和写入锁都支持锁获取期间的中断;

    5)Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。 

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

rwl.readLock().lock();    //获取读锁
rwl.readLock().unlock();  //释放读锁
rwl.writeLock().lock();   //获取写锁
rwl.writeLock().unlock(); //释放写锁

 

2. JAVA的AQS是否了解,它是干嘛的?

什么是AQS

AQS是AbustactQueuedSynchronizer(抽象队列同步器)的简称,它是一个Java提供的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。AQS的主要作用是为Java中的并发同步组件提供统一的底层支持,例如ReentrantLock,CountdowLatch就是基于AQS实现的,用法是继承AQS实现其模版方法,然后将子类作为同步组件的内部类。

同步队列

同步队列是AQS很重要的组成部分,它是一个双端队列,遵循FIFO原则,主要作用是用来存放在锁上阻塞的线程,当一个线程尝试获取锁时,如果已经被占用,那么当前线程就会被构造成一个Node节点加入到同步队列的尾部,队列的头节点是成功获取锁的节点,当头节点线程是否锁时,会唤醒后面的节点并释放当前头节点的引用。 
 

补充:

独占锁和共享锁在实现上的区别

独占锁的同步状态值为1,即同一时刻只能有一个线程成功获取同步状态
共享锁的同步状态>1,取值由上层同步组件确定
独占锁队列中头节点运行完成后释放它的直接后继节点
共享锁队列中头节点运行完成后释放它后面的所有节点
共享锁中会出现多个线程(即同步队列中的节点)同时成功获取同步状态的情况

 

重入锁

重入锁指的是当前线成功获取锁后,如果再次访问该临界区,则不会对自己产生互斥行为。Java中对ReentrantLock和synchronized都是可重入锁,synchronized由jvm实现可重入,ReentrantLock的可重入性基于AQS实现。

同时,ReentrantLock还提供公平锁和非公平锁两种模式。

非公平锁

非公平锁是指当锁状态为可用时,不管在当前锁上是否有其他线程在等待,新近线程都有机会抢占锁。只要同步状态为0,任何调用lock的线程都有可能获取到锁,而不是按照锁请求的FIFO原则来进行的。

公平锁

公平锁是指当多个线程尝试获取锁时,成功获取锁的顺序与请求获取锁的顺序相同,下面看一个ReentrantLock的实现
详情参考:https://blog.csdn.net/zhangdong2012/article/details/79983404

 

3. 除了synchronized关键字之外,你是怎么来保障线程安全的?

线程安全在三个方面体现

1)原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);

2)可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);

3)有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
 

3.1 使用原子类数据结构,如AtomicInteger,AtomicLong,AtomicBoolean,他们通过CAS(compareAndSwap)实现原子性。

cas基本原理:更改前获取变量的值,与预期的值进行比较,若值一致,并且时间戳业一致,则可以进行更改。否则不断进行重试。

3.2 还可以使用concurrent包中的同步集合类,比如:ConcurrentHashMap,CopyOnWriteArrayList(一个线程安全、并且在读操作时无锁的ArrayList),CopyOnWriteArraySet(CopyOnWriteArraySet基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法。保证了无重复元素,但在add时每次都要进行数组的遍历,因此性能会略低于上个。)等。

3.3 使用ReentrantLock或ReentrantReadWriteLock加锁同步。

 

4. 你有没有用过Spring的AOP? 是用来干嘛的? 大概会怎么使用?

AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。

使用案例:

在权限控制系统中,superset判断请求传入的userId是erp系统存储的id,用户使用前端页面创建表格申请传入的userId也是erp系统存储的id,这两个地方都需要将erp用户id转换为钉钉用户id,使用aop来实现。

1)创建切面类,其中包含转换id的方法。

2)配置xml文件,设置切面类和切入点(需要使用userId的方法都是切入点)

这样,在使用userId之前,都会先执行切面类的转换方法,将userId转换为钉钉 userId。

 

还可以使用AOP来实现数据库的读写分离,在访问数据库之前,执行判断逻辑设置数据源,是访问主库还是从库,与问题13相参考。

 

扩展:

AOP术语【掌握】

1.target:目标类,需要被代理的类。例如:UserService 
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法 
3.PointCut 切入点:已经被增强的连接点。例如:addUser() 
4.advice 通知/增强,增强代码。例如:after、before 
5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程. 
6.proxy 代理类 
7. Aspect(切面): 是切入点pointcut和通知advice的结合 
一个线是一个特殊的面。 
一个切入点和一个通知,组成成一个特殊的面。 

 

DI 和 IOC 概念

依赖注入或控制反转的定义中,调用者不负责被调用者的实例创建工作,该工作由Spring框架中的容器来负责,它通过开发者的配置来判断实例类型,创建后再注入调用者。由于Spring容器负责被调用者实例,实例创建后又负责将该实例注入调用者,因此称为依赖注入。而被调用者的实例创建工作不再由调用者来创建而是由Spring来创建,控制权由应用代码转移到了外部容器,控制权发生了反转,因此称为控制反转。

 

5. 如果一个接口有2个不同的实现, 那么怎么来Autowire一个指定的实现?
1)在实现类上使用

@Service("xxx")设置名称

2)在@Autowired 下使用@Qualifier("xxx")指定要实例化的对象。

@Autowired

@Qualifier("logDataToMongodb")

 

6. 如果想在某个Bean生成并装配完毕后执行自己的逻辑,可以什么方式实现?

如果我们需要在Spring容器完成Bean的实例化,配置和其他的初始化后添加一些自己的逻辑处理,我们就可以定义一个或者多个BeanPostProcessor接口的实现。

只要将这个BeanPostProcessor接口的实现定义到容器中就可以了,如下所示:

<bean class="com.spring.test.di.BeanPostPrcessorImpl"/>

 

7. SpringBoot没有放到web容器里为什么能跑HTTP服务?

SpringBoot默认自带一个Tomcat web容器,依赖如下:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
</dependencies>

 

8. SpringBoot中如果你想使用自定义的配置文件而不仅仅是application.properties,应该怎么弄?

在类上添加注解:@PropertySource("classpath:xxx.properties"),其中xxx.properties是自定义配置文件,目前自定义配置文件只支持.properties格式,不支持.yml格式。

 

9. SpringMVC如果希望把输出的Object(例如XXResult或者XXResponse)这种包装为JSON输出, 应该怎么处理?

第一种方式是spring2时代的产物,也就是每个json视图controller配置一个JsonView。

如:<bean id="defaultJsonView" class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/> 

或者<bean id="defaultJsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>

第二种使用JSON工具将对象序列化成json,常用工具Jackson,fastjson,gson。

第三种利用spring mvc3的注解@ResponseBody

@ResponseBody  
  @RequestMapping("/list")  
  public List<String> list(ModelMap modelMap) {  
    String hql = "select c from Clothing c ";  
    Page<Clothing> page = new Page<Clothing>();  
    page.setPageSize(6);  
    page  = clothingServiceImpl.queryForPageByHql(page, hql);  
      
    return page.getResult();  
  }

 

10. 如果有很多数据插入MYSQL 你会选择什么方式?

批量插入。

 

11. 如果查询很慢,你会想到的第一个方式是什么?索引是干嘛的?

使用索引。

在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。

索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。

 

12. 查询死掉了,想要找出执行的查询进程用什么命令?找出来之后一般你会干嘛?

ps -ef | grep xxx

杀掉进程,然后分析查询逻辑,优化查询。

 

13. 读写分离是怎么做的?你认为中间件会怎么来操作?这样操作跟事务有什么关系?

读写分离的实现原理就是在执行SQL语句的时候,判断到底是读操作还是写操作,把读的操作转向到读服务器上(从服务器,一般是多台),写的操作转到写的服务器上(主服务器,一般是一台,视数据量来看)。

与事务的关系,需要考虑:

在读写分离时会不会造成事务主从切换错误?

一个线程在Serivcie时Select时选择的是从库,DynamicDataSourceHolder中ThreadLocal对应线程存储的是slave,然后调用Manager时进入事务,事务使用默认的transacatinManager关联的dataSource,而此时会不会获取到的是slave?

经验证不会,但这是因为在AOP设置动态织出的时候,都要清空DynamicDataSourceHolder的ThreadLocal,如此避免了数据库事务传播行为影响的主从切换错误。如果Selelct DB从库完成之后不清空ThreadLocal,那么ThreadLocal跟线程绑定就会传播到Transaction,造成事务操作从库异常。而清空ThreadLocal之后,Spring的事务拦截先于动态数据源的判断,所以事务会切换成主库,即使事务中再有查询从库的操作,也不会造成主库事务异常。

事务隔离级别和传播特性会不会影响数据连接池死锁

一个线程在Service层Select数据会从数据库获取一个Connection,通常来讲,后续DB的操作在同一线线程会复用这个DB Connection,但是从Service进入Manager的事务后,Get Seq获取全局唯一标识,所以Get Seq一般都会开启新的事物从DB Pool里重新获取一个新连接进行操作,但是问题是如果两个事务关联的datasource是同一个,即DB Pool是同一个,那么如果DB Pool已经为空,是否会造成死锁?

经验证会死锁,所以在实践过程中,如果有此实现,建议Get Seq不要使用与事务同一个连接池。或者采用事务隔离级别设置PROPAGATION_REQUIRES_NEW进行处理。最优的实践是宎把Get SeqId放到事务里处理。

资料补充:主从复制

当然为了保证多台数据库数据的一致性,需要主从复制。

主从复制的实现原理是:mysql中有一种日志,叫做bin日志(二进制日志),会记录下所有修改过数据库的sql语句。

主从复制的原理实际是多台服务器都开启bin日志,然后主服务器会把执行过的sql语句记录到bin日志中,之后从服务器读取这个bin日志,把该日志的内容保存到自己中继日志里面,从服务器再把中继日志中记录的sql语句同样的执行一遍。这样从服务器上的数据就和主服务器相同了。
 

14. 分库分表有没有做过?线上的迁移过程是怎么样的?如何确定数据是正确的?

分库分表有有垂直和水平两种方式。垂直分割方式是将一张大表根据列字段来分成多个不同的表,在将这些表分散到不同的库中(主机中)。水平分割方式是按行对表数据进行拆分成多个表,限制每个表中的总数据量。当然,实际操作中,可以同时使用垂直和水平两种分割方式。

线上迁移可以使用迁移工具,如阿里dts(不需要停业务)、datax(开源免费;需要停业务?)

为了确认数据正确性,可以分批校验(比如 书写脚本,每同步一批数据,就校验数据量)。

 

优缺点补充:

垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低。相互影响非常小,业务逻辑非常清晰的系统。在这样的系统中,能够非常easy做到将不同业务模块所使用的表分拆到不同的数据库中。依据不同的表来进行拆分。对应用程序的影响也更小,拆分规则也会比較简单清晰。

水平切分于垂直切分相比。相对来说略微复杂一些。由于要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较依据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。

垂直切分的长处

◆ 数据库的拆分简单明了,拆分规则明白;

◆ 应用程序模块清晰明白,整合easy。

◆ 数据维护方便易行,easy定位。

垂直切分的缺点

◆ 部分表关联无法在数据库级别完毕。须要在程序中完毕。

◆ 对于訪问极其频繁且数据量超大的表仍然存在性能平静,不一定能满足要求。

◆ 事务处理相对更为复杂;

◆ 切分达到一定程度之后,扩展性会遇到限制;

◆ 过读切分可能会带来系统过渡复杂而难以维护。

 

水平切分的优点

◆ 表关联基本能够在数据库端全部完毕;

◆ 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;

◆ 应用程序端总体架构修改相对较少;

◆ 事务处理相对简单;

◆ 仅仅要切分规则能够定义好。基本上较难遇到扩展性限制;

水平切分的缺点

◆ 切分规则相对更为复杂,非常难抽象出一个能够满足整个数据库的切分规则;

◆ 后期数据的维护难度有所添加,人为手工定位数据更困难;

◆ 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。

 

15. 你知道哪些或者你们线上使用什么GC策略? 它有什么优势,适用于什么场景?

由上图可以看出,方法区、Java堆、直接内存 都是垃圾回收负责的内存区域。

垃圾回收算法:

1⃣️ 引用计数法(Reference Counting)

使用简单,但不能识别相互引用的情况。

2⃣️ 标记清除法(Mark-Sweep)

分两个阶段:标记阶段和清除阶段。一种可行的实现是,从根节点开始遍历所有可达的对象,不可达的对象标记为垃圾,可以回收。

缺点:容易造成内存碎片。

3⃣️ 复制算法(Copying)

将内存空间分为两块,每次使用其中的一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存中的所有对象,交换两个内存的角色,完成垃圾回收。

Java新生代串行垃圾回收器中,使用了复制算法的思想(新生代中垃圾对象通常多于存活对象,复制算法效果比较好)。

4⃣️ 标记压缩算法(Mark-Compact)

标记压缩算法效果等同于标记清除算法执行完成后,再进行一次内存碎片整理,因此也可以称之为标记清除压缩算法。

适用于老年代垃圾回收。

5⃣️ 分代算法(Generation Collecting)

将内存区间根据对象特点分成 新生代、老年代、持久代,每代分别应用上述几种算法。

6⃣️ 分区算法(Region)

分区算法将整个堆空间划分成联系的不同小区间,每个小区间都独立回收,可以控制一次回收多少个小区间。一般来说,在相同的条件下,堆空间越大,一次GC所需要的时间就越长。

 

16. JAVA类加载器包括几种?它们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?

类装载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤:

1、装载:查找和导入Class文件 2、链接:其中解析步骤是可以选择的 (a)检查:检查载入的class文件数据的正确性 (b)准备:给类的静态变量分配存储空间 (c)解析:将符号引用转成直接引用 3、初始化:对静态变量,静态代码块执行初始化工作

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。 

引导类加载器(bootstrap class loader):

它用来加载 Java 的核心库(jre/lib/rt.jar等),是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。

加载扩展类和应用程序类加载器(它是加载器的加载器),并指定他们的父类加载器,在java中获取不到。 

扩展类加载器(extensions class loader):

它用来加载 Java 的扩展库(jre/ext/*.jar)。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 

应用程序类加载器(application class loader):

它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

自定义类加载器(custom class loader):

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

 

Java装载类使用“全盘负责委托机制”。“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入;“委托机制”即“双亲委派机制”先委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类,这有两方面的考虑:

1⃣️ 可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。

2⃣️ 安全因素,试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

 

17. 如何自定义一个类加载器?你使用过哪些或者你在什么场景下需要一个自定义的类加载器吗?堆内存设置的参数是什么?

开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器。

-Xms:设置初始分配大小,默认是物理内存的1/64。

-Xmx:最大分配内存,默认是物理内存的1/4。

 

18. HashMap和Hashtable的区别。
1⃣️ HashMap是非synchronized,而Hashtable是synchronized

2⃣️ HashMap可以接受为null的键(key)和值(value),而Hashtable则不行。

3⃣️ HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

4⃣️ HashMap不能保证随着时间的推移Map中的元素次序是不变的。

 

ps:

fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

 

19. 实现一个保证迭代顺序的HashMap。

参考LinkedHashMap。

Java里面有个容器LinkedHashMap, 它能实现按照插入的顺序输出结果。
它的原理是维护一张链表,并且hashmap中维护指向链表的指针,这样可以快速定位链表中的元素进行删除。
它的时间复杂度是O(n)。

ps: 链表的节点按元素的加入顺序组织,其中存放指向Map元素的指针,遍历链表,就可以按序依次取出元素。

 

20. 说一说排序算法,稳定性,复杂度。

参见:https://blog.csdn.net/tianlan996/article/details/7791491

 

21. JVM如何加载一个类的过程,双亲委派模型中有哪些方法?

java运行过程就可以分为  编译  》 类加载  》  执行

类加载主要是由jvm虚拟机负责的,过程非常复杂,类加载分三步  加载   》  连接  》初始化,(这里的加载和本文标题的类加载是不同的,标题的类加载包含了完整的三个步骤)下面详细说说每一步的过程

  • 加载:这个很简单,程序运行之前jvm会把编译完成的.class二进制文件加载到内存,供程序使用,用到的就是类加载器classLoader ,这里也可以看出java程序的运行并不是直接依  靠底层的操作系统,而是基于jvm虚拟机。如果没有类加载器,java文件就只是磁盘中的一个普通文件。
  • 连接:连接是很重要的一步,过程比较复杂,分为三步  验证  》准备  》解析    

  验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件 

  准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 

  解析:把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。

  • 初始化:为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值

双亲委派模型方法:

loadClass(加载类,实现了双亲委派机制)、

findClass(loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数)、defineClass(将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组)

loadClass(加载类,实现了双亲委派机制)、findClass(loadClass在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的findeClass函数)、defineClass(将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组)

 

22. TCP三次握手,四次分手?

三次握手:

  • 客户端首先要SYN=1,表示要创建连接,
  • 服务端接收到后,要告诉客户端:我接受到了!所以加个ACK=1,就变成了ACK=1,SYN=1
  • 理论上这时就创建连接成功了,但是要防止意外,所以客户端要再发一个消息给服务端确认一下,这时只需要ACK=1就行了。

三次握手完成!

如果一个连接请求在网络中跑的慢,超时了,这时客户端会从发请求,但是这个跑的慢的请求最后还是跑到了,然后服务端就接收了两个连接请求,然后全部回应就会创建两个连接,浪费资源!

如果加了第三次客户端确认,客户端在接受到一个服务端连接确认请求后,后面再接收到的连接确认请求就可以抛弃不管了。

 

四次分手:

1.首先客户端请求关闭客户端到服务端方向的连接,这时客户端就要发送一个FIN=1,表示要关闭一个方向的连接

2.服务端接收到后是需要确认一下的,所以返回了一个ACK=1

3.这时只关闭了一个方向,另一个方向也需要关闭,所以服务端也向客户端发了一个FIN=1 ACK=1

4.客户端接收到后发送ACK=1,表示接受成功

四次分手完成!

 

22. JVM垃圾收集器?

① 串行回收器

1)它仅仅使用单线程进行来及回收

2)它是独占式的垃圾回收

可以应用在新生代和老年代,即新生代串行回收器和老年代串行回收器。
新生代串行回收器使用复制算法,老年代串行回收器使用标记压缩算法。

② 并行回收器

使用多个线程同时进行来及回收。

  • 新生代ParNew回收器:只是简单的将串行回收器多线程化。
  • 新生代ParallelGC回收器:非常关注系统吞吐量,使用-XX:MaxGCPauseMillis设置最大停顿时间,-XX:GCTimeRatio设置吞吐量大小。支持自适应的GC调节策略,使用-XX:+UseAdaptiveSizePolicy打开该策略。
  • 上面两种都是用复制算法。
  • 老年代ParallelOldGC回收器:多线程,关注吞吐量,使用标记压缩算法。

③CMS回收器

CMS即Concurrent Mark Sweep并发标记清除,使用的是标记清除算法,关注的是系统停顿时间。用于老年代。

初始标记和重新标记是stop the world,其它动作都是并发执行的。

④ G1回收器

JDK1.7开始出现,目的是取代CMS。它是分代回收器,也是分区回收器。

作为CMS的长期替代方案,G1特点如下:

新生代GC关注Eden区和survivor区,使用分区算法收集。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值