怒刷牛客JAVA面经(6)

本文探讨了JVM内存的划分,包括堆内存、栈内存和本地内存,重点介绍了垃圾回收策略,尤其是分代垃圾回收和fullgc的影响及解决方法。此外,文中还涉及Redis在高并发场景的应用、MySQL索引的使用规则以及消息队列中的重复消息问题及其解决方案。
摘要由CSDN通过智能技术生成

本文题目来自于牛客上面的面经分享,会贴上来源帖子
本次题目来源:感谢分享
个人想法,结合其他资料整理的,文章有问题希望大佬可以指出,目前正在准备春招,希望上岸🙏🏻

1.jvm内存划分

引流:面试1的第十一题

  这里就直接总结一下javaguide的图:
在这里插入图片描述
  jvm内存一共被分为两大块,一块是线程共享区域,一块是线程独占区域,还有一块叫本地内存,这一块内存不是由JVM管理的,而是由操作系统自己管理的,下面分开简单讲讲:

  • 内存共享:

    1. 堆:
        Java 中的堆区(Heap)是用于存储对象实例的内存区域。堆区是 Java 虚拟机(JVM)管理的最大的一块内存区域,它的大小在 JVM 启动时就已经确定,并且可以动态扩展。其主要作用就是存放对象实例,因此也是垃圾回收的主要区域。
    2. 字符串常量池:
        顾名思义,就是用来存储字符串常量的地方,当你创建一个字符串常量时,如果字符串常量池中已经存在相同内容的字符串,那么该字符串的引用会被重用而不是重新创建一个新的字符串对象。这个机制在很多情况下可以减少内存的消耗,提高程序的执行效率。
  • 内存独占:

    1. 栈:
        java将栈区域分为了虚拟机栈和本地方法栈,他们两个的机制都是差不多的,都被用于存储方法的局部变量、方法的参数、部分计算结果以及方法的调用和返回信息,每一次调用方法就相当于创建了一个栈帧压入到虚拟机栈中,栈帧则是用来储存本次调用方法的相关信息,只是虚拟机栈存储的是Java方法,而本地方法栈存储的是本地方法,也就是被native修饰的方法。
    2. 程序计数器:
        不单单是java有这个概念,在计算机组成原理中,或者是单片机里面都有这个东西,他的作用就是用来记录当前线程执行到的指令位置,简单来说就是线程执行字节码指令的指示器。
  • 本地内存:

    1. 运行时常量池:
        它是用于存储类文件中的常量池信息以及在运行时产生的一些常量,他会在类加载后被创建,属于每个类的一部分。
    2. 直接内存:
        直接内存在Java中的作用是提供一种高效、灵活、不受堆内存限制的内存管理方式,特别适用于需要处理大量数据或高性能I/O操作的场景,能够帮助提高程序的性能和效率。



2.new创建的对象一定在java堆吗,局部变量是基本类型创建在哪,如果基本类型是成员变量呢。

  基本上所有的对象都会被放在java堆中,但是在JDK1.7以后,默认开启了逃逸分析功能,也就是我们可以通过分析对象的作用域,来判断这个对象是不是只被当前线程使用,或者只被这一次方法调用使用,如果是的话,就可以被分配到对应的栈中
  局部变量指的是方法中的变量,那么就是存放在栈空间,如果是基本数据类型,则是在栈中存放具体数据,如果是引用对象,则存放的是对象的指针,也就是存放在堆里的对象的地址,实际上对象是存放在堆内存中的(存在特殊情况,就是上面提到的逃逸情况)。
  如果成员变量的话,因为是按照对象为一个整体存放的,所以会被存在堆里面,不管是不是基本类型。
  可以用一句话来总结:方法体中的引用变量和基本类型的变量都在栈上,其他都在堆上。



3.jvm堆内存详细说说,为什么要这么划分,用的垃圾回收算法

  通过题目可以猜测,答的是堆内存的分代问题,下面就围绕内存分代讲讲:
  首先,java的堆内存是用来存放实例化对象的地方,也是垃圾回收的主要区域,因此也被成为GC堆,由于不同的垃圾回收算法都有各自的优缺点,所以为了更好地回收内存,或者更快地分配内存,会将堆内存按照对象的存活时间划分为三块区域:新生代、老年代、永久代(jdk8以后变成元空间),所以的存活时间指的是经过了多少轮垃圾回收却还存活的对象的垃圾回收轮数。
  至于分代的原因,我们可以根据不同区域的特性和垃圾回收算法来讲解:在新生代中,每次收集都会有大量对象被回收,只会存活下来少量的对象,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集,如果使用复制算法,由于大量的对象存活了下来,需要使用更多的时间来将对象复制到另一半空间,显然是不合适的。

4.什么时候会发生full gc

  有以下几种情况:

  1. 调用System.gc():
      这个方法并不是掉了就会让JVM进行full gc,而是建议JVM去执行full gc,具体是否执行需要JVM自行判断。
  2. 老年代空间不足:
      当Minor gc,也就是在新生代中的gc对象存活后,会存入到老年代中,这时候如果老年代空间不足,则会导致full gc。
  3. 永久代空间不足:
      这是jdk7之前的版本会出现的问题,永久代被用来存放类的相关信息,如果永久代满了以后,就无法创建新的类的对象,这时候就需要进行full gc。(之后的版本通过元空间代替永久代,元空间使用的是本地内存而不是通过jvm分配内存)。



5.full gc对程序的影响

 &emsp首先full gc是对整个堆区域进行垃圾回收,会导致整个程序的停止,并且是对所有区域回收,时间可能会比较长,就导致了程序的吞吐量降低,对于需要高性能,高并发的系统来说是比较严重的,full gc如果过于频繁,还会占用较多的cpu资源,导致其他程序也会受到影响。



6. 怎么解决full gc

  由于频繁的full gc会导致性能严重降低,所以我们可以通过以下方法减少full gc的次数:

  1. 增加堆内存:
      这是最简单的方法,内存不够我们就加内存,可以通过调整JVM参数来实现,比如 -Xms(初始堆大小)和 -Xmx(最大堆大小)。
  2. 通过工具查看gc情况:
      我们可以使用jstack或jvisualvm同时分析下占用cpu较高的线程或者内存占用较大的对象,看看是否可以优化代码来减少此类对象的创建。
  3. 尽量避免调用System.gc():
      虽然这个方法是建议垃圾回收,但是万一JVM采用了这个建议,那不就是会触发full gc,所以非必要不调用此方法。

  这些是比较好理解的方法,还有很多方法是需要调整JVM参数的,建议看看具体的文章来加强这一块。



7.线上系统发生了full gc我应该怎么快速解决定位

  首先,我们可以开启JVM的GC日志功能,可以通过JVM参数设置,具体参数如下:

  • -XX:+PrintGCDetails:打印GC日志细节
  • -XX:+PrintGCTimeStamps:打印GC日志时间
  • -Xloggc:xxx.log:将GC日志输出到指定的磁盘文件上去

  或者我们可以通过一下内存分析工具,比如jdk自带的jvisualVM来分析对象的使用内存情况,再具体分析可能的原因。
  定位问题无非就是找到问题的所在之处,通过工具分析基本上都能找到问题。


8.redis主要解决什么问题

  问的就是redis的使用场景,建议根据自己项目来答,这里提供几个常见案例:

  1. 缓存:
      这个估计是redis使用最多的场景,我们都知道从内存中读取数据是远远快于从磁盘中读取数据,而redis是基于缓存的nosql数据库,因此从redis读取数据会非常快,我们通过这个特性可以将数据库中经常被访问的热点数据放入到redis中,用户在获取这些数据的时候就会先访问redis,而不是访问数据库,这样既可以加快响应速度,也可以减轻数据库访问压力,防止数据库被压垮。
  2. 会话存储:
      也就是session存储,session可以看做是一种用户凭证,但他是被存放在服务端的,前端通过传送这个会话的id,后端再根据id定位到具体的session可以判断这个用户是否为合法登录用户或者判断他的权限。(现在的登录权限板块基本上是通过token进行验证控制的)
  3. 分布式锁:
      分布式锁被用在多个节点需要操作同一个资源的时候产生的竞争上,如果是单节点单体项目,则没必要使用分布式锁;redis的分布式锁可以通过setnx实现,或者如果是java项目,我们还可以通过Redisson实现,他提供的看门狗机制可以解决锁占用时间的问题。
  4. 限速器:
      我们可以通过信号量来限制访问流量,避免一瞬间的高并发将服务击垮,我们可以通过使用Redisson提供的令牌桶或者型号量实现。

  redis还有其他应用场景,比如消息队列、排行榜等,可以基于项目去答这个问题。


9.redis单机qps一般多少

  一般是上万的,但是得看指令的复杂度和请求数据的大小,如果请求的是大key,那么肯定就会降低。



10.如果qps远超redis的qps的话有什么方案(不让使用集群和多级缓存)

不是很会这道题,求大佬帮忙。

  可以参考一下以下方案:

  1. 增加服务器性能,比如更换更好的网卡、CPU、内存等,然后通过配置Redis的参数,比如说最大内存使用量,TPC连接队列长度等来提高redis的连接数,以此来提高并发量。
  2. 如果业务允许,可以使用消息队列,通过异步方式来处理请求。
  3. 优化代码。



11.给你一个场景,比如几万人同时访问淘宝页面,如果redis承载不足,超额并发,有什么解决方案(不会)

不是很会这道题,求大佬帮忙。

  如果是资源充足,我们可以通过增加Redis节点或者使用Redis集群的方式来提高并发量。
  或者使用CDN加速,将静态资源缓存在CDN节点上,减少对服务器的请求,并且只将热点数据缓存在Redis中。



12、13.mysql索引的数据结构

  估计问的是同一个问题,所以放一起讲:
  按照索引的底层数据结构来区分,在mysql中分为B+tree索引、Hash索引、Full-text索引。基本上都会采用B+树来当做索引的数据结构。
  为什么使用B+树呢,首先与Hash索引对比,虽然Hash索引对于等值查询的时间复杂度是O(1),但是对于范围查询,因为Hash是无序的,所以范围查询需要遍历整个索引才能查到具体数据,而B+数由于对于数据是有序存放的,更适合做范围查询。
  而Full-text索引,也叫做全文索引,是被用来替代效率低下的 LIKE ‘%***%’ 操作,只有CHAR、VARCHAR和TEXT这三种数据类型支持全文索引。



14. mysql联合索引是(a,b,c),查询条件是b=2,c=3,a=1,,怎么用索引,可以用吗

  这样是不能使用联合索引的,因为她不符合最左匹配原则。



15.mysql联合索引是(a,b,c),查询条件是a=1,b>2,c=3,怎么用索引,可以用吗.

  这个时候符合最左匹配原则,所以可以使用索引,但是由于出现了范围查询,在这个组合索引中,a字段是有序的,可以通过二分查找定位到a=1的数据;b字段在a=1确定的情况下是有序的,可以通过二分查找取出所有b大于2的数据;但是c字段是无序的,无法用二分查找来查询c=3的数据,因此c用不到索引。



16.消息队列mq消息重复现象怎么出现的,怎么解决

  可以使用唯一id来绑定这条消息,这是最简单粗暴的方法,至于如何回答这个问题,可以根据自己的项目或者具体开发中使用的方法来回答,不同公司会采用不同的方法来应对消息重复消费的情况。



代码题:链表归并排序

  力扣原题:21. 合并两个有序链表

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值