Java虚拟机问题总结

JVM参数主要有几种分类

  • 标准参数
    标准参数,顾名思义,标准参数中包括功能以及输出的结果都是很稳定的,基本上不会随着JVM版本的变化而变化。标准参数以-开头,如:java -version、java -jar等,通过java -help可以查询所有的标准
    参数。
  • 非标准参数
    非标准参数以-X开头,是标准参数的扩展。对应前⾯讲的标准化参数,这是非标准化参数。表示在将来的JVM版本中可能会发生改变,但是这类以-X开始的参数变化的⽐较小。
  • 不稳定参数
    这是我们日常开发中接触到最多的参数类型。这也是⾮标准化参数,相对来说不稳定,随着JVM版本的变化可能会发生变化,主要用于JVM调优和debug。
    不稳定参数以-XX 开头,此类参数的设置很容易引起JVM 性能上的差异,使JVM存在极大的不稳定性。如果此类参数设置合理将大大提⾼JVM的性能及稳定性。
    不稳定参数分为三类:
    • 性能参数:用于JVM的性能调优和内存分配控制,如内存大小的设置;
    • 行为参数:用于改变JVM的基础⾏为,如GC的⽅式和算法的选择;
    • 调试参数:用于监控、打印、输出jvm的信息。

Java内存泄漏

理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。
例如hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。

ThreadLocal的内存泄漏问题。

Java 中引用类型

  • 强引用:发生 gc 的时候不会被回收。 new
  • 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。SoftReference
  • 弱引用:有用但不是必须的对象,在下一次GC时会被回收。WeakReference
  • 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

对象被GC的触发条件

要真正宣告一个对象的死亡,至少需要进行两次标记:当在可达性算法标记之后,发现对象没有引用链,就被第一次标记,随后进程筛选,如果对象的finalize方法没有被重写或者finalize方法被调用过了,就进行第二次标记,这样对象就死了。如果finalize方法没被调用,虚拟机就会给对象一次重生机会。

StackOverflow触发条件

栈内存溢出,一般由栈内存的局部变量过爆了,导致内存溢出。出现在递归方法,参数个数过多,
递归过深,递归没有出口。

堆空间分哪几个部分,以及如何设置各个部分大小

Java堆被所有线程共享,在Java虚拟机启动时创建。是虚拟机管理最大的⼀块内存。
Java堆是垃圾回收的主要区域,而且主要采用分代回收算法。堆进⼀步划分主要是为了更好的回收内存
或更快的分配内存。
Java虚拟机规范的描述是:所有的对象实例以及数组都要在堆上分配。
堆内存空间在物理上可以不连续,逻辑上连续即可。
堆大小 = 新生代 + ⽼年代。

堆的大小可通过参数 –Xms(堆的初始容量)、-Xmx(堆的最大容量) 来指定。
堆内存分配:
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64。
JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。

其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为from 和 to,以示区分。
默认的,Eden : from : to = 8 : 1 : 1 。(可以通过参数 –XX:SurvivorRatio 来设定 。)即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的⼀块 Survivor 区域来为对象服务,所以⽆论什么时候,总是有⼀块Survivor 区域是空闲着的。
新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
-Xmn:⾄于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置,也就是说如果通过Xmn来配置新生代的内存大小,那么-XX:newSize = -XX:MaxnewSize = -Xmn。

什么是栈帧?栈帧存储了什么?

虚拟机栈也是线程私有,而且生命周期与线程相同,每个Java⽅法在执⾏的时候都会创建⼀个栈帧(Stack Frame)。栈帧(Stack Frame)是用于⽀持虚拟机进⾏⽅法调用和⽅法执⾏的数据结构。栈帧存储了⽅法的局部变量表、操作数栈、动态连接和⽅法返回地址等信息。每⼀个⽅法从调用⾄执⾏完成的过程,都对应着⼀个栈帧在虚拟机栈⾥从⼊栈到出栈的过程。

如何设置参数生成GC日志

设置JVM GC格式日志的主要参数包括如下8个:

  1. -XX:+PrintGC 输出简要GC日志
  2. -XX:+PrintGCDetails 输出详细GC日志
  3. -Xloggc:gc.log 输出GC日志到⽂件
  4. -XX:+PrintGCTimeStamps 输出GC的时间戳(以JVM启动到当期的总时⻓的时间戳形式)
  5. -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2020-04-26T21:53:59.234+0800)
  6. -XX:+PrintHeapAtGC 在进⾏GC的前后打印出堆的信息
  7. -verbose:gc : 在JDK 8中, -verbose:gc 是 -XX:+PrintGC ⼀个别称,日志格式等价与: -XX:+PrintGC 。不过在JDK 9中 -XX:+PrintGC被标记为deprecated。
    -verbose:gc是⼀个标准的选项,-XX:+PrintGC是⼀个实验的选项,建议使用-verbose:gc 替代 -XX:+PrintGC
  8. -XX:+PrintReferenceGC 打印年轻代各个引用的数量以及时⻓
  • 开启GC日志
    多种⽅法都能开启GC的日志功能,其中包括:使用-verbose:gc或-XX:+PrintGC这两个标志中的任意⼀个能创建基本的GC日志(这两个日志标志实际上互为别名,默认情况下的GC日志功能是关闭的)使用XX:+PrintGCDetails标志会创建更详细的GC日志推荐使用-XX:+PrintGCDetails标志(这个标志默认情况下也是关闭的);通常情况下使用基本的GC日志很难诊断垃圾回收时发生的问题。
  • 开启GC时间提示
    除了使用详细的GC日志,我们还推荐使用-XX:+PrintGCTimeStamps或者-XX:+PrintGCDateStamps,便于我们更精确地判断⼏次GC操作之间的时间。这两个参数之间的差别在于时间戳是相对于0(依据JVM启动的时间)的值,而日期戳(date stamp)是实际的日期字符串。由于日期戳需要进⾏格式化,所以它的效率可能会受轻微的影响,不过这种操作并不频繁,它造成的影响也很难被我们感知。
  • 指定GC日志路径
    默认情况下GC日志直接输出到标准输出,不过使用-Xloggc:filename标志也能修改输出到某个⽂件。除⾮显式地使用-PrintGCDetails标志,否则使用-Xloggc会⾃动地开启基本日志模式。使用日志循环(Logrotation)标志可以限制保存在GC日志中的数据量;对于需要⻓时间运⾏的服务器而⾔,这是⼀个⾮常有用的标志,否则累积⼏个⽉的数据很可能会耗尽服务器的磁盘。
  • 开启日志滚动输出
    通过-XX:+UseGCLogfileRotation -XX:NumberOfGCLogfiles=N -XX:GCLogfileSize=N标志可以控制日志⽂件的循环。默认情况下,UseGCLogfileRotation标志是关闭的。它负责打开或关闭GC日志滚动记录功能的。要求必须设置 -Xloggc参数开启UseGCLogfileRotation标志后,默认的⽂件数⽬是0(意味着不作任何限制),默认的日志⽂件大小是0(同样也是不作任何限制)。因此,为了让日志循环功能真正生效,我们必须为所有这些标志设定值。
  • 需要注意的是:
    The size of the log file at which point the log will be rotated, must be >= 8K. 设置滚动日志⽂件的大
    小,必须大于8k。当前写日志⽂件大小超过该参数值时,日志将写⼊下⼀个⽂件设置滚动日志⽂件的个数,必须大于等于1必须设置 -Xloggc 参数。

Java 虚拟机是如何判定两个 Java 类是相同

① 类的全限定名是否相等
② 类加载器是否相等

即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器ClassLoaderA和 ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。

常用的jdk命令及其作用是

  • jps
    jps:Java Virtual Machine Process Status Tool
    查看Java进程 ,相当于Linux下的ps命令,只不过它只列出Java进程。
  • jstat
    jstat:JVM Statistics Monitoring Tool
    jstat可以查看Java程序运⾏时相关信息,可以通过它查看堆信息的相关情况。
  • jinfo
    jinfo:Java Configuration Info
    jinfo可以用来查看正在运⾏的java程序的扩展参数,甚⾄⽀持运⾏时,修改部分参数。
  • jmap
    jmap:Memory Map
    jmap用来查看堆内存使用状况,⼀般结合jhat使用。
  • jstack
    jstack:Java Stack Trace,jstack是java虚拟机⾃带的⼀种堆栈跟踪⼯具。jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每⼀条线程正在执⾏的⽅法堆栈的集合,生成线程快照的主要⽬的是定位线程出现⻓时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的⻓时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core⽂件,jstack⼯具可以用来获得core⽂件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack⼯具还可以附属到正在运⾏的java程序中,看到当时运⾏的java程序的javastack和native stack的信息, 如果现在运⾏的java程序呈现hung的状态,jstack是⾮常有用的。
  • jconsole
    Jconsole:Java Monitoring and Management Console,Java 5引⼊,⼀个内置 Java 性能分析器,
    可以从命令⾏或在 GUI shell 中运⾏。您可以轻松地使用 JConsole来监控 Java 应用程序性能和跟踪Java 中的代码。

破坏双亲委派模型

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件。

逃逸分析有几种类型

逃逸分析(Escape Analysis)是⽬前Java虚拟机中⽐较前沿的优化技术。这是⼀种可以有效减少Java 程序中同步负载和内存堆分配压⼒的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出⼀个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。逃逸分析的基本⾏为就是分析对象动态作用域:当⼀个对象在⽅法中被定义后,它可能被外部⽅法所引用。

⽅法逃逸:作为调用参数传递到其他地⽅中。
线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量。

逃逸分析包括:

  • 全局变量赋值逃逸
  • ⽅法返回值逃逸
  • 实例引用发生逃逸

简单讨论垃圾收集器

  • Serial收集器:单线程的收集器,收集垃圾时,必须stoptheworld,使用标记复制算法。
  • ParNew收集器:Serial收集器的多线程版本,也需要stoptheworld,标记复制算法。
  • ParallelScavenge收集器:新生代收集器,复制算法的收集器,多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
  • SerialOld收集器:是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
  • ParallelOld收集器:是ParallelScavenge收集器的老年代版本,使用多线程,标记整理算法。
  • CMS(ConcurrentMarkSweep)收集器:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
  • G1收集器:标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收。不会产生空间碎片,可以精确地控制停顿。

CMS收集器和G1收集器的区别:

  • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
  • CMS收集器以最小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间;
  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
  • CMS无法解决浮动垃圾问题,G1则通过最终标记处理浮动垃圾。
  • G1垃圾收集时产生的内存占用和程序运行时的额外执行负载高于GMS。空间换时间。
原文地址:https://www.cnblogs.com/lippon/p/14219519.html