美文网首页
2019-12-14

2019-12-14

作者: 滔滔逐浪 | 来源:发表于2022-10-30 18:04 被阅读0次

    1,ThreadLocal有什么缺陷? 如果是线程池里用ThreadLocal会有什么问题?
    ```
    可能会引起内存的泄漏,因为ThreadLocalMap中 key维护着一个weakReference,他在下次GC之前会被清理,如果value 让然保持着外部的强引用,该 ThreadLocal没有在进行set,get或者remove操作,时间长了就可能导致OutOfMemoryError

        线程池中的线程是会被重复利用的,而ThreadLocal是用线程来做key的,有个实例在一次请求中会出现被调用多次的情况,所以需要区分线程当前处理是否同一次请求。不是同一次请求,可能ThreadLcal保存的请求信息是不同的,例如用户信息。不同用户的请求可能可能会共同线程池中同一个线程去处理,则这时候需要重置线程本地变量。
        ```
    

    2,类的加载机制,为什么要用双亲委托? 如何打破双亲加载机制?
    JVM自带的ClassLoader类
    JDK中提供了三个ClassLoader,根据层级,从高到低为:
    1,Bootstrap ClassLoader,主要加载JVM自身工作需要的类
    2, Extension ClassLoader,主要加载%JAVA_HOME%\lib\ext目录下的库类。
    3,Application ClassLoader,主要加载Classpath指定的库类,一般情况下这是程序中的默认类加载器。也是ClassLoader.getSystemClassLoader()的返回值(这里的classpath)默认指的是环境变量中配置的Classpath,但是可以在执行java命令的时候使用-cp参数来修改当前程序使用的Classpath)


    image

    jvm加载类的实现方式,我们叫做双亲委托模型:
    如果一个类加载器收到类的加载请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父类加载器,每一层的类加载器都是如此,因此所有的类加载请求都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器无法完成加载请求时,子加载器才会尝试自己加载。

    双亲委托模型加载类的过程
    我们用一张简单的图片来描述 “使用自定义ClassLoader 加载一个类的过程”:


    image

    1,自定义ClassLoader 向自己的上层(Application ClassLoader)请求
    2 Application ClassLoader继续向上层(Extension ClassLoader) 请求
    3,Extension ClassLoader继续向上层(Bootstrap ClassLoader)请求
    Bootstrap ClassLoader是最上层类加载器,所以它尝试在自己的路径中查找要加载类,如果查找到了就加载类,否则向下层(Extension ClassLoader)反馈自己无法加载类。
    Extension ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
    Application ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
    自定义ClassLoader从自己的路径中寻找要加载类,找到则加载。由于类加载请求是自定义ClassLoader发起的,所以当它自己也找不到要加载的类时会终止加载并抛出
    ClassNotFoundException。

    为什么要用双亲委托模型
    双亲委托模型的重要用途是为了解决类载入过程中的安全性问题。
    假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义的ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader 的路径下找到java.lang.Object类,并载入他。

    java的类加载是否一定要遵循双亲委托管理模型?
    这个答案是否定的
    双亲委托模型只是JDK提供的ClassLoader类的实现模式。
    在实际开发中,我们可以通过自定义ClassLoader,并且重写父类的loadClass方法,来打破这一机制。

    3,如果有个100万的qps项目,你会从哪些方面考虑系统的设计?

    4,你平常使用的设计模式有哪些?

    5,熟悉Reactive开发模式吗?

                Reactive编程是一种通过将只能路由和事件消费组合起来改变行为的微架构风格。
    Recative编程的应用场景
       1,外部服务调用
            当今的后端系统很多都是RESTful的,底层协议是阻塞和同步的。并且服务之间会相互调用,必须要等到第一个请求调用完成后才能调用下一个请求。客户端可能在服务端处理完成之前就放弃请求了。因此外部服务调用,特别是需要调用多个服务才能完成处理的时候,是一个需要去优化的场景。
    2,高并发的消息消费
           高并发的消息处理是企业应用的常见场景,Reactive模式非常适合处理消息(事件可以很容易的转换为消息)。
    3,电子表格
      这不是一个企业应用场景,但是Reactive模式可以很轻松的处理这种需求。
    4,对异步调用进行抽象
    Recative编程可以让我们不用关心调用的是同步的还是异步的,单纯的异步编程是很繁琐的,Recative模式可以简化异步编程
    
    5,Recative可以帮助我们节省服务器资源,可以用少量的线程支持更高的负载。Recative,非阻塞,异步提供了很好的解决问题的方法
    
    
    

    6,你熟悉的分布式技术有哪些? 了解他们底层的实现机制吗?

    7,springcloud各个组件的运行机制是什么?

    8,TreeMap与TreeSet 实现原理是什么?

    9,Array和ArrayList的区别?
    可以将ArrayList想象成一种“会自动扩容量的Array”
    Array([]):最高效;但是其容量固定且无法改变;
    ArrayList: 容量可动态增长;但是牺牲效率

    基于效率和类型检验: 尽可能使用Array,无法确定数组大小时才使用ArrayList
    不过当你试着解决更一般化的问题时,Array的功能就可能过于受限。

    java中一切皆对象,Array也是对象。不论你所使用得Array型别如何。

    10 JVM的数据区有哪些?


    image

    如上图所示,首先java源代码文件(.java)后缀会被java编译器编译微微字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称为Runtime Data Area(运行时数据区),也就是我们常说的JVM 内存。因此,在java中我们常说的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)

    一 运行时数据区包括哪些部分

    根据<<java虚拟机规范>>的规定,运行时数据区通常包括这几个部分,程序计数器,java栈,本地方栈。方法区,堆 image

    如上图所示,JVM中运行时数据区应该包括这些部分。在JVM规范中虽然规定了程序在执行期间运行时数据区应该包括这几个部分,但是至于具体如何实现并没有做出规定:

    1,程序计数器
    程序计数器,也有被称作为PC寄存器。想必学过汇编语言的朋友对程序计数器这个概念并不陌生,在汇编语言中,程序计数器是指CPU中的寄存器,他保存的是程序当前执行的指令的地址(也可以说是保存下一条指令的所在存储单元地址),当cpu需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取指令,在得到指令以后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。
    虽然在JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上 的CPU寄存器,但是JVM中的程序计数器的功能跟汇编语言中的程序计数器功能在逻辑上是等同的,也就是说用来指示 执行哪条指令的。

    11 JVM堆内存结构是怎样的? 哪些情况会触发GC?

              JVM堆内存的内部结构:
     所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制.堆被划分为新生代和旧生代。新生代又被划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,结构图如下:
    ![image](https://img.haomeiwen.com/i12197462/11e5fbede462eab3.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
         新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代的大小可以由-Xmn来控制。也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象。
     分代收集算法是目前大部分JVM的垃圾收集器采用的算法。他的核心思想是根据对象存活的生命周期内存划分为若干个不同的区域。一般情况下将堆区划分为老生代和新生代。老生代的特点是每次垃圾收集时只有少量对象需要被回收,二新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么久可以根据不同代的特点采取最适合的收集算法。
    
    目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1 比例来划分新生代的空间的,一般来说将新生代划分为一块较大的Eden空间和2块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚刚使用过的Survior空间。
    
    由于老年代的特点是每次回收少量对象,一般使用的是Mark-Compact算法
    
    对象的内存分配,往大方向说就是在堆上分配,对象主要分配在新生代的Eden Space和From Space,少数情况下会直接分配在老年代。如果新生代发Eden Space和From Space的空间不足,则会发起一次GC,如果进行了GC之后,Eden space和From Sapce 能够容纳该对象就放在Eden Space和From Space。在GC过程中会将EdenSpace 和Form Space中的存活对象移动到To space,然后将Eden Space 和From Space进行清理。如果在清理过程中,Tospace 无法足够来存储对象,就会将该对象移动到老年代中。在进行了GC之后,使用的便是Eden Space 和To Space了, 下次GC时,会将存活对象复制到From Space,如此反复循环。当对象在Survivor区躲过一次GC的话,其对象年龄便会叫1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。
          一般来说,大对象会直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象。最常见的一种大对象就是大数组,比如:
     byte[] data=new byte[4*1024*1024] ;
    这种一般会直接在老年代分配存储空间。
    当然分配的规则并不是百分之百固定的,这要取决于当前使用那种垃圾收集器组合和JVM的相关参数。
    
    
    
    
    
           
    
    

    12 数据库你们是怎么优化的?

    一, 百万级数据库优化方案
    1对查询进行优化,要尽量避免全表扫描,首先考虑在where以及order by涉及的列上建立索引。
    2,应尽量避免在where 字句中对字段进行 null的判断,否则将导致引擎放弃使用索引而进行全表扫描:
    
    如:
    select id from t where num is null
    最好不要给数据库留null,尽可能的使用Not Null 填充数据库
    备注,描述,评论之类的可以设置为NULL,其他的最好不要使用NULL.
    不要以为NUll不需要空间,比如: char(100)型,在字段建立时,空间就固定了,不管是否插入值(NULL 也包含在内),都是占用100个字符的空间的,如果是varchar这样的变长字段,NULL不占用空间。
    
    可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
      select id  from  t where num=0
    
    3,应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引进行全表扫描。
    
    4,应尽量避免在where 子句中使用or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致放弃索引而进行全表扫扫描,如:
    select id from  t where num=10  or  name='admin'
    也可以这样查询:
    select id  from  t where num =10  union allselect id from t where Name='admin'
    
    5, in  和 not in 也要慎用,否则会导致全表扫描,如:
    
         select id from t where  num in(1,2,3)
      对于连续的数值,能用between就不要用in 了;
    select id from t where num between 1and 3
    很多时候用 exists 代替in  是个好选择:
    select num from a where num in (select num from b)
    用下面的语句替换:
    select  num from a where  exists(select 1 from b  where num=a.num)
    
    6 ,下面的查询也将导致全表扫描:
    select id from t where name  like '%abc%'
    若要提高效率,可以考虑全文检索。
    7,如果在where 字句中使用参数,也会导致全表扫描。因为SQL只有在运行的时候,才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;他必须在编译的时候进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面的语句将进行全表扫描:
    select id from t where num =@num
    可以改为强制查询使用索引:
    select id from  t with(index(索引名))  where num =@num
    
    应尽量避免在where子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。比如:
    select id from  t where num/2 =100
    
    应该改为:
    select id from  t where num =100*2;
    9,应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引,而进行全表扫描:
    如:
    select id from t where sunstring(name,1,3)='abc'  --name以abc开头的id
    select id from  t where datediff(day,createdate, '2005-111-30')=0 
    应改为:
    select  id from t where  name like 'abc%'
    select id from t where createdate >= '2015-11-30' and createdate < '2005-12-1'
    
    10 不要再 where 子句中的"="左边进行函数,算数运算或其他的表达式运算,否则系统将可能无法正确的使用索引。
    
    11 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一至。
    
    12 不要写一些没有意义的的查询,如需要生成一个空表字段。
    select  col1,col2 into #t from t where 1=0
    
    这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
    create table #t(...)
    
    13 Update语句,如果只改1,2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
    
    14 对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页在JOIN, 否则逻辑读会很高。性能很差。
    
    15  select count(*)from table ;  这样不带任何条件的count 会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
    
    16  索引不是越多越好,索引固然可以提高相应的select 的效率,但是同时也降低了insert以及update 的效率,因为insert 或update 时有可能会重建索引,所以怎么样建索引需要慎重考虑,,一个表的索引最好不要超过6个
    
    
    17 应该尽可能的避免更新clustered 索引数据列,因为clustered索引数据列的的顺序就是表记录里的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源,若应用系统频繁更新,clustered 索引数据列,那么需要考虑的是否应该将索引建为clustered 索引。
    
    18 尽量使用数字型字段,若只含数值信息的字段尽量不要设为字符型,这会降低查询和连接的性能,并会增加存储开销,这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字而言只需要比较一次就可以。
    
    
    19  尽可能的使用 varchar/nvarchar   代替 char/nch  因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些、
    
    20 任何地方都不要使用 select * from t  用具体的字段列表代替 "*" ,不要反回用不到的字段。
    
    
    21  尽量使用表变量来代替临时表,如果表变量包含大量的数据
    
    22 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表不是不可以用,适当的使用可以使某些例程更有效,例如,当需要重复引用大型表或常用表中某个数据集的时候,但是对于一次性事件,最好使用导出表。
    
    
    23, 在新建临时表时,如果一次性插入数据量很大,那么可以使用select into 代替create table,避免造成大量的log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create  table,然后insert。
    
    24 如果使用到了临时表,在存储过程的最好务必将所有的临时表显示删除,先truncate table,然后 drop table,这样可以避免系统表的较长时间锁定。
    
    25 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过一万行,那么就该考虑改写。
    
    26 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常有效。
    
    27  与临时表一样,游标并不是不可使用。对小型数据集使用FAST_ForWARD 游标 通常要优于其他逐行处理的方法。尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括"合计"的历程通常要比使用游标执行的速度快。
    
    28 在搜优的存储过程和触发器的开始处设置 SET NOCOUNT  ON,在结束时候设置SET NOCOUNT OFF  。无需再执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC 消息。
    
    29 尽量避免大事物操作。提高系统的并发能力。
    
    30 尽量避免向客户端反回大数据量,若数据量过大,应该考虑相应需求是否合理。
    
    
    实际案例分析: 拆分大的DELETE 或INSERT语句,批量提交SQL语句
    如果你需要在一个在线的网站上区执行一个大的DELETE 或INSERT查询,你需要非常小心,要避免因为你的操作让你的网站停止响应。因为这2个操作是会锁表的,表一锁住了,别的操作都进不来了。
    
    Apache 会有很多的子进程或线程。所以,其工作起来相当的有效率,而我们的服务器也不希望有太多的子进程,线程和数据库相连接,这是极大的占服务资源的事情,尤其是内存。
      如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的网站来说,这30秒所积累的进程、线程,数据库连接,打开的文件数。可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂掉。
    
    
      所以,如果你有一个大的处理。你一定把其拆分,使用LIMIT Oracle(rownum),sqlserver(top)),条件是一个好的办法。下面是一个MYSQL实例。
    
    
    
    
    while(1){
      //每次只做1000条 mysql_query(delete from logs where log_date <='2012-11-01' limit 1000 );
                   if(mysql_affected_rows() ==0){
                            //删除完成,退出!  break;
    }//每一次暂停一段时间,释放表让其他进程/线程访问
    usleep(50000)
    
    
    }
          
    
    二,数据库性能访问优化
    
    优化法则归纳为5个层次:
     1,减少数据访问(减少磁盘访问)
     2 反回更少的数据(减少网络传输或者磁盘访问)
    3减少交互次数(减少网络传输)
    4 减少服务器CPU开销(减少CPU以及内存开销)
    5 利用更多的资源
    
    由于每一层优化法则都是其对应硬件的性能问题,所以带来的性能提升比例也不一样。传统数据库系统设计也是尽可能对低速设备提供优化方法,因此针对低速设备问题的可优化手段也很多,优化成本也更低
    
    
    
    
    
    
    
    
    
    
    

    13 synchronization 和lock 有什么区别?

    14 用过反向代理服务器吗? 用来做什么? nginx 负载均衡有哪些参数?

    15 你熟悉的消息队列中间件的实现原理是什么? 和其他的消息中间件对比,有什么优势?

    16 Poll 与ePool的区别?

    17 BIO与NO 有什么区别?

    相关文章

      网友评论

          本文标题:2019-12-14

          本文链接:https://www.haomeiwen.com/subject/ckkcnctx.html