jvm内存区域划分是怎样的?
JVM内存区域划分主要分为以下几个部分:
- 程序计数器:每个线程都有一个独立的程序计数器,用于指示下一条指令的位置,属于线程私有的内存区域,不会出现内存溢出的情况。
- Java虚拟机栈:用于存储Java方法执行的内存模型。每个方法在执行时都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等信息。线程私有的内存区域,可以动态扩展,但是如果无法扩展,就会抛出StackOverflowError异常。
- 本地方法栈:与Java虚拟机栈类似,只不过是为Native方法服务的。
- Java堆:用于存储Java对象的内存区域。可以分为新生代、老年代、永久代或元空间。
- 新生代:用于存储新创建的对象,分为Eden区和两个Survivor区。当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,当Survivor区满时,会将存活的对象移动到另一个Survivor区(如果另一个Survivor区也满了,则通过分配担保将对象放入老年代),如果经过多次GC后还存活的对象会移入到老年代。
- 老年代:用于存储长期存活的对象,存储在老年代的对象,一般都是由多次Minor GC所存活的对象,当老年代满时,会触发Full GC。
- 永久代或元空间:用于存储Java类的元信息,比如类名、访问修饰符、字段描述符、方法描述符等,不会存储Java对象,而是存储Java类的元信息,因此不会触发垃圾回收。
- 方法区:用于存储已被加载的类的信息、常量、静态变量、即时编译器编译后的代码等。JDK1.8之前的版本中,方法区是永久代,JDK1.8之后修改为元空间。方法区与Java堆一样,也是各个线程共享的内存区域,会抛出OutOfMemoryError异常。
GC垃圾回收基本原理
构建一个实例对象,并通过P1引用指向这个对象
1)当p1引用不再指向构建的Point对象时,此对象会被GC系统认为是垃圾对象。
2)当JVM系统触发了GC操作时,对象可能会被回收。
3)此对象会被JVM中的垃圾回收系统(GC系统)进行回收。(释放内存)
触发GC操作(GC系统触发时会对内存中的对象进行可达性分析,就是检测是否还可以通过引用访问到此对象,假如不能通过任何引用访问此对象,这个对象就会被标识垃圾)
几种常见的垃圾回收器的特性
- CMS垃圾回收器是多线程、基于标记清除算法的垃圾回收器,为老年代设计,追求最短停顿时间。主要有四个步骤:初始标记、并发标记、重新标记、并发清除。不会暂停用户工作线程。重要参数包括:-XX:+UseConcMarkSweepGC(启用CMS垃圾回收器)、-XX:CMSInitiatingOccupancyFraction(指定老年代使用率达到多少时开始CMS收集周期)、-XX:+UseCMSInitiatingOccupancyOnly(只使用CMSInitiatingOccupancyFraction,不自动调整)等。
- G1垃圾回收器将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾,收集价值最大的区域。相比CMS不会产生内存碎片,并且可精确控制停顿时间。分为四个阶段:初始标记、并发标记、最终标记、筛选回收。重要参数包括:-XX:+UseG1GC(启用G1垃圾回收器)、-XX:G1HeapRegionSize(指定Region大小,默认为堆内存的1/2048)、-XX:MaxGCPauseMillis(最大GC停顿时间)、-XX:G1NewSizePercent(新生代最小占用堆内存比例)、-XX:G1MaxNewSizePercent(新生代最大占用堆内存比例)等。
- Serial垃圾回收器是单线程、基于复制算法的垃圾回收器,适用于小内存的情况。Parallel垃圾回收器是多线程、基于复制算法的垃圾回收器,适用于大数据量的情况。Parallel Old垃圾回收器是多线程、基于标记整理算法的垃圾回收器,适用于老年代的回收。重要参数包括:-XX:+UseSerialGC(启用Serial垃圾回收器)、-XX:+UseParallelGC(启用Parallel垃圾回收器)、-XX:+UseParallelOldGC(启用Parallel Old垃圾回收器)等。
重点了解CMS(或G1)以及一些重要的参数
CMS垃圾回收器
该垃圾回收器的特点:多线程,基于标记清除算法,为老年代设计,追求最短停顿时间。主要有四个步骤:初始标记、并发标记、重新标记、并发清除。不会暂停用户工作线程。
全称为Concurrent mark sweep,该回收器是专门为老年代设计的,主要追求的是最短的停顿时间,采用的标记清除算法。四个主要工作阶段的内容为:
初始标记:标记一下GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。(这里简要回顾下哪些对象可以作为GC Roots:java虚拟机栈中的引用对象、方法区中的常量引用对象、方法区中的静态变量引用的对象、本地方法栈中一般Native方法引用的对象)
并发标记:进行GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
并发清除:清除GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以
总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
G1垃圾回收器
该垃圾回收器的主要特点:将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾,收集价值最大的区域。相比CMS不会产生内存碎片,并且可精确控制停顿时间。分为四个阶段:初始标记、并发标记、最终标记、筛选回收
该垃圾回收器看似和CMS工作流程差不多。采用的都是标记清除算法,但是本质上还是存在差别,尤其是将堆内存分为大小固定的几个区域,并且维持了一个优先列表,选取其中最有价值的回收垃圾。
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS 收集器,G1 收集器两个最突出的改进是:
1.基于标记-整理算法,不产生内存碎片。
2.可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1 收集器可以在有限时间获得最高的垃圾收几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1 收集器可以在有限时间获得最高的垃圾收集效率。
CMS GC回收分为哪几个阶段?分别做了什么事情?
CMS GC回收分为4个阶段:
初始标记:在这个阶段,GC线程会遍历GC Roots,标记可达的对象,并暂停所有的工作线程。
并发标记:在这个阶段,GC线程会遍历标记出来的对象,标记这些对象引用的对象,并与应用线程并发执行,不会暂停工作线程。
重新标记:在这个阶段,GC线程会暂停所有的工作线程,标记在并发标记阶段中因应用线程继续执行而产生变化的可达对象。
并发清理:在这个阶段,GC线程会遍历标记出来的对象,清除未标记的对象,并与应用线程并发执行,不会暂停工作线程。
CMS有哪些重要参数?
XX:CMSInitiatingOccupancyFraction:指定CMS启动垃圾收集的阈值,即老年代使用率达到多少时启动CMS垃圾收集,范围是0~100。
XX:+UseCMSInitiatingOccupancyOnly:该参数为true时,只使用CMSInitiatingOccupancyFraction 参数,不再根据系统运行情况动态调整阈值。
XX:+UseConcMarkSweepGC:启用CMS垃圾收集器。
XX:+CMSParallelRemarkEnabled:启用多线程标记。
XX:+CMSClassUnloadingEnabled:启用CMS垃圾收集器对类的回收。
XX:CMSFullGCsBeforeCompaction:设定在执行多少次不压缩的 Full GC 后,进行一次压缩。默认是0,即每次Full GC都会压缩老年代。
Concurrent Model Failure和ParNew promotion failed什么情况下会发生?
Concurrent Mode Failure:当CMS GC 工作期间,老年代空间不足,将会触发Full GC,由于Full GC 会停止所有应用线程,因此会导致长时间的停顿。因此CMS GC 为了避免Full GC 的发生,会采用一种叫做CMS-concurrent-mode-failure 的机制,当CMS 工作期间预留的空间不能满足老年代对象晋升的需要时,会触发CMS-concurrent-mode-failure,此时JVM 会启动后备预案,将当前的CMS GC 切换为Serial Old GC,由于Serial Old GC 会停止应用线程,因此可能会导致较长时间的停顿。
ParNew promotion failed:当ParNew GC 工作时,新生代空间不足,对象无法晋升到老年代,此时会触发Full GC。产生原因有两种,一种是新生代空间不足,此时需要手工调整新生代空间大小;另一种是老年代空间不足,此时需要手工减少老年代空间大小。
CMS的优缺点?
CMS垃圾回收器的优点在于其并发性,可以让用户线程在垃圾回收的过程中继续执行而不需要暂停,尽量减少了垃圾回收的停顿时间。此外,CMS垃圾回收器是基于标记清除算法的,相比于其他垃圾回收器来说,内存回收的效率更高。
缺点在于CMS垃圾回收器在执行垃圾回收时会产生内存碎片,这会导致在某些情况下产生大量的碎片化空间,最终可能会导致OutOfMemoryError异常。此外,由于CMS垃圾回收器是并发的,垃圾回收器需要消耗一定的CPU资源,这可能会影响应用程序的性能。
有做过哪些GC调优?
GC调优的主要目的是降低垃圾回收对系统性能的影响,具体方法如下:
对于老年代的垃圾回收,使用CMS(Concurrent Mark Sweep)或G1(Garbage First)回收器,这两种回收器都是为了减少垃圾回收对系统性能的影响而设计的。
调整新生代大小,一般来说,新生代占用的内存越大,垃圾回收次数就越少,但是每次回收的时间就越长,反之亦然。
调整垃圾回收线程数,可以根据系统的CPU核心数来设置垃圾回收线程数,一般来说,垃圾回收线程数不应超过CPU核心数的70%。
调整JVM参数,如设置堆的大小、设置新生代和老年代比例、设置垃圾回收器等。
使用内存分析工具,如jmap、jhat、jconsole等,分析程序的内存使用情况,找出内存泄漏等问题。
避免使用finalize方法,因为它会增加垃圾回收的时间。
避免创建过多的临时对象,因为它们会增加垃圾回收的次数。
避免使用字符串拼接操作,因为它会创建很多临时对象,增加垃圾回收的次数。
为什么要划分成年轻代和老年代?
主要是为了优化垃圾回收的效率。大多数对象都是朝生夕死的,只有一小部分对象能够存活很长时间,所以将这些对象分为不同的代,可以使用不同的回收算法和参数来优化垃圾回收的效率。新生代中的对象生命周期短,使用复制算法可以高效地回收垃圾,而老年代中的对象生命周期长,使用标记-清除或标记-整理算法可以更好地回收垃圾。
年轻代为什么被划分成eden、survivor区域?
年轻代被划分成eden和survivor区域是为了实现新生代的垃圾回收。当新对象被创建时,它们被分配到eden区域中。当eden区域满时,一个Minor GC被触发,对eden区域进行垃圾回收。在这个过程中,所有存活的对象被移动到survivor区域中。当第一次Minor GC完成后,eden区域会被清空。存活的对象会被移动到survivor区域中的一个,这个survivor区域叫做To Survivor Space。另一个survivor区域叫做From Survivor Space。在之后的新生代垃圾回收中,存活的对象会被移动到From Survivor Space,To Survivor Space会变成空的。存活时间长的对象会被移动到老年代中。
年轻代为什么采用的是复制算法?
年轻代采用复制算法的原因是由于在年轻代中,存活的对象比较少,而且对象的生命周期很短,很快就会被回收。因此采用复制算法,将内存分为两块,每次只使用其中一块,将存活的对象复制到另一块中,然后清空使用的那一块,这样就保证了内存的连续性,避免了内存碎片问题。同时,复制算法的效率也比较高,时间复杂度为O(n),相比较于标记-清除算法和标记-整理算法,复制算法的效率更高。
老年代为什么采用的是标记清除、标记整理算法
老年代采用标记清除或标记整理算法的原因是老年代中的对象通常存活时间较长,所以采用标记清除或标记整理算法可以更好地处理这些长生命周期的对象。标记清除算法通过标记出存活的对象和未标记的垃圾对象,然后清除未标记的垃圾对象。标记整理算法则通过标记存活的对象,将它们移动到一端,然后清除边界外的垃圾对象。这两种算法都可以处理老年代中的长生命周期对象,同时也可以避免在清理垃圾时对整个堆进行暂停。但是标记整理算法在进行垃圾回收时需要移动对象,因此它的效率可能会略低于标记清除算法,尤其是在老年代中。因此,在一些场景下,标记清除算法可能更适合老年代的垃圾回收。
什么情况下使用堆外内存?要注意些什么?
堆外内存是指不在JVM管理的内存空间中,一般是通过Unsafe.allocateMemory()或ByteBuffer.allocateDirect()等方法分配。通常在以下情况下使用堆外内存:
对于需要频繁创建和销毁的对象,使用堆外内存可以减少JVM内存的压力,提高Java程序的性能。
对于需要共享内存的情况,堆外内存可以更方便地在不同进程之间共享。
对于需要使用Native方法的情况,堆外内存可以更方便地与Native方法进行交互。
但是,使用堆外内存也存在一些需要注意的问题:
堆外内存的分配和释放需要手动进行,容易引起内存泄漏和安全问题。
堆外内存不能被JVM自动进行垃圾回收,需要手动管理内存的生命周期。
堆外内存的性能通常比JVM内存要好,但是堆外内存的访问开销会比JVM内存高,因此需要在性能和开销之间做出权衡。
堆外内存如何被回收?
堆外内存指的是JVM进程的堆外内存,它是通过使用NIO(New IO)库的DirectBuffer API来分配的。堆外内存的分配和回收都是由操作系统负责的,JVM进程不会对其进行管理。JVM进程只是持有指向这些内存块的引用,在JVM进程退出时,操作系统会回收这些内存块。如果要在JVM进程运行期间释放堆外内存,可以使用sun.misc.Cleaner类或者PhantomReference类。这些类可以在堆内存中创建一个对象,当这个对象被回收时,会调用一个回调函数来释放堆外内存。
JVM 内置的通用垃圾回收原则。堆内存划分为 Eden、Survivor 和 Tenured/Old 空间。
JVM内存区域划分主要分为以下几个部分:
程序计数器:每个线程都有一个独立的程序计数器,用于指示下一条指令的位置,属于线程私有的内存区域,不会出现内存溢出的情况。
Java虚拟机栈:用于存储Java方法执行的内存模型。每个方法在执行时都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等信息。线程私有的内存区域,可以动态扩展,但是如果无法扩展,就会抛出StackOverflowError异常。
本地方法栈:与Java虚拟机栈类似,只不过是为Native方法服务的。
Java堆:用于存储Java对象的内存区域。可以分为新生代、老年代、永久代或元空间。
新生代:用于存储新创建的对象,分为Eden区和两个Survivor区。当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,当Survivor区满时,会将存活的对象移动到另一个Survivor区(如果另一个Survivor区也满了,则通过分配担保将对象放入老年代),如果经过多次GC后还存活的对象会移入到老年代。
老年代:用于存储长期存活的对象,存储在老年代的对象,一般都是由多次Minor GC所存活的对象,当老年代满时,会触发Full GC。
永久代或元空间:用于存储Java类的元信息,比如类名、访问修饰符、字段描述符、方法描述符等,不会存储Java对象,而是存储Java类的元信息,因此不会触发垃圾回收。
方法区:用于存储已被加载的类的信息、常量、静态变量、即时编译器编译后的代码等。JDK1.8之前的版本中,方法区是永久代,JDK1.8之后修改为元空间。方法区与Java堆一样,也是各个线程共享的内存区域,会抛出OutOfMemoryError异常。
GC垃圾回收基本原理:
构建一个实例对象,并通过P1引用指向这个对象
1)当p1引用不再指向构建的Point对象时,此对象会被GC系统认为是垃圾对象。
2)当JVM系统触发了GC操作时,对象可能会被回收。
3)此对象会被JVM中的垃圾回收系统(GC系统)进行回收。(释放内存)
触发GC操作(GC系统触发时会对内存中的对象进行可达性分析,就是检测是否还可以通过引用访问到此对象,假如不能通过任何引用访问此对象,这个对象就会被标识垃圾)
几种常见的垃圾回收器的特性:
CMS垃圾回收器是多线程、基于标记清除算法的垃圾回收器,为老年代设计,追求最短停顿时间。主要有四个步骤:初始标记、并发标记、重新标记、并发清除。不会暂停用户工作线程。重要参数包括:-XX:+UseConcMarkSweepGC(启用CMS垃圾回收器)、-XX:CMSInitiatingOccupancyFraction(指定老年代使用率达到多少时开始CMS收集周期)、-XX:+UseCMSInitiatingOccupancyOnly(只使用CMSInitiatingOccupancyFraction,不自动调整)等。
G1垃圾回收器
Minor GC
JVM参数主要有⼏种分类
JVM参数主要有以下几种分类:
标准参数:所有版本都必须支持的参数。
非标准参数:特定版本才支持的参数,未来可能会取消。
高级运行时参数:用于调节虚拟机性能的参数。
适用于服务端的JVM参数:用于提高应用程序的吞吐量和性能,并减少停顿时间的参数。
适用于客户端的JVM参数:主要用于提高应用程序的响应速度和交互性的参数。
Java中会存在内存泄漏吗,简述一下。
Java 中也会存在内存泄漏的情况,一般是由于程序中的对象在使用完之后未被垃圾回收器及时回收,而导致内存无法被释放,从而引发内存泄漏。
常见导致内存泄漏的情况包括:
静态集合类引起的内存泄漏:静态集合类中的对象一旦被添加到集合中,就无法被回收,除非将对象从集合中移除,否则这些对象将一直占用内存。
对象被持有导致的内存泄漏:当一个对象被持有时,如果持有它的对象没有被释放,则这个对象也无法被释放。
未关闭资源导致的内存泄漏:例如未关闭的数据库连接或文件等,会一直占用内存,直到应用程序退出或强制关闭。
为避免内存泄漏,需要注意以下几点:
单例模式:如果使用单例模式,尽量使用弱引用或软引用,确保对象不再被使用时及时释放内存。
集合类:使用集合类时,尽量使用弱引用或软引用,确保对象不再被使用时及时释放内存。
变量作用域:变量应该定义在尽可能小的作用域中,确保对象在使用完之后及时释放内存。
资源关闭:使用完资源后,应该及时关闭,确保资源不再被占用。
Java虚拟机是如何判定两个Java类是相同的?
Java虚拟机是通过类加载器和类本身来判断两个Java类是否相同的。同一个类加载器下,若类名、包名和类加载器均相同,则认为是相同的类。同一个类加载器下,若类名和包名相同但是加载器不同,则认为是不同的类。不同的类加载器即使加载同一个.class文件,也会在内存中形成各自独立的类,相互之间不可见。因此,同一个.class文件被不同的类加载器加载时,就会形成多个不同的类,并且这些类之间是互相独立的。
Java 中都有哪些引用类型
强引用:强引用是指的是程序中普遍存在的引用,只要强引用存在,垃圾回收器永远不会回收被引用的对象。
软引用:软引用也是用来描述一些还有用但并非必需的对象。当系统内存充足时,软引用的对象不会被回收,当系统内存不足时,这些对象会被回收。
弱引用:弱引用也是用来描述非必需对象,但是它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当系统内存不足时,这些对象会被回收。
虚引用:虚引用是最弱的一种引用关系,虚引用关联的对象随时可能被垃圾回收器回收,当试图通过虚引用的get()方法取得对象时,总是返回null。
在 Java 中,对象什么时候可以被垃圾回收?
在Java中,对象可以被垃圾回收的时机是当垃圾回收器判断该对象不再被引用时,就可以将其回收。垃圾回收器通过可达性分析算法来判断对象是否可以被回收。如果对象不再被任何引用所指向,那么该对象就可以被回收。当JVM系统触发了GC操作时,对象可能会被回收。
StackOverflow异常有没有遇到过?一般你猜测会在什么情况下被触发?
StackOverflow异常是Java虚拟机中常见的异常之一。当一个线程需要更多的栈空间,但是栈空间已经耗尽时,就会抛出StackOverflow异常。通常情况下,这会发生在递归调用过程中,如果递归调用没有结束条件或者递归层数太深,就会导致栈空间不足,从而抛出StackOverflow异常。
堆空间分哪些部分?以及如何设置各个部分?
Java堆被分为新生代、老年代和永久代或元空间。
新生代:用于存储新创建的对象,分为Eden区和两个Survivor区。当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,当Survivor区满时,会将存活的对象移动到另一个Survivor区(如果另一个Survivor区也满了,则通过分配担保将对象放入老年代),如果经过多次GC后还存活的对象会移入到老年代。
老年代:用于存储长期存活的对象,存储在老年代的对象,一般都是由多次Minor GC所存活的对象,当老年代满时,会触发Full GC。
永久代或元空间:用于存储Java类的元信息,比如类名、访问修饰符、字段描述符、方法描述符等,不会存储Java对象,而是存储Java类的元信息,因此不会触发垃圾回收。
可以通过设置不同的参数来配置Java堆的大小和新生代和老年代的比例。一些重要的参数包括:
Xms:Java堆的初始大小
Xmx:Java堆的最大大小
XX:NewRatio:新生代和老年代的比例
XX:SurvivorRatio:Eden区和Survivor区的比例
XX:MaxPermSize(JDK1.7之前)或-XX:MaxMetaspaceSize(JDK1.8之后):永久代或元空间的最大大小
什么是栈帧?栈帧存储了什么?
栈帧指的是虚拟机栈中的一个元素,每当一个方法被调用时,就会在虚拟机栈中创建一个栈帧,用于存储该方法的局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表用于存放方法参数和方法内部定义的局部变量,操作数栈用于存放计算结果,动态链接用于连接该方法所属的类与其父类,方法出口指向该方法执行完后的返回地址。栈帧是栈的基本单位,多个栈帧构成了方法调用栈。
如何设置参数生成GC日志?
设置GC日志的参数如下:
-verbose:gc -Xloggc:<log_file_path>
其中,<log_file_path> 是生成日志文件的路径和文件名。
例如,要将GC日志记录到 /var/log/gc.log 文件中,可以使用以下参数:
-verbose:gc -Xloggc:/var/log/gc.log
GC 是什么?为什么要有 GC?
GC是垃圾回收(Garbage Collection)的缩写,是一种自动内存管理机制。在Java程序运行期间,程序不断地创建对象,如果程序不进行垃圾回收,程序将会一直占用内存,直到内存耗尽,导致程序崩溃。因此,垃圾回收是Java程序中必不可少的一部分。
在Java中,垃圾回收器会自动回收不再使用的对象,并释放内存空间。它会定期检查内存中的对象是否有被引用,如果没有被引用,就会将其回收。这样,程序就不需要手动管理内存,避免了内存泄漏和空指针异常等问题。
垃圾回收器会在运行时自动回收不用的对象,但是它会占用一定的系统资源,可能会导致一定的性能损失。因此,垃圾回收器的实现需要权衡内存使用和性能之间的平衡,以达到最优的效果。
使用过哪些jdk命令,并说明各个的作用是什么
jps
作用:查看所有Java进程的进程ID以及进程名
jstat
作用:监视虚拟机各种运行状态信息
参数:
class:显示加载class的数量、大小、类加载信息等
gc:显示gc相关的堆信息
compiler:显示JIT编译器编译过的方法信息
jmap
作用:生成堆转储快照
参数:
heap:显示堆的详细信息
histo:显示堆中对象的统计信息
dump:生成堆转储快照
jstack
作用:生成虚拟机当前时刻的线程快照
jcmd
作用:向正在运行的 Java 进程发送诊断命令,可以修改 JVM 配置参数,甚至可以执行 Java 程序的一些基本操作
jinfo
作用:实时查看和调整虚拟机各项参数
jhat
作用:分析 heap dump 文件,查看对象数量、分布情况、实例引用关系等
jconsole
作用:Java监视和管理控制台程序
JVM运行时数据区区域分为哪⼏部分?
JVM运行时数据区区域分为五部分:
程序计数器
Java虚拟机栈
本地方法栈
Java堆
方法区
GC垃圾回收基本原理:
当JVM系统触发了GC操作时,会对内存中的对象进行可达性分析,检测是否还可以通过引用访问到此对象。如果不能通过任何引用访问此对象,这个对象就会被标记为垃圾。然后,JVM中的垃圾回收系统会进行回收并释放内存。
几种常见的垃圾回收器的特性:
CMS垃圾回收器是多线程、基于标记清除算法的垃圾回收器,主要为老年代设计,追求最短停顿时间。重要参数包括:-XX:+UseConcMarkSweepGC、-XX:CMSInitiatingOccupancyFraction、-XX:+UseCMSInitiatingOccupancyOnly等。
G1垃圾回收器将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾,收集价值最大的区域。分为四个阶段:初始标记、并发标记、最终标记、筛选回收。重要参数包括:-XX:+UseG1GC、-XX:G1HeapRegionSize、-XX:MaxGCPauseMillis、-XX:G1NewSizePercent等。
Serial垃圾回收器是单线程、基于复制算法的垃圾回收器,适用于小内存的情况。Parallel垃圾回收器是多线程、基于复制算法的垃圾回收器,适用于大数据量的情况。Parallel Old垃圾回收器是多线程、基于标记整理算法的垃圾回收器,适用于老年代的回收。重要参数包括:-XX:+UseSerialGC、-XX:+UseParallelGC、-XX:+UseParallelOldGC等。
重点了解CMS(或G1)以及一些重要的参数:
CMS垃圾回收器采用标记清除算法,为老年代设计,追求最短停顿时间。主要有四个步骤:初始标记、并发标记、重新标记、并发清除。重要参数包括:-XX:+UseConcMarkSweepGC、-XX:CMSInitiatingOccupancyFraction、-XX:+UseCMSInitiatingOccupancyOnly等。
G1垃圾回收器将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾,收集价值最大的区域。分为四个阶段:初始标记、并发标记、最终标记、筛选回收。重要参数包括:-XX:+UseG1GC、-XX:G1HeapRegionSize、-XX:MaxGCPauseMillis、-XX:G1NewSizePercent等。
是否了解类加载器双亲委派模型机制和破坏双亲委派模型?
Java中的类加载器采用了双亲委派模型,这是一种特殊的层次结构,用于解决类的依赖性问题。当一个类加载器需要加载某个类时,它首先会将此请求委派给父类加载器,由父类加载器向上搜索类路径,如果父类加载器无法加载该类,则会将请求传递回子类加载器,由子类加载器尝试加载该类。这样可以确保相同名称的类只会被加载一次,同时保证类的安全性。但是,有时候需要破坏双亲委派模型,例如在某些特殊情况下需要在运行时动态加载类,此时可以使用自定义的类加载器来加载类。自定义的类加载器可以绕过双亲委派模型,直接加载类,从而实现灵活的类加载机制。一些框架和应用服务器也会破坏双亲委派模型,例如OSGi和Tomcat。在OSGi中,每个Bundle都有自己的类加载器,不同的Bundle可以使用不同的类库版本。在Tomcat中,每个Web应用都使用自己的类加载器,从而实现了隔离和保护。
逃逸分析有几种类型?
逃逸分析有两种类型:全局逃逸和本地逃逸。全局逃逸是指对象被外部方法引用,本地逃逸是指对象只被内部方法引用,但是由于外部方法传递了对象的引用给其他方法或线程,导致对象逃逸到了方法或线程之外。
Xms这些参数的含义是什么?
Xms是指JVM启动时初始堆内存大小,Xmx是指堆内存最大值。JVM在启动时会分配Xms指定大小的内存,而且不会扩展到指定大小之外。如果应用需要更多的内存,JVM会自动扩展堆内存大小,直到达到Xmx的最大值。如果内存不足导致无法分配堆内存,JVM会抛出OutOfMemoryError异常。
JVM的内存结构,Eden和Survivor比例是多少?
JVM的内存结构分为以下几个部分:
程序计数器:每个线程都有一个独立的程序计数器,用于指示下一条指令的位置,属于线程私有的内存区域,不会出现内存溢出的情况。
Java虚拟机栈:用于存储Java方法执行的内存模型。每个方法在执行时都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等信息。线程私有的内存区域,可以动态扩展,但是如果无法扩展,就会抛出StackOverflowError异常。
本地方法栈:与Java虚拟机栈类似,只不过是为Native方法服务的。
Java堆:用于存储Java对象的内存区域。可以分为新生代、老年代、元空间或永久代。
新生代:用于存储新创建的对象,分为Eden区和两个Survivor区。Eden区和Survivor区的比例通常为8:1:1。当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,当Survivor区满时,会将存活的对象移动到另一个Survivor区(如果另一个Survivor区也满了,则通过分配担保将对象放入老年代),如果经过多次GC后还存活的对象会移入到老年代。
老年代:用于存储长期存活的对象,存储在老年代的对象,一般都是由多次Minor GC所存活的对象,当老年代满时,会触发Full GC。
元空间或永久代:用于存储Java类的元信息,比如类名、访问修饰符、字段描述符、方法描述符等,不会存储Java对象,而是存储Java类的元信息,因此不会触发垃圾回收。
方法区:用于存储已被加载的类的信息、常量、静态变量、即时编译器编译后的代码等。JDK1.8之前的版本中,方法区是永久代,JDK1.8之后修改为元空间。方法区与Java堆一样,也是各个线程共享的内存区域,会抛出OutOfMemoryError异常。
Eden和Survivor区的比例通常为8:1:1,即Eden区占整个新生代的80%,剩下的20%平均分配到两个Survivor区中。
内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决
内存溢出(OOM)和堆栈溢出(SOE)是Java开发中常见的问题。
内存溢出通常是由于程序尝试申请超出JVM内存限制的内存时发生的。这可能是因为程序中存在内存泄漏或内存过度使用的情况。例如,如果程序中有一个无限循环,每次迭代都会创建一个新的对象并将其添加到列表中,最终会使内存耗尽,导致OOM错误。要解决这个问题,可以使用Java虚拟机的一些工具,如jmap和jstat,来检查哪些对象占用了大量内存,以及调整JVM内存限制。
堆栈溢出通常是由于程序递归调用过多或函数调用栈过深而发生的。例如,如果程序中有一个递归函数,每次递归都会在堆栈中创建一个新的帧,当递归次数过多时,堆栈就会耗尽,导致SOE错误。要解决这个问题,可以通过减少递归次数或者优化递归算法来避免堆栈溢出。
在排查内存溢出和堆栈溢出时,可以使用Java虚拟机的一些工具来诊断问题。例如,使用jmap和jstat命令来监视内存使用情况,使用jstack命令来分析线程堆栈信息。在排查问题时,可以通过查看堆栈信息确定哪个方法导致了溢出,并尝试通过增加内存或者优化代码来解决问题。
常见的GC回收算法及其含义
常见的垃圾回收算法包括:
标记-清除算法:首先标记不再使用的对象,然后清除它们。这个算法的主要问题是容易产生内存碎片。
复制算法:将存活对象复制到一个新的内存区域,并将原始内存区域清空。这个算法的主要问题是需要两倍的内存空间。
标记-整理算法:首先标记不再使用的对象,然后将存活对象移动到内存区域的一端,然后清除其余部分。这个算法的主要问题是需要移动对象,因此可能会影响性能。
常见的垃圾回收器包括:
Serial垃圾回收器:单线程、基于复制算法,适用于小内存。
Parallel垃圾回收器:多线程、基于复制算法,适用于大数据量。
Parallel Old垃圾回收器:多线程、基于标记整理算法,适用于老年代。
CMS垃圾回收器:多线程、基于标记清除算法,为老年代设计,追求最短停顿时间。
G1垃圾回收器:将堆内存分为几个大小固定的独立区域,在后台维护了一个优先列表,根据允许的收集时间回收垃圾,收集价值最大的区域。相比CMS不会产生内存碎片,并且可精确控制停顿时间。
常见的垃圾回收器参数包括:
XX:+UseConcMarkSweepGC:启用CMS垃圾回收器。
XX:CMSInitiatingOccupancyFraction:指定老年代使用率达到多少时开始CMS收集周期。
XX:+UseCMSInitiatingOccupancyOnly:只使用CMSInitiatingOccupancyFraction,不自动调整。
XX:+UseG1GC:启用G1垃圾回收器。
XX:G1HeapRegionSize:指定Region大小,默认为堆内存的1/2048。
XX:MaxGCPauseMillis:最大GC停顿时间。
XX:G1NewSizePercent:新生代最小占用堆内存比例。
XX:G1MaxNewSizePercent:新生代最大占用堆内存比例。
常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等
JVM性能调优
JVM 的性能调优是指通过调整 JVM 内存分配、垃圾回收器、线程池等参数,来提高 JVM 的性能。
JVM 内存分配:JVM 内存分为堆内存和非堆内存两部分。堆内存中又分为新生代和老年代。可以通过 -Xmx 和 -Xms 参数来调整 JVM 的内存分配。
垃圾回收器:JVM 支持多种垃圾回收器,并且每种垃圾回收器都有不同的优缺点。可以根据具体应用场景来选择不同的垃圾回收器。常见的垃圾回收器有 CMS、G1、Serial、Parallel 等。
线程池:JVM 中的线程池可以通过 -Xss 参数来调整。减小线程栈的大小可以增加线程数,从而提高 JVM 的并发性能。
其他参数:还有很多其他的 JVM 参数可以调整,例如 -XX:+UseCompressedOops、-XX:MaxPermSize、-XX:ReservedCodeCacheSize 等。具体使用哪些参数需要结合具体的应用场景来决定。
在进行 JVM 性能调优时,需要注意以下几点:
需要有一定的 JVM 基础知识,了解 JVM 的内存结构、垃圾回收算法等。
需要有一定的性能测试经验,可以使用一些性能测试工具来测试 JVM 的性能指标。
需要根据具体的应用场景来进行调优,不同的应用场景可能需要采用不同的调优策略。
需要进行反复测试和验证,以确保调优效果符合预期。
类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的
类加载的过程:加载、验证、准备、解析、初始化
类加载的过程分为五个阶段:
加载(Loading):查找并加载类的二进制数据。
验证(Verification):验证加载的类是否符合JVM规范,例如检查类文件的版本是否正确,是否有非法的访问修饰符等。
准备(Preparation):为类的静态变量分配内存,并将其初始化为默认值,例如int类型的默认值为0,对象引用类型的默认值为null。
解析(Resolution):将符号引用转换为直接引用,例如将类名转换为类的实际地址,常量池中的符号引用转换为实际的内存地址等。解析通常是在初始化阶段之前完成的,但也可能在初始化阶段之后进行。
初始化(Initialization):执行类的初始化代码,包括静态变量的显式赋值和静态代码块中的代码。
Java内存模型JMM
Java内存模型(JMM)是一组规则,用于确定在多线程环境下,各个线程如何与主内存中的变量进行交互。JMM定义了线程和主内存之间的抽象关系,以及线程之间的交互方式。JMM确保在多线程环境下,共享变量的访问能够正确地进行同步和互斥。JMM的重要特征包括:原子性、可见性、有序性。在Java程序中,使用synchronized、volatile、final等关键字来确保并发线程的正确性。
说一下 Jvm 的主要组成部分?及其作用?
JVM 的主要组成部分包括:程序计数器、虚拟机栈、本地方法栈、Java 堆、方法区。各个部分的作用分别如下:
程序计数器:每个线程都有一个独立的程序计数器,用于指示下一条指令的位置,属于线程私有的内存区域,不会出现内存溢出的情况。
Java 虚拟机栈:用于存储 Java 方法执行的内存模型。每个方法在执行时都会创建一个栈帧,栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等信息。线程私有的内存区域,可以动态扩展,但是如果无法扩展,就会抛出 StackOverflowError 异常。
本地方法栈:与 Java 虚拟机栈类似,只不过是为 Native 方法服务的。
Java 堆:用于存储 Java 对象的内存区域。可以分为新生代、老年代、永久代或元空间。
新生代:用于存储新创建的对象,分为 Eden 区和两个 Survivor 区。当 Eden 区满时,会触发 Minor GC,将存活的对象移动到 Survivor 区,当 Survivor 区满时,会将存活的对象移动到另一个 Survivor 区(如果另一个 Survivor 区也满了,则通过分配担保将对象放入老年代),如果经过多次 GC 后还存活的对象会移入到老年代。
老年代:用于存储长期存活的对象,存储在老年代的对象,一般都是由多次 Minor GC 所存活的对象,当老年代满时,会触发 Full GC。
永久代或元空间:用于存储 Java 类的元信息,比如类名、访问修饰符、字段描述符、方法描述符等,不会存储 Java 对象,而是存储 Java 类的元信息,因此不会触发垃圾回收。
方法区:用于存储已被加载的类的信息、常量、静态变量、即时编译器编译后的代码等。JDK1.8 之前的版本中,方法区是永久代,JDK1.8 之后修改为元空间。方法区与 Java 堆一样,也是各个线程共享的内存区域,会抛出 OutOfMemoryError 异常。
说一下堆栈的区别?
堆和栈都是用来存储数据的,二者的区别在于数据的存储方式和作用范围。堆是用来存储对象的,存储方式是动态分配的,对象的生命周期由垃圾回收器来管理。在堆中存储的对象,可以被多个线程共享。栈是用来存储局部变量的,存储方式是静态分配的,变量的生命周期由它所在的方法的调用结束来决定。栈的作用范围只在当前线程内部,不同线程的栈不共享。
队列和栈是什么?有什么区别?
队列和栈都是数据结构中的基本概念,它们都可以用来存储元素,但是它们之间有很大的区别。
队列是一种先进先出(FIFO)的数据结构,它只允许在队列的一端插入元素,在另一端删除元素。向队列中插入元素的操作称为入队(enqueue),删除元素的操作称为出队(dequeue)。队列可以用来实现广度优先搜索(BFS)等算法。
栈是一种后进先出(LIFO)的数据结构,它只允许在栈顶插入和删除元素。向栈中插入元素的操作称为入栈(push),删除元素的操作称为出栈(pop)。栈可以用来实现深度优先搜索(DFS)等算法。
总之,队列和栈都是非常基础的数据结构,它们各自适用于不同的场景。队列适用于先进先出的场景,栈适用于后进先出的场景。
什么是双亲委派模型?
双亲委派模型是一种类加载器的工作机制。它的基本思想是:如果一个类加载器收到了类加载的请求,它首先将请求委派给父类加载器,依次递归,直到最上层的父类加载器无法处理该请求,才自己尝试加载类。这样可以避免重复加载,同时保证类的唯一性。
新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代垃圾回收器和老年代垃圾回收器是JVM内存回收机制的两个重要部分。新生代垃圾回收器主要用于回收新创建的对象,而老年代垃圾回收器主要用于回收长期存活的对象。
新生代垃圾回收器包括Serial、ParNew和G1,其中Serial和ParNew采用的是复制算法,而G1采用的是标记整理算法。在新生代中,对象被分为Eden区和两个Survivor区。当Eden区满时,会触发Minor GC,将存活的对象移动到Survivor区,当Survivor区满时,会将存活的对象移动到另一个Survivor区(如果另一个Survivor区也满了,则通过分配担保将对象放入老年代),如果经过多次GC后还存活的对象会移入到老年代。
老年代垃圾回收器包括Serial Old、Parallel Old和CMS,其中Serial Old和Parallel Old采用的是标记整理算法,而CMS采用的是标记清除算法。老年代中的对象一般都是由多次Minor GC所存活的对象,当老年代满时,会触发Full GC。
总的来说,新生代和老年代的垃圾回收器在回收对象的生命周期和采用的算法上存在差异,这些差异导致了它们在性能和效率上的差异,需要根据具体场景选择合适的垃圾回收器来提高应用的性能和响应速度。
简述分代垃圾回收器是怎么工作的?
分代垃圾回收器是根据对象存活时间不同将内存分为不同的区域,每个区域采用不同的垃圾回收算法。内存划分为年轻代和老年代两个区域,年轻代又分为Eden区和两个Survivor区。通常,新生的对象被分配在Eden区,在Minor GC时,将Eden区和Survivor区中存活的对象复制到另一个Survivor区。当Survivor区满时,会将存活的对象移动到老年代。老年代中存活的对象则会在Full GC时得到回收。分代垃圾回收器可以根据对象的存活时间采用不同的垃圾回收算法,提高垃圾回收的效率。