JVM Collections

2021年05月31日

JVM 学习总结

Java运行时数据区


1

  • 程序计数器 Program Counter Register
  • 虚拟机栈 Virtual Machine Stacks
    1

    虚拟机栈也是线程私有,而且生命周期与线程相同,每个Java方法在执行的时候都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息

  • 本地方法栈 Native Method Stacks

    本地方法栈为虚拟机使用到本地方法服务。

  • Java堆 Heap

    对大多数应用来说,Java堆(Heap)是Java虚拟机所管理的内存中最大的一块,Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。该内存区域唯一的目的就是存放对象实例,Java对象实例以及数组都在堆上分配(随着JIT编译器发展等技术成熟,所有对象分配在堆上也渐渐不是那么“绝对”了)。
    Java堆是垃圾收集器管理的主要区域,因此Java堆也常被称为“GC堆”,由于现在收集器基于分代收集算法,Java堆还可以细分为:新生代和老年代。

  • 方法区 Method Area

    方法区与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    方法区(永久代)回收性价比比较低,主要回收两部分内容:废弃常量和无用的类。

    无用的类必须满足三个条件:

    • 该类的所有实例都已经被回收,Java堆中不存在该类的任何实例。
    • 加载该类的ClassLoader已经被回收。
    • 该类的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 运行时常量池 Runtime Constant Pool

    运行时常量池是方法区的一部分,Class文件中除了有关类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
    运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,Java语言并非不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量池放入池中。

  • 直接内存 Direct Memory

    使用Native函数库直接分配的堆外内存,避免在Java堆和Native堆中来回复制数据。
    忽略直接内存,会导致各个内存区域总和大于物理内存。

对象已死么

  • 引用计数法 Reference Counting

    难以解决对象之间的相互引用问题。

  • 可达性分析 Accessibility Analysis

    起始点GC Roots;搜索走过的路径称为引用链(Reference Chain)

GC Roots 对象

  • 虚拟机栈(栈帧中本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

引用 Reference

4种引用强度依次减弱, Strong > Soft > Weak > Phantom

  • 强引用 Strong Reference

只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。

  • 软引用 Soft Reference

用来描述一些还有用非必要的对象,在将要发生内存溢出之前,将会把这些对象列入范围进入二次回收。

  • 弱引用 Weak Reference

用来描述非必须对象,被弱引用关联的对象只能生存到下次垃圾回收之前。

  • 虚引用 Phantom Reference

虚引用对原对象没有任何影响,也无法通过虚引用获得一个对象实例。
设置虚引用的唯一目的就是对象被垃圾回收时收到一个系统通知。

finalize

真正宣告一个对象死亡,至少需要经过两次标记过程。
finalize方法只会被系统调用一次。

1

垃圾收集算法

  • 标记-清除算法(Mark-Sweep)
    1. 标记:第一阶段标记出所有需要回收的对象;
    2. 清除:标记完成后统一回收被标记的对象。

      两个不足:

      1. 效率问题,标记和清除的效率都不是太高;
      2. 空间问题,标记清除后会产生大量不连续的内存碎片。空间碎片太多会导致分配较大对象,因为没有足够连续的内存空间,而提前触发另一次垃圾收集。
  • 复制算法(Copying)

    将内存分配成大小相等的两块,当一块用完时,按顺序将还存活的对象复制到另一块中,再将原来的块一次清理掉。 优点:实现简单,运行高效。 不足:将内存缩小为原来的一半,代价高昂。

  • 标记-整理算法(Mark-Compact)

    过程与标记-清除算法一样,整理阶段会将所有存活的对象想一端移动,然后直接清理掉端边界以外的内存。

  • 分代收集算法(Generational Collection)

    将不同的内存区域划分为几块。将Java堆划分为新生代和老年代,对不同的使用适合的垃圾收集算法。

    • 老年代:其中的对象存活率高,没有额外的空间进行担保,就必须使用标记-清(整)理来进行回收。
    • 新生代:其中大批对象都会朝生夕死,只有少部分存活,采用复制算法。

      新生代内存空间默认会划分为一块较大的Eden区(80%),和两块较小的Survivor区(各10%)。
      每次回收时,会将Eden区+一块Survivor区中存活的对象复制到另一块Survivor区,然后清理掉已用过的Eden区和Survivor区。
      新生代中每次的可用容量为(80%+10%),剩下的10%会被闲置浪费掉。 当Survivor区空间不足时,剩下的存活对象会通过分配担保机制进入老年代。

GC概念

  • GC停顿:可达性分析时,不可以出现对象引用不断变化的情况,所以GC时必须要停止所有java执行线程。
  • 安全点(safe point):程序只有执行到安全点时才能停顿。
  • 安全区域(safe region):指在一段代码片段中,对象引用关系不会发生变化。

JDK工具

jps - JVM Process Status

显示指定系统内所有的Hotspot虚拟机进程

  • jps -q 只输出LVMID,省略主类的名称 30764
  • jps -m 输出虚拟机进程启动时传递给主类 Main()函数的参数 30764 DemoApplication
  • jps -l 输出主类的全名,如果进程执行的是Jar包,输出Jar路径。 30764 com.example.demo.DemoApplication
  • jps -v 输出虚拟机进程启动时JVM参数 30764 DemoApplication -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always
    -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=41407 -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dspring.liveBeansView.mbeanDomain
    -Dspring.application.admin.enabled=true -javaagent:/home/xiong/opt/idea/lib/idea_rt.jar=42621:/home/xiong/opt/idea/bin -Dfile.encoding=UTF-8

jstack - Stack Trace For Java

显示虚拟机的线程快照

  • jstack -l 30764

jstat - JVM Statistics Monitoring

用于收集Hotspot各方面的运行数据,主要分三类:类装载,垃圾收集,运行期编译状况

  • jstat -class 30764 监视类装载,卸载数量,总空间以及类装载所耗费的时间。
  • jstat -gc/-gccapacity/-gcutil/-gcause 30764 监视java堆状况,GC信息。
  • jstat -compiler/printcompilation 30764 输出JIT编译的方法

jinfo - Configuration Info For Java

显示虚拟机配置信息

jmap - Memory Map For Java

生成虚拟机的内存转储快照(heapdump文件)

  • jmap -dump:format=b,file=dump.bin 30764

jhat - JVM Heap Dump Browser

用于分析heapdump文件,它会建立一个web服务,让用户可以在浏览器上查看分析结果。

  • jhat dump.bin