1、如何开启和查看 GC 日志?
常见的 GC 日志开启参数包括:
1、 -Xloggc:filename,指定日志文件路径
2、 -XX:+PrintGC,打印 GC 基本信息
3、 -XX:+PrintGCDetails,打印 GC 详细信息
4、 -XX:+PrintGCTimeStamps,打印 GC 时间戳
5、 -XX:+PrintGCDateStamps,打印 GC 日期与时间
6、 -XX:+PrintHeapAtGC,打印 GC 前后的堆、方法区、元空间可用容量变化
7、 -XX:+PrintTenuringDistribution,打印熬过收集后剩余对象的年龄分布信息,有助于 MaxTenuringThreshold 参数调优设置
8、 -XX:+PrintAdaptiveSizePolicy,打印收集器自动设置堆空间各分代区域大小、收集目标等自动调节的相关信息
9、 -XX:+PrintGCApplicationConcurrentTime,打印 GC 过程中用户线程并发时间
10、 -XX:+PrintGCApplicationStoppedTime,打印 GC 过程中用户线程停顿时间
11、 -XX:+HeapDumpOnOutOfMemoryError,堆 oom 时自动 dump
12、 -XX:HeapDumpPath,堆 oom 时 dump 文件路径
Java 9 JVM 日志模块进行了重构,参数格式发生变化,这个需要知道。
GC 日志输出的格式,会随着上面的参数不同而发生变化。关注各个分代的内存使用情况、垃圾回收次数、垃圾回收的原因、垃圾回收占用的时间、吞吐量、用户线程停顿时间。
借助工具可视化工具可以更方便的分析,在线工具 GCeasy;离线版可以使用 GCViewer。
如果现场环境不允许,可以使用 JDK 自带的 jstat 工具监控观察 GC 情况。
2、在Java中Executor和Executors的区别?
1、 Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
2、 Executor 接口对象能执行我们的线程任务。
3、 ExecutorService接口继承了Executor接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
4、 使用ThreadPoolExecutor 可以创建自定义线程池。
5、 Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果。
3、常用JVM基本配置参数
1、 -Xmx:最大分配内存,默认为物理内存的1/4
2、 -Xms:初始分配内存,默认为物理内存的1/64
3、 -Xss:等价于-XX:ThreadStackSize,单个线程栈空间大小,默认一般为512k-1024k,通过jinfo查看为0时,表示使用默认值
4、 -Xmn:设置年轻代大小
5、 -XX:MetaspeaceSize:设置元空间大小(默认21M左右,可以配置大一些),元空间的本质可永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代的最大区别在于:元空间不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间大小仅受本地内存大小限制
6、 典型设置案例:-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
7、 -XX:+PrintGCDetails:打印垃圾回收细节,打印GC: 打印Full GC:
8、 -XX:SurvivorRatio:调整Eden中survivor区比例,默认-XX:SurvivorRatio=8(8:1:1),调整为-XX:SurvivorRatio=4(4:1:1),一般使用默认值
9、 -XX:NewRatio:调整新生代与老年代的比例,默认为2(新生代1,老年代2,年轻代占整个堆的1/3),调整为-XX:NewRatio=4表示(新生代1,老年代4,年轻代占堆的1/5),一般使用默认值
10、 -XX:MaxTenuringThreshold:设置垃圾的最大年龄(经历多少次垃圾回收进入老年代),默认15(15次垃圾回收后依旧存活的对象进入老年代),JDK1.8设置必须0<-XX:MaxTenuringThreshold<15
4、什么是类加载器,类加载器有哪些?
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
1、 动类加载器(Bootstrap ClassLoader)
用来加载 Java 核心类库,无法被 Java 程序直接引用。
2、 展类加载器(extensions class loader):
它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
3、 统类加载器(system class loader):
它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
4、 户自定义类加载器
通过继承 java.lang.ClassLoader 类的方式实现。
5、AQS支持两种同步方式:
1、 独占式
2、 共享式
这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。
6、线程的状态流转图
线程的生命周期及五种基本状态:
7、并发编程有什么缺点
并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、线程安全、死锁等问题。
8、synchronized 和 volatile 的区别是什么?
1、 synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
2、 volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
区别
1、 volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
2、 volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
3、 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
4、 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
5、 volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
9、什么是线程组,为什么在Java中不推荐使用?
线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
10、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
1、 线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
2、 时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
3、 线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。