答案: Java虚拟机(JVM)是Java平台的一部分,是一个虚拟计算机,负责在运行时执行Java字节码。它提供了Java程序运行的环境,包括内存管理、垃圾回收、即时编译等功能,使得Java程序可以在不同的平台上实现一次编写,到处运行的特性。
答案: Java内存模型是一种规范,定义了Java虚拟机如何协调多线程访问共享内存的规则。它确保线程之间的可见性、有序性和一致性。JMM包括主内存和每个线程的工作内存,通过内存屏障和同步操作来协调线程之间的交互。这是为了保证在多线程环境中程序的正确性。
答案: 垃圾回收是指自动识别和释放不再被程序使用的内存的过程。在Java中,垃圾回收器负责管理和清理不再被引用的对象。常见的垃圾回收算法有以下几点:
答案: Java堆是Java虚拟机管理的最大一块内存区域,用于存储对象实例。栈则是每个线程私有的,用于存储局部变量和方法调用信息。主要区别在于,堆是用于存储对象的动态分配内存区域,而栈是用于存储基本数据类型和对象引用的内存区域。堆的内存空间由垃圾回收器负责管理,而栈的内存空间由线程自动管理。
答案: 类加载器是Java虚拟机的一部分,负责将类的字节码加载到内存中,并转换成Java对象。常见的类加载器有启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用程序类加载器(Application ClassLoader)和自定义类加载器。这些类加载器形成了层次结构,按照双亲委派模型,父类加载器负责委托给子类加载器进行类加载。
答:垃圾回收的主要目的是:
答:Java9之后,官方JDK默认使用的垃圾收集器是G1。
常见的垃圾收集器包括:
类装载分为以下 5 个步骤:
答:
常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。
答:栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。
答Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo
答:新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC
答:当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位 的 OOP。
答:在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。
类加载器分类:
启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
其他类加载器:
扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
答:JVM性能调优的主要目标包括:
答:选择垃圾回收器应考虑以下因素:
答:可以使用-Xmx和-Xms标志来调整堆大小。其中:
调整堆大小的目标是确保堆足够大,以容纳应用程序的内存需求,同时避免浪费过多内存。
答:G1 GC是一种面向大堆内存的垃圾回收器,其工作原理包括以下步骤:
答:理论上说上 32 位的 JVM 堆内存可以到达 2^32, 即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5GB,Solaris 大约3GB。64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。
JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java编译器,它也包含 JRE。
JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。
JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。
答:截止目前(2020年3月),JVM可配置参数已经达到1000多个,其中GC和内存配置相关的JVM参数就有600多个。但在绝大部分业务场景下,常用的JVM配置参数也就10来个。
例如:
# JVM启动参数不换行
# 设置堆内存
‐Xmx4g ‐Xms4g
# 指定GC算法
‐XX:+UseG1GC ‐XX:MaxGCPauseMillis=50
# 指定GC并行线程数
‐XX:ParallelGCThreads=4
# 打印GC日志
‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps
# 指定GC日志文件
‐Xloggc:gc.log
# 指定Meta区的最大值
‐XX:MaxMetaspaceSize=2g
# 设置单个线程栈的大小
‐Xss1m
# 指定堆内存溢出时自动进行Dump
‐XX:+HeapDumpOnOutOfMemoryError
‐XX:HeapDumpPath=/usr/local/
此外,还有一些常用的属性配置:
# 指定默认的连接超时时间
‐Dsun.net.client.defaultConnectTimeout=2000
‐Dsun.net.client.defaultReadTimeout=2000
# 指定时区
‐Duser.timezone=GMT+08
# 设置默认的文件编码为UTF‐8
‐Dfile.encoding=UTF‐8
# 指定随机数熵源(Entropy Source)
‐Djava.security.egd=file:/dev/./urandom
答:需要根据系统的配置来确定,要给操作系统和JVM本身留下一定的剩余空间。推荐配置系统或容器里可用内存的 70-80% 最好。
比如说系统有 8G 物理内存,系统自己可能会用掉一点,大概还有 7.5G 可以用,那么建议配置‐Xmx6g
说明:7.5G*0.8 = 6G,如果知道系统里有明确使用堆外内存的地方,还需要进一步降低这个值。
答:一般来说,JDK8及以下版本通过以下参数来开启GC日志:
‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐Xloggc:
如果是在JDK9及以上的版本,则格式略有不同
‐Xlog:gc*=info:file=gc.log:time:filecount=0
java ‐XX:+UseG1GC
‐Xms4g
‐Xmx4g
‐Xloggc:gc.log
‐XX:+PrintGCDetails
‐XX:+PrintGCDateStamps HelloWorld
答:内存溢出错误(OutOfMemoryError)是一种Java程序运行时错误,表示程序试图分配更多内存,但没有足够的内存可供使用。内存溢出错误是由于程序中创建的对象太多,而Java虚拟机(JVM)的堆内存不足以容纳这些对象而引起的。常见的内存溢出错误类型包括:
答:分析线程转储(Thread Dump)是诊断多线程应用程序问题的关键步骤之一。以下是一般的线程转储分析步骤:获取线程转储:在应用程序运行时,可以使用工具(如jstack命令,VisualVM等)来生成线程转储。通常,您可以通过以下方式获取线程转储:
使用jstack命令:运行jstack ,其中是目标Java进程的进程ID。
使用监控工具:如果您使用监控工具(如VisualVM、JConsole等),通常可以通过工具界面生成线程转储。
在发生问题时生成线程转储:如果应用程序出现性能问题或死锁,您可以使用操作系统工具来生成线程转储,例如在Unix/Linux系统上可以使用kill -3 命令。
查看线程状态:打开生成的线程转储文件(通常是文本文件),查看线程列表。了解每个线程的状态,例如RUNNABLE(运行中)、WAITING(等待中)、BLOCKED(阻塞中)等。
分析堆栈跟踪:对于每个线程,查看其堆栈跟踪以了解线程执行的代码路径。堆栈跟踪通常包含了方法和类名,帮助您确定问题代码的位置。
查找共享资源和锁信息:如果线程转储包含锁信息,查看哪些线程正在等待获取哪些锁,以及哪些线程已经持有锁。这有助于识别死锁或资源争用问题。
识别问题线程:找出占用CPU高或处于异常状态的线程,以及哪些线程可能导致了性能问题或死锁。
使用性能分析工具:如果需要更深入的分析,您可以使用性能分析工具(如VisualVM、JProfiler、YourKit等)来可视化和交互式地分析线程转储数据。这些工具通常提供更强大的分析功能,有助于更轻松地识别问题。
解决问题:根据分析的结果,采取必要的措施来解决线程问题。这可能包括优化代码、解决死锁、减少线程争用等。
监控和预防:定期监控应用程序的线程情况,以及线程转储文件,以便在早期发现和解决问题。采取预防措施,如合理的锁策略、避免死锁、资源池管理等,以减少线程问题的发生。
线程转储分析通常需要一定的经验和技能,特别是在处理复杂的多线程应用程序时。性能分析工具可以大大简化这个过程,并提供更多的可视化信息,有助于更快地定位和解决问题。
答:CPU占用高(High CPU Usage)问题是指应用程序或进程占用了大量的CPU资源,导致系统负载升高、性能下降或系统变得不响应。要排查高CPU占用问题,可以采取以下步骤:
监控CPU使用率:使用系统监控工具(如top、htop、Windows任务管理器)来检查哪个进程或线程占用了大量的CPU资源。
查看线程转储:如果CPU占用问题与多线程应用程序相关,生成线程转储并查看问题线程的堆栈跟踪,以了解问题的代码路径。
分析代码:确定占用CPU的代码部分。查看哪些方法或循环消耗了大量的CPU时间。
检查循环:查看是否存在无限循环或非预期的循环条件,这可能导致CPU占用问题。
检查阻塞操作:查看是否存在长时间的阻塞操作,例如等待网络或文件系统操作,这可能导致CPU资源浪费。
使用性能分析工具:使用性能分析工具(如VisualVM、JProfiler、YourKit)来更详细地分析CPU占用问题。这些工具可以提供更多的可视化数据和分析功能。
优化代码:根据分析结果采取必要的措施来优化代码,减少CPU占用。这可能包括改进算法、减少不必要的计算或循环、并行化任务等。
避免不必要的轮询:如果应用程序中存在轮询机制,尽量避免过于频繁的轮询,采用事件驱动或异步机制。
增加硬件资源:如果必要,增加CPU核心数量或升级硬件来缓解CPU占用问题。
通过以上步骤,您可以确定高CPU占用问题的原因,并采取适当的措施来解决问题,从而提高应用程序的性能。