1、newCachedThreadPool
特点:
newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制
缺点:
他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问题的点,就可以去重写一个方法限制一下这个最大值
总结:
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程
2、synchronized 和 ReentrantLock 区别是什么?
1、 synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量
2、 synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
相同点:两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
主要区别如下:
1、 ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
2、 ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
3、 ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
4、 二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word
5、 Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1、 普通同步方法,锁是当前实例对象
2、 静态同步方法,锁是当前类的class对象
3、 同步方法块,锁是括号里面的对象
3、说下有哪些类加载器?
Bootstrap ClassLoader(启动类加载器) Extention ClassLoader(扩展类加载器) App ClassLoader(应用类加载器)
4、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
1、 可以使锁更公平
2、 可以使线程在等待锁的时候响应中断
3、 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
4、 可以在不同的范围,以不同的顺序获取和释放锁
5、JVM的永久代中会发生垃圾回收么
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
6、为什么wait(), notify()和notifyAll ()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
7、简单描述一下(分代)垃圾回收的过程
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3
,老生代的默认占比是 2/3
。
新生代使用的是复制算法,新生代里有 3 个分区:Eden
、To Survivor
、From Survivor
,它们的默认占比是 8:1:1
,它的执行流程如下:
当年轻代中的Eden区分配满的时候,就会触发年轻代的GC(Minor GC)。具体过程如下:
1、 在Eden区执行了 第一次
GC之后,存活的对象会被移动到其中一个Survivor分区(以下简称from)
2、 Eden区再次GC,这时会采用复制算法,将Eden和from区一起清理。存活的对象会被复制到to区。接下来,只需要清空from区就可以了
8、说一下Java对象的创建过程
1、 类加载检查:虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。2)分配内存:在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。选择以上2种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。
2、 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:CAS+失败重试:CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。TLAB:为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配
3、 初始化零值:内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4、 设置对象头:初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。这些信息存放在对象头中。另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5、 执行 init 方法:在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
9、JVM 监控与分析工具你用过哪些?介绍一下。
1、 jps,显示系统所有虚拟机进程信息的命令行工具
2、 jstat,监视分析虚拟机运行状态的命令行工具
3、 jinfo,查看和调整虚拟机参数的命令行工具
4、 jmap,生成虚拟机堆内存转储快照的命令行工具
5、 jhat,显示和分析虚拟机的转储快照文件的命令行工具
6、 jstack,生成虚拟机的线程快照的命令行工具
7、 jcmd,虚拟机诊断工具,JDK 7 提供
8、 jhsdb,基于服务性代理实现的进程外可视化调试工具,JDK 9 提供
9、 JConsole,基于JMX的可视化监视和管理工具
10、 jvisualvm,图形化虚拟机使用情况的分析工具
11、 Java Mission Control,监控和管理 Java 应用程序的工具
1、 MAT,Memory Analyzer Tool,虚拟机内存分析工具
2、 vjtools,唯品会的包含核心类库与问题分析工具
3、 arthas,阿里开源的 Java 诊断工具
4、 greys,JVM进程执行过程中的异常诊断工具
5、 GCHisto,GC 分析工具
6、 GCViewer,GC 日志文件分析工具
7、 GCeasy,在线版 GC 日志文件分析工具
8、 JProfiler,检查、监控、追踪 Java 性能的工具
9、 BTrace,基于动态字节码修改技术(Hotswap)实现的Java程序追踪与分析工具
下面可以重点体验下:
JDK 自带的命令行工具方便快捷,不是特别复杂的问题可以快速定位;
阿里的 arthas 命令行也不错;
可视化工具 MAT、JProfiler 比较强大。
10、Java中Semaphore是什么?
Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。