历史面试知识点1

知识点

1.平时碰到系统CPU飙高和频繁GC,你会怎么排查?

• 代码中某个位置读取数据量较大,导致系统内存耗尽,从而导致Full GC次数过多,系统缓慢;

• 代码中有比较耗CPU的操作,导致CPU过高,系统运行缓慢

• 代码某个位置有阻塞性的操作,导致该功能调用整体比较耗时,但出现是比较随机的;

• 某个线程由于某种原因而进入WAITING状态,此时该功能整体不可用,但是无法复现;

• 由于锁使用不当,导致多个线程进入死锁状态,从而导致系统整体比较缓慢。

Full 次数过多

代码中一次获取了大量的对象,导致内存溢出,此时可以通过eclipse的mat工具查看内存中有哪些对象比较多;

内存占用不高,但是Full GC次数还是比较多,此时可能是显示的System.gc()调用导致GC次数过多,这可以通过添加-XX:+DisableExplicitGC来禁用JVM对显示GC的响应。

CPU过高: top命令查看当前CPU消耗过高的进程是哪个,从而得到进程id;然后通过top -Hp <pid>来查看该进程中有哪些线程CPU过高,通过jstack命令查看线程id为10的线程为什么耗费CPU最高

在jstack日志中查看当前线程具体的堆栈信息。

mat工具进行查看dump内存 得到的内存日志

 

2.JVM 发生 OOM

堆内存不足:Java heap space

原因

1、代码中可能存在大对象分配

2、可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象

解决方法

1、检查是否存在大对象的分配,最有可能的是大数组分配

2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题

3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存

4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性

方法栈溢出:unable to create new native Thread

创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。

1、通过 *-Xss *降低的每个线程栈大小的容量

2、线程总数也受到系统空闲内存和操作系统的限制,

堆太小 GC overhead limit exceeded

1、检查项目中是否有大量的死循环或有使用大内存的代码,优化代码

3、dump内存,检查是否存在内存泄露,如果没有,加大内存。

idea小技巧

1.var声明:String.var     int.var.   class.var.  都会自动补齐选项,可以补齐

2.null 判空:object.null. 自动补齐

3.notnull 判非空: object.notnull. 自动补齐

4. nn 判非空。  object.nn. 自动补齐

5. for 遍历.  list.for   自动补齐

6.fori 带索引的遍历

8. if 条件判断:   list.size() > 0.if.     自动补齐

new BigDecimal(0) —> BigDecimal.zero

list 判空 用 CollectionUtils.isEmpty()

 

3.在cms算法中,young gc的实现过程?

先找出根对象,如Java栈中引用的对象、静态变量引用的对象和系统词典中引用的对象等待,把这些对象标记成活跃对象,并复制到to区,接着遍历这些活跃对象中引用的对象并标记,找出老年代对象在eden区有引用关系的对象并标记,最后把这些标记的对象复制到to,在复制过程还要判断活跃对象的gc年龄是否已经达到阈值,如果已经达到阈值,就直接晋升到老年代,YGC结束之后把from和to的引用互换。

 

 4.HashMap在并发使用时可能发生死循环,导致cpu100%why?

在并发的情况,发生扩容时,可能会产生循环链表,在执行get的时候,会触发死循环,引起CPU的100%问题,所以一定要避免在并发环境下使用HashMap。

 

5.对ThreadLocal的理解?

它是一个数据结构,有点像HashMap,可以保存"key : value"键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。这个key是ThreadLocal对象本身。set的值是需要保存的值。

set()时,通过获得到当前的thread,然后得到 t.threadLocals == ThreadLocalMap

,如果该map!=null, map.set(this, value);

每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行set方法中,是从当前线程的threadLocals变量获取。保证了线程之间不会相互干扰。

 

6.ThreadLocal可能导致内存泄漏,为什么?

,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。 Entry extends  WeakReference<ThreadLocal<?>>

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

 防止:使用完ThreadLocal之后,记得调用remove方法。

 

 7.垃圾回收机制方法:

引用计数法:给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加1,当引用失效时,计数器的值减1,任一时刻,如果对象的计数器值为0,那么这个对象就不会再被使用,

error:如果JVM垃圾收集器采用引用计数法,当obj1和obj2不再指向堆中的实例A、B时,虽然A、B已经不可能再被访问,但彼此间相互引用导致计数器的值不为0,最终导致无法回收A和B。(无法释放有循环引用的垃圾)

可达性分析:通过引用关系向下搜索,能被遍历到的 (可到达的) 对象就被判定为存活,其余对象 (也就是没有被遍历到的) 自然被判定为死亡。(通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的)

首次被标记的对象并一定会被回收,它还有自救的机会。一个对象真正的死亡至少需要经历两次标记过程:

标记所有不可达对象,并进行筛选,筛选的标准是该对象覆盖了finalize()方法且finalize()方法没有被虚拟机调用过,选出的对象将被放置在一个“即将被回收”的队列中。稍后虚拟机会创建一个低优先级的Finalizer线程去遍历队列中的所有对象并执行finalize()方法 对队列中的对象进行第二次标记,如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,那么这个对象将被移除队列,而还留在队列中的对象,就会被回收了。

 不可达的对象并非“非死不可”

即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survior空间,所以只好通过 分配担保机制 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。

大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。

为什么要这样呢?

为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。如果reference类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用动态对象年龄判定

为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。

引用:如果reference类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用

强引用:当内存空 间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。(软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。)

弱引用:一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

虚引用:它就和没有任何引用一样,在任何时候都可能被垃圾回收。(虚引用主要用来跟踪对象被垃圾回收的活动。)

 

8.垃圾收集算法

1 标记-清除算法

算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,效率也很高,但是会带来两个明显的问题:

0. 效率问题

0. 空间问题(标记清除后会产生大量不连续的碎片)

2 复制算法

为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。

3 标记-整理算法

根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。

选择合适的垃圾收集算法。

在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清楚”或“标记-整理”算法进行垃圾收集。

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

垃圾收集器

Serial收集器

它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。(新生代采用复制算法,老年代采用标记-整理算法,简单而高效)

 ParNew收集器

Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样(新生代采用复制算法,老年代采用标记-整理算法)

CMS收集器

一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。(“标记-清除”算法实现,并发收集、低停顿)

• 初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;

• 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

• 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

• 并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫。

G1收集器

面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region

 cms gc 通过一个后台线程触发,触发机制是默认每隔2秒判断一下当前老年代的内存使用率是否达到阈值,当然具体的触发条件没有这么简单,如果是则触发一次cms gc,在该过程中只会标记出存活对象,然后清除死亡对象,期间会产生碎片空间。

full gc 是通过 vm thread 执行的,整个过程是 stop-the-world,在该过程中会判断当前 gc 是否需要进行compact,即把存活对象移动到内存的一端,可以有效的消除cms gc产生的碎片空间。

 

9.反射

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Proxy target = new Proxy();

        //调用Class类的getDeclaredMethod可以获取指定方法名和参数的方法对象Method。

        //generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象,

//        所次每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象,如果需要频繁调用,最好把Method对象缓存起来

        Method method = Proxy.class.getDeclaredMethod("fun");

//       获取到指定的方法对象Method之后,就可以调用它的invoke方法了

        method.invoke(target);

    }

    static class Proxy {

        public void fun() {

            System.out.println("run");

        }

    }

Class对象:虚拟机在class文件的加载阶段,把类信息保存在方法区数据结构中,并在Java堆中生成一个Class对象,作为类信息的入口。

获取Class对象一般有三种方式:

0. 通过实例变量方式

 Dog dog = new Dog(); Class clazz = dog.getClass();

0. 通过类名方式

 Class clazz = Dog.class; 通过这种方式时,只会加载Dog类,并不会触发其类构造器的初始化。

0. 通过Class.forName(String classname)方式

  Class clazz = Class.forName("zzzzzz.Dog");

JDK源码实现中,forName方法会调用Native方法forName0(),private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader,  Class<?> caller)

它在JVM中调用findClassFromClassLoader()加载Dog类,其原理和ClassLoader一样,将会触发Dog类的类构造器初始化,

其中initialize参数,用来告诉虚拟机是否需要对加载的类进行初始化,如果initialize为false,则不会进行初始化Dog类。

 反射机制reflect可以在运行期间获取类的字段、方法、父类和接口等信息。

1、获取类字段 Field[] fields = class_dog.getDeclaredFields(); field.getName()

2、获取类方法 Method[] methods = class_dog.getDeclaredMethods();通过method.invoke(obj, ...args)可以调用obj实例的method方法。

4、通过newInstance()方法生成类实例

Class class_dog = Dog.class;

Dog dog = class_dog.newInstance();

反射的性能问题

Stackoverflow上,很多人觉得使用反射reflect会影响系统性能,主要有以下几点看法:

1、代码的验证防御逻辑过于复杂,本来这块验证时在链接阶段实现的,使用反射reflect时需要在运行时进行;

2、产生过多的临时对象,影响GC的消耗;

3、由于缺少上下文,导致不能进行更多的优化,如JIT;

 

10.ConcurrentHashMap

jdk1.7中采用Segment + HashEntry的方式进行实现:ConcurrentHashMap初始化时,计算出Segment数组的大小ssize和每个Segment中HashEntry数组的大小cap,并初始化Segment数组的第一个元素;其中ssize大小为2的幂次方,默认为16,cap大小也是2的幂次方,最小值为2,最终结果根据根据初始化容量initialCapacity进行计算

其中Segment在实现上继承了ReentrantLock,这样就自带了锁的功能。

put实现

当执行put方法插入数据时,根据key的hash值,在Segment数组中找到相应的位置,如果相应位置的Segment还未初始化,则通过CAS进行赋值,接着执行Segment对象的put方法通过加锁机制插入数据

场景:线程A和线程B同时执行相同Segment对象的put方法

1、线程A执行tryLock()方法成功获取锁,则把HashEntry对象插入到相应的位置;

2、线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;

3、当线程A执行完插入操作时,会通过unlock()方法释放锁,接着唤醒线程B继续执行;

 

1.8中采用Node + CAS + Synchronized来保证并发安全进行实现

 put实现

当执行put方法插入数据时,根据key的hash值,在Node数组中找到相应的位置,实现如下:

1、如果相应位置的Node还未初始化,则通过CAS插入相应的数据;

2、如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;

3、如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;

4、如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;

Java对象的内存分配 有以下几种分配方式:

1、从线程的局部缓冲区分配临时内存

2、从内存堆中分配临时内存

3、从内存堆中分配永久内存

新生代:大小通过-Xmn参数指定;-XX:+PrintGCDetails参数

老年代:空间大小即-Xmx 与-Xmn 两个参数之差

 

11.永久代(元空间)

 元数据如方法数据、方法信息(字节码,栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中

把永久代从Java堆中移除了,并把类的元数据直接保存在本地内存区域(堆外内存),称之为元空间。

这样做有什么好处?

有经验的同学会发现,对永久代的调优过程非常困难,永久代的大小很难确定,其中涉及到太多因素,如类的总数、常量池大小和方法数量等,而且永久代的数据可能会随着每一次Full GC而发生移动。

而在JDK8中,类的元数据保存在本地内存中,元空间的最大可分配空间就是系统可用内存空间,可以避免永久代的内存溢出问题,不过需要监控内存的消耗情况,一旦发生内存泄漏,会占用大量的本地内存。

 

12.双亲委派模型

工作过程:当一个类加载器收到类加载任务,优先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。

双亲委派模型有什么好处?

比如位于rt.jar包中的类java.lang.Object,无论哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,确保了Object类在各种加载器环境中都是同一个类。

 

13.内存模型

 

可以理解为在特定的操作协议下,对特定的内存或告诉缓存进行读写访问的过程抽象

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范

顺序一致性内存模型,Java内存模型,处理器内存模型

处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,

Java内存模型:JVM虚拟机规范中定义了一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果

Java内存模型就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。

 

14.

netstat ano 查看端口使用情况

jmap 查看dump文件中堆使用信息

  jmap head pid

15.

    rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常

    rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常

    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列

    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务

16.

1:Bean的建立:

容器寻找Bean的定义信息并将其实例化。

2:属性注入:

使用依赖注入,Spring按照Bean定义信息配置Bean所有属性

4:BeanFactoryAware的setBeanFactory():工厂调用setBeanFactory()方法传入工厂自身。

5:初始化

6,使用

7,容器关闭,销毁bean

17.

volatile 保证变量可见性

atomicInteger 通过volatile修饰value,用cas保证同步

synchronize 通过在对象头中设置标志使同步,CAS

lock  volatile变量,CAS,基于CLH(线程同步)队列(储存拥有线程的node节点),实现锁的排队策略,获取不到,则进入等待队列

公平锁和非公平锁:在放入队列时是否判断这个队列是否有值

18,

分布式锁

1.数据库乐观锁

2.redis的setnx(数据库的cas)

3.基于Zookeeper的分布式锁 利用节点名称的唯一性来实现独占锁

19.

Aop:

面向切面编程,把一些系统级的业务,比如事务,日志,异常处理,以切面的形式织入程序,是我们的代码看起来更整洁,有效的保护了我们的业务逻辑,使程序员能更专注的关注逻辑本身.

Spring Aop是使用代理来实现的,包括jdk动态代理和cglib代理

Jdk动态代理:

实现:被代理类实现一个被代理接口,代理类实现invocationhandler,并且重写invoke方法,我们的对业务的增强就是在invoke中实现的。原理是字节码重组,在内存中生成一个新的代理类,继承proxy,实现被代理接口,生成新的代理类中有一个this.h.inveke,这个invoke方法就是由我们的代理对象来调用的,而字节码重组是由proxy类的newinstance方法实现的

 

 

CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻

JDK 可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

 

1、JDK动态代理具体实现原理:

 

通过实现InvocationHandler接口创建自己的调用处理器;

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;

通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

 

JDK动态代理是面向接口的代理模式,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

 

2、CGLib动态代理:

CGLib 可以实现运行期动态扩展java类,Spring在运行期间通过CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

 

3、两者对比:

JDK动态代理是面向接口的。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。

 

20.

4. 事务的传播机制

 

类型    说明

PROPAGATION_REQUIRED    如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。

PROPAGATION_SUPPORTS    支持当前事务,如果当前没有事务,就以非事务方式执行

PROPAGATION_MANDATOR    使用当前的事务,如果当前没有事务,就抛出异常

PROPAGATION_REQUIRES_NEW    新建事务,如果当前存在事务,把当前事务挂起

PROPAGATION_NOT_SUPPORTED    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

PROPAGATION_NEVER    以非事务方式执行,如果当前存在事务,则抛出异常

PROPAGATION_NESTED    如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED 类似的操作

21.

JVM参数说明

      -Xmx3550m:设置JVM最大堆内存为3550M。

      -Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

      -Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,

      -Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代

 

回收器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况

吞吐量优先的并行收集器

响应时间优先的并发收集器

27.

left join

如果想对右表进行限制,则一定要在on条件中进行,若在where中进行则可能导致数据缺失,导致左表在右表中无匹配行的行在最终结果中不出现,违背了我们对left join的理解。

28.

执行偏向锁,如果执行CAS失败,表示当前存在多个线程竞争锁,当达到全局安全点(safepoint),获得偏向锁的线程被挂起,撤销偏向锁,并升级为轻量级,升级完成后被阻塞在安全点的线程继续执行同步代码块。如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级。如果CAS失败,说明有其它线程在尝试获取该锁,这时需要将该锁升级为重量级锁,并释放。重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
旅游社交小程序功能有管理员和用户。管理员有个人中心,用户管理,每日签到管理,景点推荐管理,景点分类管理,防疫查询管理,美食推荐管理,酒店推荐管理,周边推荐管理,分享圈管理,我的收藏管理,系统管理。用户可以在微信小程序上注册登录,进行每日签到,防疫查询,可以在分享圈里面进行分享自己想要分享的内容,查看和收藏景点以及美食的推荐等操作。因而具有一定的实用性。 本站后台采用Java的SSM框架进行后台管理开发,可以在浏览器上登录进行后台数据方面的管理,MySQL作为本地数据库,微信小程序用到了微信开发者工具,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得旅游社交小程序管理工作系统化、规范化。 管理员可以管理用户信息,可以对用户信息添加修改删除。管理员可以对景点推荐信息进行添加修改删除操作。管理员可以对分享圈信息进行添加,修改,删除操作。管理员可以对美食推荐信息进行添加,修改,删除操作。管理员可以对酒店推荐信息进行添加,修改,删除操作。管理员可以对周边推荐信息进行添加,修改,删除操作。 小程序用户是需要注册才可以进行登录的,登录后在首页可以查看相关信息,并且下面导航可以点击到其他功能模块。在小程序里点击我的,会出现关于我的界面,在这里可以修改个人信息,以及可以点击其他功能模块。用户想要把一些信息分享到分享圈的时候,可以点击新增,然后输入自己想要分享的信息就可以进行分享圈的操作。用户可以在景点推荐里面进行收藏和评论等操作。用户可以在美食推荐模块搜索和查看美食推荐的相关信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值