不定期面试总结

spring有哪些主要模块

spring core:框架的最基层部分,提供Ioc和DI特性

spring context:构架于core封装包基础上的context封装包,提供了一种框架式的对象访问方法

spring dao:Data Access Object提供了JDBC的抽象层

spring aop:提供了面向切面的编程,可以自定义拦截器、切点等

spring mvc:提供了Web应用的model-view-controller的实现

sql执行顺序

select --distinct-- from-- join-- on-- where-- group by --having--order by --limit

去重:distinct-->group by-->having

常用注解


@Configuration 标识当前类是配置类
@ComponentScan 包扫描注解 扫描注解
@Bean 标识该方法的返回值交给Spring容器管理
@Scope 控制多例和单例
@Lazy 懒加载
@PostConstruct 初始化方法
@PreDestroy 销毁方法
@Component 将当前类未来的对象交给容器管理
@Autowired 按照类型进行注入
@Qualifier 按照名称进行注入
@Repository 标识持久层注解
@Service 标识Service层
@Controller 标识Controller层
@Value 为属性赋值 @Value("${key}")
@PropertySource 加载指定路径的配置文件properties
@Aspect 标识当前类是一个切面类
@Pointcut 用于定义切入点表达式 表达式写法4种
@EnableAspectJAutoProxy 让AOP的注解有效果
@Before AOP-前置通知
@AfterReturning AOP-后置通知
@AfterThrowing AOP-异常通知
@After AOP-最终通知
@Around AOP-环绕通知

Java基础

面向对象

  • 什么是面向对象:封装、继承、多态

  • 封装

    • 对象(实例)

    • 引用

    • 构造方法

    • this

      • this.xxxx
      • this(...)
    • 方法的重载

      • 同名不同参
    • 隐藏

      • 访问控制符 private
  • 继承

    • 作用: 代码重复、 代码复用

    • 创建子类实例

      1. 新建父类实例
      2. 再新建子类实例
      3. 两个实例绑定整体作为一个实例
    • 构造方法的执行

      1. 先执行父类构造方法
      2. 再执行子类构造方法
    • 调用成员时

      1. 先找子类实例
      2. 再找父类实例
    • 重写 override

      • 从父类继承的方法,在子类中重新定义,重新编写
    • super

      • super.xxxxx() - 重写方法时,调用父类中同一个方法
      • super() - 调用父类构造方法,默认调用无参构造
  • 多态

    • 作用: 一致的类型,所有子类型都可以被当做一致的父类型来处理

    • 类型转换

      • 向上转型
      • 向下转型
    • instanceof

      • 运行期类型识别, 对真实类型及其父类型判断,都返回true
      • o1 instanceof A
      • o1 instanceof B
  • 抽象类

    • 完成了一半的类,半成品类

    • 抽象方法的作用:

      • 作为通用方法在父类中定义
      • 要求子类必须实现
  • final

    • 常量 - 不可变
    • 方法 - 不能重写
    • 类 - 不能被继承
  • static

    • 静态属于类,而不属于实例

    • 静态初始化块

      class A {

      static {

      }

      }

  • 访问控制符

    • public, protected, [default], private
  • 接口

    • 结构设计工具,用来解耦合
  • 内部类

    • 静态内部类
    • 非静态内部类
    • 局部内部类
    • 匿名内部类
  • 泛型

  • 反射

数据结构

  • ArrayList

    • 内部用数组存储数据
    • 默认初始长度 10
    • 容量增长 1.5 倍
  • LinkedList

    • 双向链表
    • 两端效率高
    • 栈LIFO:push()、 pop()
    • 队列FIFO: offer()、 peek()、pull()
  • HashMap

    • 用 Entry[] 数组存放数据

    • 数组默认初始长度 16

    • 放入数据:

      • 用 key.hashCode() 获取键的哈希值,用哈希值计算下标 i

      • 新建 Entry 对象封装键值对

      • 放入 i 位置

        • 空位置,直接放入

        • 有数据,依次用 equals() 方法比较键是否相等

          • 找到相等的键,覆盖值
          • 没有相等的键,链表连接在一起
      • 加载因子、负载率 0.75

        • 新建翻倍容量的新数组
        • 所有数据重新执行哈希运算,放入新数组
      • jdk 1.8

        • 链表长度到 8,转成红黑树
        • 树上的数据减少到 6,转回链表
  • LinkedHashMap

    • 有序的HashMap
    • 哈希表结合双向链表
  • ConcurrentHashMap

    • 并发的HashMap
    • 多线程环境下,避免数据访问冲突
    • 分段锁

线程

  • 创建线程

    • 继承 Thread
    • 实现 Runnable
  • 常用方法

    • Thread.sleep()
    • Thread.yield() 让步
    • interrupt() 打断其他线程的暂停状态
    • join() 等待指定线程结束后,再继续当前线程
  • 线程同步 synchronized

    • synchronized(对象) { } 抢指定的对象的锁
    • synchronized void f() { } 抢当前实例(this)的锁
    • static synchronized void f() { } 抢“类对象”的锁
  • 生产者消费者模型

    • 线程间传递数据的模型
    • 中间用一个集合存放数据,传递数据
  • 等待和通知

    • 没有数据时,消费者暂停等待数据: list.wait()
    • 生产者产生数据后,通知消费者醒过来继续处理数据: list.notifyAll()
  • ThreadLocal

    • 线程的数据绑定
    • set(数据) - 把数据绑定到当前线程
    • get() - 访问数据
    • remove() - 删除数据
  • 线程池

    • ExecutorService - 线程池

      • execute(Runnable) - 任务对象丢进线程池

      • submit(Runnable)

      • submit(Callable)

        • 可以得到一个 Futute 对象,以异步的方式等待线程结束,或获取线程的执行结果
    • Exccutors - 创建线程池的辅助工具

      • newFixedThreadPool(5) - 固定的线程数量
      • newCachedThreadPool() - 足够多的线程,让任务不必等待
      • newSingleThreadExecutor()
    • Callable 和 Future

      • Callable 和 Runnable 相比,有返回值、可以抛任意异常
      • Future 是取餐条,Callable任务丢进线程池,得到Future对象后可以继续执行其他运算,需要结果时,用Future(取餐条),来获取结果
    • volatile

      • 可见性

        • 多线程场景下,频繁修改的数据因改价 volatile
      • 禁用指令重排

        • cpu为了提高运算效率,可能对多条执行重新排序再执行
        • 如果指令重拍可能引起问题,可以用 volatile 禁用重排        

spring cloud-Nefeix 的核心组件有哪些?

Eureka:服务注册与发现。
Feign:基于动态代理机制,根据注解和选择的机器,拼接请求 url 地址,发起请求。
Ribbon:实现负载均衡,从一个服务的多台机器中选择一台。
Hystrix:提供线程池,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。
Zuul:网关管理,由 Zuul 网关转发请求给对应的服务。

Redis在项目中怎么使用

1.存验证码,利用 redis 的时效过期机制
2.做缓存,redis 数据结构结构简单,提高查询效率
3.读写分离,redis 有很好的复制功能
4.保证数据一致性,使用 redis 中 setnx 做分布式锁,也可以使用 redisson 中的 trylock 方法
5.数据量大的时候做集群,哨兵,rediscluster ,sentinel

简述 java 类加载机制?

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型。

GC 的三种收集方法:

标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?

先标记,标记完毕之后再清除,效率不高,会产生碎片

复制算法:分为 8:1 的 Eden 区和 survivor 区,就是上面谈到的 YGC

标记整理:标记完毕之后,让所有存活的对象向一端移动

Redis 主要消耗什么物理资源?

内存。

查看 Redis 使用情况及状态信息用什么命令?

info

Redis 集群最大节点个数是多少?

答:16384 个。

Redis 的内存用完了会发生什么?

如果达到设置的上限,Redis 的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将 Redis 当缓存来使用配置淘汰机制,当 Redis 达到内存上限时会冲刷掉旧的内容。

Spring 框架中都用到了哪些设计模式?

(1)工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;

(2)单例模式:Bean 默认为单例模式。

(3)代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术;

(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中 listener 的实现–ApplicationListener。

面向对象和面向过程的区别:

面向过程:性能高,一般单片机、嵌入式都属于面向过程开发

面向对象:类加载时需要实例化,消耗资源,性能较低,但是易维护,易复用,易扩展; 

java有自动内存管理机制,不需要程序员手动释放无用内存 

字符常量是单引号引起的单个字符,是整型字值,参加表达式运算,占两个字节;

字符串常量是双引号引起的多个字符,代表的是该字符串在内存中存放的地址值

构造函数不能重写

如果父类的方法是被private,static,final修饰的则子类不能重写

封装:

将类的某些信息隐藏在内部,不允许外部访问,而是通过该类提供的方法来实现对隐藏信息是操作和访问;

内部类:定义在类里面的类,提供了更好的封装,并且内部类的方法可以直接访问外部类的所有数据,包括私有资源;

主要分为:成员内部类

                  静态内部类

                  方法内部类

                  匿名内部类

多态:指对象的多种形态,继承是多态的基础

        主要表现为:引用多态:父类引用子类对象

                              方法多态:创建子类对象时,调用的方法是子类重写的方法或继承的方法,引用多个不同子对象会调用不同的方法;

自动向上转型,强制向下转型

xxx instanceof xxx--->左边的对象是否是右边的类的实例

抽象类不能直接创建对象,需要定义父类引用变量来指向子类对象

接口:由全局常量、公共的抽象方法组成,定义一套规范 ;接口可以是多继承的

注意:继承父类必须在实现接口之前

String、StringBuffer和StringBuilder的区别是什么? 

String类是使用final关键字修饰的用来保存的字符串,对象是不能改变的;线程安全,

StringBuffer和StringBuilder都可以对自身操作,不会创建出新的对象,但线程不安全;

StringBuffer对方法加了同步锁,线程安全;

在一个静态方法内调用一个非静态方法成员为什么是非法的?

非静态方法可以调用静态方法,然鹅静态方法不能调用非静态方法和非静态变量,

因为静态方法可以不通过对象调用,静态代码块是先于创建对象加载的,可以直接通过类名来调用

 在Java中定义一个不做事且没有参数的构造方法的作用

因为java程序在执行子类的构造方法时会先调用父类的构造方法,如果没有,就调用父类的无参构造,为了编译不报错,必须定义;主要是帮助子类做初始化工作

接口中不能含有静态代码块,而抽象类可以有静态代码块;接口是对行为的抽象 

成员变量与局部变量的区别 

成员变量:属于类,存在于堆中,可以被public,private,static修饰的,随着对象的消失而消失

局部变量:属于方法,基本类型值是存于栈中 ,随着方法消失而消失

 对象实体与对象引用的区别

对象实体是存在堆内存中,对象引用是存在栈内存。一个对象可以被任意个对象引用所引用。

为什么重写equals时必须重写hashCode方法? 

hashCode()的作用是获取哈希码,可以确定对象的位置,能够快速查找

如果两个对象相等,则hashcode一定也是相同的。两个对象相等,对两个对象分别调用equals方法都返回true。但是,两个对象有相同的hashcode值,他们也不一定是相等的。因此equals方法被覆盖过,则hashCode方法也必须被覆盖。这样才能保证相等。

异常处理

在Java中,所有的异常都有一个共同的祖先是Throwable类。其中有两个子类Exception(异常)和Error(错误)。Exception能被程序本身处理(try..catch),Error是无法处理的(只能尽量避免)。

异常处理总结:

Try块:用于捕获异常。

Catch块:用于处理try捕获到的异常

Finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。

 序列化:

 是将java对象转换成二进制流写入磁盘,其中字节流包含对象的数据和信息,一个序列化对象可以写入磁盘、数据库、网络传输等,实体类实现Serializable接口,目的就是为了让其可序列化。

获取用键盘输入常用的方法 

        Scanner:     new Scanner(system.in).nextLine();

        BufferedReader:        new  BufferedReader(new InputStreamReader(sysytem.in))

BufferedInputStream(缓冲字节流),对数据进行处理为程序提供功能强大、灵活的读写功能,减少程序与磁盘的交互;应用了Java的装饰者设计模式。 

 说一下反射机制

反射机制就是采用动态加载来加载我们需要的驱动,3种方法获取class,通过newInstance()方法来创建Class对象对应类的实例 

数组在内存中是一块连续的区域,查找快; 链表在内存中的任何地方,不需要按顺序,增删快

static关键字

在静态方法中,我们可以直接类名.方法名()即可调用;

静态变量是被所有的对象共享,在内存中只有一个副本(存在方法区),只能在类初次加载的时候才会被初始化;

 只需要执行一次的初始化操作都放在static代码块中进行

Stream关键字 

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作。 

Cookie的作用是什么?和Session有什么区别? 

TTP是一种不保存状态,即无状态协议。 

Session的主要作用就是通过服务端记录用户的状态: 

Cookie一般通过客户端用来保存用户信息

URI和URL的区别是:

URI(Uniform Resource Identified)是统一资源标志符,可以唯一表示一个资源;

URL(Uniform Resource Location)是统一资源定位符,可以提供该资源的路径。它是一种URI; 

HTTP和HTTPS的区别 

1.端口:

HTTP的默认端口是80,HTTPS的默认端口是443。

 2.安全性

HTTPS更加安全,传输内容是加密的,包括对称加密和非对称加密,通过私钥和公钥

ArrayList的扩容机制

1.构造方法:初始化一个空数组

2.add()方法:1.扩容逻辑 2.赋值 3.返回true

扩容逻辑:①第一次直接初始化长度为10的数组

                 ②后续按照1.5倍扩容(满足扩容条件)传入的长度(Mincapacity)大于了现有数组长度(elementData.length)。

HashMap可以存储null的key和value 

HashSet底层是基于HashMap实现的,直接调用HashMap中的方法。 

HashCode与equals的相关规定:

1.如果两个对象相等,则hashcode一定也是相同的

2.两个对象相等,对两个euqals方法返回true

3.两个对象有相同的hashcode值,他们也不一定是相等的

4.综上,equals()方法被覆盖过,则hashCode()方法也必须被覆盖

5.hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即时这两个对象指向相同的数据) 

 如果equals()方法被重写(例如String),则比较的是地址里的内容。

索引分类 

 主键索引

普通索引

哈希索引

全文索引

事务

指逻辑上的一组操作,要么都执行,要么都不执行

原子性:事务执行的最小单位

一致性:事务执行前后数据保持一致

隔离性:各并发事务之间数据库是独立的

持久性 :事务执行之后数据的改变是持久的

并发事务带来那些问题 

 脏读、丢失修改、不可重复读、幻读

事务隔离级别有那些?默认的是?

读未提交:最低隔离级别,会发生并发事务的问题

读已提交:可阻止脏读

可重复读:对同一数据多次读取结果都是一样的

可串行化:最高隔离级别,可阻止脏读、不可重复读、幻读

池化设计思想 

预设资源,避免每次获取资源的消耗 

 数据库的连接本质就是一个Socket的连接。在连接池中,创建连接后,把连接放进池子里,下次再连接的时候直接从池子里面取,减少用户和数据库之间连接的时间

生成全局Id的方法

UUID:32个16位数,太长

数据库自增id

利用redis生成id: 引入了redis的新组件会造成系统更加复杂,可用性降低,增加系统成本。

Twitter的snowflake雪花算法:64位的整数(1位标识+41位时间戳+10位节点+12位序列号) 

美团的Leaf分布式ID生成系统:能保证全局唯一性、趋势递增、单调递增、信息安全,但也需要关系型数据库、Zookeeper等中间件。 

一条SQL语句在Mysql中如何执行的 

主要分为Server层与储存引擎:

Server层:

  直接上逻辑~! 客服端要连接Mysql, 第一步连接器来连接;第二分析器分析客户端给的sql是否正确;第三,优化器选最优查询方面!比如查一个id=1 name=“万小猿”  到底是先筛选那个字段呢?这就是优化器做的。第四,执行器调用存储引擎去执行sql。

储存引擎层:

  主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。

  权限校验-》查询缓存-》分析器-》优化器-》权限校验-》执行器-》引擎

数据库:redis 

高性能:增加读取效率

高并发:直接操作缓存能承受的请求远远大于操作数据库的数量。所以我们可以将数据库的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

 是分布式内存缓存,各实例共用一份缓存数据,缓存具有一致性

redis 的线程模型

 使用单线程的文件事件处理器,它采用 IO 多路复用机制(多个 IO 流用一个进程处理同时监听多个 socket,根据 socket 上的事件来选择对应的事情处理器进行处理。

 redis 常见数据结构以及使用场景分析

String

常见命令:set get keys *(获取所有的值) exists(判断是否存在) append(拼接字符串值) strlen(获取值的长度)

Hash:

常见命令:hget,hset,hgetall 等。

是一个映射表,key=表名,value= 各属性字段值,特别适用于存储对象

List

  常用命令:lpush,rpush,lpop,rpop,lrange 等

可以通过 lrange 命令,就是决定从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 

Set 

 常用集合:sadd,spop,smembers,sunion 等,可以基于轻易实现交集、并集、差集的操作。

Sorted Set: 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列。 

redis 设置过期时间 

expire time

setex key 30 hello:设置key的值shihello,30秒后过期

定期删除:随机抽取,对过期key删除

惰性删除: 

redis 内存淘汰机制 

Redis 提供 6 种数据淘汰策略:
1.volatile(不稳定的)-lru:

从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

2.volatile-ttl:

从已设置过期时间的数据集(server.db[i].expires)中挑选要过期的数据淘汰

3.volatile-random:

从已设置过期时间的数据集(server.db[i].expires)中挑选任意数据淘汰

4.allkeys-lru:

当内存不足以容纳新写入数据时,在键空间汇总,移除最近最少使用的 key(这种方法最常用)

5.allkeys-random:

从数据集(server.db[i].dict)中任意选择数据淘汰

6.no-eviction:

禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错

 redis 持久化机制(保证 redis 断电挂掉之后再重启数据可以进行恢复)

 1.快照持久化:RBD 是通过父进程创建了一个子进程,该子进程的作用是将 redis 内存中的数据快照一份下来,写入一个临时的rdb文件,然后等持久化完毕后,然后覆盖掉上一次的 RDB 文件。

 触发机制:

1.满足了 save 的规则, 2.执行了 flushall 命令, 3.退出 redis

优点

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

2.只追加文件(AOF):实时性更好

不是默认的需要配置:appendonly yes

把写操作都记录下来,重启 redis 之后,再执行这个记录了写操作的文件appendonly.aof 

1.每次修改都记录

2.一秒记录一次

3.操作系统自己同步数据

修复速度和运行效率较慢

redis 事务

Redis 事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制, 并且在事务执行期间,服务器不会中断事务而去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才会去处理其他客户端的命令请求。 

 支持特性:一致性隔离性

缓存雪崩(全去查 mysql) 

解决方案:
redis 高可用:既然一台 redis 挂掉了,那么就多增几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建缓存集群,设置主节点、从节点。
限流降级:在缓存失效后,通过加锁或者队列来控制数据库写缓存的线程数量。比如某个key 值允许一个线程查询数据和写缓存,其他想要操作这个 key 那么必须等待
数据预热:先把可能的数据先访问一次,访问的数据加入缓存中,再设置不同的过期时间,让缓存失效时间点尽量均匀。

缓存穿透(查不到导致)

用户想要查询一个数据,发现 redis 内存数据库没有,也就是缓存没有命中,于是向数据库查询也没有查到

解决方案:

1.布隆过滤器

2.缓存空对象 

 缓存击穿:

缓存中没有但数据库中有的数据(一般是缓存时间到期)

解决方案:

  1. 设置热点数据永不过期。
  2. 加互斥锁 

如何解决 Redis 的并发竞争 Key 问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同!

推荐方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)

Redis为何这么快

1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2.数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的

3.采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多线程的切换导致消耗CPU,不用去考虑各种锁的问题。

4.使用多路I/O复用模型,非阻塞IO;

如何调用方法/函数? 

每一次函数调用都会有一个对应的栈帧压入 java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。

Java 方法有两种返回方式:  

     1. return 方式

     2. 抛出异常

不管哪种返回方式都会导致栈帧被弹出。

java 虚拟机栈是由一个一个栈帧组成,每个栈帧都有方法索引、输入输出参数、本地变量(局部变量)、引用、父帧、子帧),调用函数的所有需要的东西。

在普通类中,写入了方法都需要写方法体的。但是加入了 Native 关键字就不用写方法体 

因为带了 native 关键字的说明 java 的作用范围达不到了,他会去调用 c 语言的库会进入本地方法栈,调用本地方法接口 JNI

JNI 作用:拓展 JAVA 的使用,拓展其他的语言接口为 java 所用,比如:c/c++

在最终的执行中,都是本地方法栈去执行通过 JNI 接口。

当 java 文件成了字节码了,然后 jvm 里面一般是有解释器和编译器。

判断是否热点程序的两个方法

  1.  基于采样的热点探测:周期检查每个线程的栈顶方法,经常出现的方法,就是“热点方法”
  2.  基于计数器的热点探测:为每个方法建立计数器,统计执行方法的次数,超过一定的次数就认为它是”热点方法” 。包含两个计数器:(1)方法调用计数器;(2)回边计数器。

 所有定义的方法的信息都保存在方法区,此区域属于共享区域。

调用方法的过程:

  1. 先将类模板加载,
  2. 将默认的成员加载到常量池,
  3. 调用方法
  4. 去堆进行赋值操作,如果没有赋值,从常量池取

 Java 对象创建的过程

new指令--类加载检查--确定对象所需内存大小--为新生代对象分配内存--初始化零值--设置对象头--执行init方法 

两种方式来保证创建对象    是线程安全的: 

1.CAS+失败重试:

CAS 是乐观锁的一种实现。乐观锁是指,它每次都假设没有其他线程来干扰的,如果有线程干扰,那就重新创建,直到创建成功。这样可以保证更新操作的原子性。

2.TLAB:

为每一个线程预先在 Eden 区域分配一块内存,首先 TLAB 分配,对象的需要的内存大于了 TLAB 提供的,再采用 CAS 进行内存分配。

 堆内存常见分配策略:1.对象优先在eden区进行分配;2.大对象(需要大量连续内存空间的对象(比如:字符串、数组))直接进入老年代;3.长期存活的对象将进入老年代

进行 GC 收集的时候要判断这个对象是符合”垃圾”对象,才会去回收,2种判断方法:引用计数法、可达性算法 

运行时常量池主要回收的是废弃的常量。当前没有任何对象引用该字符串常量的话,就说明常量就是废弃常量。

垃圾收集有哪些算法

标记-清除算法

复制算法

标记-压缩算法

分代收集算法:

        当前采用的,新生代有大量的对象死去,所以我们选择复制算法;老年代的对象存活率比较高,而且没有额外的空间对他进行分配担保,所以我们是选择标记-清除或者标记-压缩算法。         

为什么要分新生区和老年区?为了提升 GC 效率,因为不同区使用不同的 GC 算法。 

JVM 调优 

 目标是:使用较小的内存占用来获得较高的吞吐量或者较低的延迟

重要指标:

  • 内存占用:程序正常运用需要的内存大小
  • 延迟:由于垃圾收集而引起的程序停顿时间
  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值

因为物理内存一定的条件下,新生代设置越大,老年代就越小,Full GC 频率越高,但是 Full GC 时间越短;相反新生代设置越小,老年代就越大,Full GC 频率就越低,但每次 Full GC 消耗的时间越大。所以要找到自己场景适合的合适点。 

调优建议
-Xms 和-Xmx 的值设置一样,为了避免内存的动态调整,因为当空闲堆内存不同的时候, 会切换-Xms 和-Xmx 内存状态,如果设置一样的话,就不会进行动态内存调整,节约资源。
新生代尽量设置大一些,让对象在新生代多存活一段时间,每次 Minor GC 尽可能多的收集垃圾对象,防止进入老年代,进行 Full GC.
老年代如果使用 CMS 收集器,新生代可以不用太大,因为 CMS 的并发收集速度也很快,收集过程可以与用户线程并发执行。

避免不需要的对象进入老年代:

 避免创建过大的对象或者数组;

避免同时加载大量数据;

当程序中有对象引用,如果使用完后,尽量设置为 null;

避免长时间等待外部资源,缩小对象的生命周期;

系统运行一个程序是一个进程创建、运行到消亡的过程。

每个线程有自己的程序计数器本地方法栈虚拟机栈。 

进程和线程最大的区别在于,不同程序的进程之间互不干扰,同一个进程中的线程极有可能互相影响。所以线程开销小(有共享内存区),但不利于资源的管理和保护;进程则相反 。

在一个JVM进程中,有五块内容:堆,方法区,程序计数器,本地方法栈,虚拟机栈。其中一个JVM进程中包含着多个线程。其中程序计数器、本地方法栈、虚拟机栈是单个线程私有的。堆和方法区是公有区域。 

一句话简单了解堆和方法区

堆和方法区是所有线程共享的资源,其中堆是最大的一块内存,主要存放新创建的对象;方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 

并发和并行的区别? 

并发:同一个时间段,多个任务都在执行(交替执行)。

并行:单位时间内,多个任务同时执行。

 使用多线程可能带来哪些问题?

并发编程的目的就是为了能提高程序的执行效率和提高程序运行速度,

会出现的问题:内存泄漏、上下文切换、死锁

什么是上下文切换?

一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采用的策略是为每个线程分配时间片轮转形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换

sleep()和wait()方法区别和共同点?

1.两者主要区别在于:sleep()方法没有释放锁,而wait()方法释放了锁。

2.两者都可以暂停线程的执行

3.Wait常用于线程之间的交互/通信,sleep常用于线程的停止

4.Sleep()方法必须设置苏醒时间,wait可以不用设置,等待唤醒。

为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

当我们new一个Thread,线程进入新建模式。调用start()方法的时候才会让线程进入就绪状态,当分配到时间片就可以进行运行了。Start()方法会执行线程的准备工作,然后自动去执行run方法,这是真正的多线程工作。如果直接调用run(),只会把run方法当做Main线程中的普通方法调用,而不会进入某个线程调用,所以这并不是多线程工作。

怎么使用synchronized关键字

1.修饰实例方法

2.修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,进入同步代码前获得当前class的锁。访问静态synchronized方法是占用的是当前类的锁,而访问非静态synchronized方法占用的是当前实例对象的锁。

3.修饰代码块:指定加锁对象,对给定对象/类加锁。

Synchronized同步语句块的实现使用的是monitorenter和monitorexit指令,其中monitorenter指令执行同步代码块的开始位置。

synchronized关键字和volatile关键字的区别

  1.Volatile关键字是线程同步的轻量级实现。所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只用于变量而synchronized关键字可以修饰方法以及代码块。

  2.Volatile关键字主要用于解决多个线程之间的可见性(缓存一致性的校验功能),而synchronized关键字解决的是多个线程之间访问资源的同步性。

为什么要使用线程池

1.降低资源的消耗

2.提高响应速度

3.提高线程的可管理性

1.execute()方法用于提交不需要返回值的任务;2.submit()方法用于提交需要返回值的任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值