深入理解Java虚拟机

深入理解JVM

JVM 三大部分

  1. 类装载子系统
  2. 字节码执行引擎
  3. 运行时数据区(内存模型)

类加载过程

多个java文件经过编译打包成jar包,最终由java命令运行某个主类的main函数启动程序,这里首先需要通过类加载器把主类加载到JVM。
主类在运行过程中,如果使用到其他的类,会逐步加载这些类。
注意,jar包里的类(自己写的类)不是一次性全部加载,使用时才加载,可以通过指定参数 -verbose:class
A a = null; // 不会加载A,new的时候才加载

类加载步骤

  • 加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
  • 加载:将.class文件读入内存, 先加载main函数所在的主类,并不是同时加载所有的类,用到时才加载
  • 验证:验证字节码文件是否符合JVM规范
  • 准备:给类的静态变量(static)分配内存,并赋予默认值(如int = 0, obj = null)
  • 解析:将符合引用替换为直接引用,该阶段会把一些静态方法或私有方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是程序运行期间完成的将符号引用转换为直接引用。
  • 初始化:对静态变量初始化为指定的值,并执行静态代码块
  • 卸载: 从内存中清除
  • Math.java/User.java… 编译打包 -> jar包 java Math.class main() -> 使用 User.class

类加载器和双亲委派机制

  • 启动类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库(jre/lib下的jar包),如rt.jar,charsets.jar等
  • 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的jar包(jre/lib/ext下的jar包)
  • 应用程序类加载器:负责加载classpath路径下的类,主要是加载自己的class
  • 自定义加载器:
    • 继承ClassLoader
    • 重写findClass和loadClass方法
    • 负责加载用户自定义路径下的类包
  • defineClass: 验证 准备 解析 初始化

为什么设计双亲委派机制

  • 为了安全,防止核心API库被随意篡改
  • 为了避免重复加载,当父加载器已经加载了该类时,子加载器不用再次加载

打破双亲委派机制

  • tomcat下不同的war应用需要使用同一个第三方依赖库的不同版本时,无法通过双亲委派机制完成该需求
  • tomcat WebappClassLoader: 为了实现隔离性,没有遵循双亲委派机制,每个webappClassLoader加载自己目录下的class文件,不会传递给父加载器,打破了双亲委派机制
  • SPI机制也是打破双亲委派机制
  • 自己实现打破双亲委派机制的ClassLoader,需要重写loadClass方法,不通过父加载器加载
  • 自定义类加载器不能加载java开头的包,这是由Java的沙箱安全机制决定的,为了保证核心API不被恶意串改

Tomcat 类加载器

  • CommonClassLoader, 加载common目录下的jar包, Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • CatalinaClassLoader, 加载server/lib目录下的jar包,Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见
  • SharedClassLoader, 加载shared目录下的jar包,各个Webapp共享的类加载器,加载路径中的class对于所有的Webapp可见,但是对Tomcat容器本身不可见
  • WebappClassLoader, 各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见

JVM内存模型

在这里插入图片描述

1.堆:线程共享,在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存,注意Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做GC 堆,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常

堆中的java对象头中有一个指针,指向方法区中的该类的类元信息,及该类的类对象(Class对象)

  • 年轻代(默认1/3)
    • Eden(8/10): 新new出的对象通常在这里; Eden满的时候会触发一次GC,这个GC是minor GC(或者young GC),不是full GC
    • Survivor区:
      • From(1/10):
      • To(1/10):

    minor GC会回收Eden和Survivor中无引用的对象,是通过字节码执行引擎开启的垃圾收集线程来完成的

  • 老年代(默认2/3): 默认15次未被minor GC回收的对象会被挪到老年代,当老年代空间满的时候回触发full GC
    在这里插入图片描述
/**
 * 堆内存示例
 */
public class HeapTest {
    byte[] a = new byte[1024*100]; //100 kb

    public static void main(String[] args) throws Exception {
        List heapTestList = new ArrayList();
        while (true) {
            heapTestList.add(new HeapTest());
            Thread.sleep(30);
        }
    }
}

在这里插入图片描述
注意上图中黑色矩形圈出的部分:

  • 可以看到,当Eden满的时候会触发minor GC, 触发minor GC时,Eden区域会清空,Survivor0和Survivor1会交替的清空和填充,Old区域会随着每次minor GC而增加对象(minor GC 15次未清理的对象)
  • 当老年代被充满后会触发full GC
  • 当full GC无法回收空间后,程序抛出OOM异常:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

堆内存相关参数:

  • -Xms:堆内存初始大小
  • -Xmx:堆内存最大值
  • -Xmn: 新生代大小

2.方法区(元空间):线程共享,又称Non-Heap: 主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到运行时常量池中,以便后续使用。

  • 静态变量,常量等引用存放在方法区,该引用也是一个指针,指向堆内存中实际的对象
  • -XX:MetaspaceSize
  • -XX:MaxMetaspaceSize

3.程序计数器: 线程私有,属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器。字节码执行引擎工作时,通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

4.虚拟机栈: 线程私有,与线程同时创建,总数与线程关联,代表Java方法执行的内存模型。每个方法执行时都会创建一个栈桢来存储方法的的变量表(局部变量)、操作数栈、动态链接方法、返回值、返回地址等信息。每个方法从调用到结束就对于一个栈桢在虚拟机栈中的入栈和出栈过程

栈帧: 一个方法对应一块栈帧内存区域,栈帧中包含以下内容:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 方法出口
/**
 * 栈内存溢出示例
 * 设置栈内存大小:-Xss160k, 每一个线程的栈内存大小;
 */
public class StackOverflowTest {

    static int count = 0;

    public static void redo() {
        count ++;
        redo();
    }

    public static void main(String[] args) {
        try {
            redo();
        } catch (Throwable t) {
            t.printStackTrace();
            System.out.println("count:" + count);
        }
    }
}

运行结果:
java.lang.StackOverflowError
	at com.guoweizu.demo.tuling.StackOverflowTest.redo(StackOverflowTest.java:12)
	at com.guoweizu.demo.tuling.StackOverflowTest.redo(StackOverflowTest.java:13)
	at com.guoweizu.demo.tuling.StackOverflowTest.redo(StackOverflowTest.java:13)
	...
count:22422

虚拟机栈中有存放了对象的引用以及基本类型的值,这个对象引用是一个内存地址,指向堆内存中实际的对象

参数: -Xss1m(默认), -Xss160k(jdk1.8.111版本的最小栈空间)

5.本地方法栈: 线程私有,这部分主要与虚拟机用到的 Native 方法相关,一般情况下,我们无需关心此区域。

JVM参数调优

  1. 根据自己的系统估算每秒大概会产生多少对象,根据这些对象的特性判断这些对象会存放在堆中的哪个区域,以及在这些区域大概会存留多久,然后在针对不同的内存区域设置适当的参数
  2. 根据对象特性选择适当的垃圾回收器
  3. 垃圾对象尽量在年轻代就被minor GC回收,避免被挪到老年代,减少full GC次数

逃逸分析

JVM运行的三种模式:

  • 解释模式(Interpreted Mode): 只使用解释器(-Xint 强制JVM使用解释器模式),执行一行JVM字节码就编译一行为机器码
  • 编译模式(Compiled Mode): 只使用编译器(-Xcomp 强制JVM使用编译模式),先将所有JVM字节码一次编译为机器码,然后一次性执行所有机器码
  • 混合模式(Mixed Mode): 依然使用解释模式执行代码,但是对于一些"热点"代码采用编译模式执行,JVM一般采用混合模式执行代码

解释模式启动快,对于只需要执行部分代码,且大多数代码只会执行一次的情况比较适合;编译模式启动慢,但是后期执行快,而且比较占用内存,因为机器码的数量至少是JVM字节码的10倍以上,这种模式适合代码可能被反复执行的场景;混合模式JVM默认的执行方式,一开始还是解释执行,但是对于少部分"热点"代码会裁员编译模式执行,这些热点代码对应的机器码会被缓存起来,下次执行无需再编译,这就是我们常见的JIT(Just In Time Compiler)即使编译技术。

  • 对象逃逸分析:

逃逸分析:JVM在即时编译时判断方法中变量是否只保留在本方法内部,不会逃逸出方法外,例如,局部变量没有被return;如果只保留在方法内部,对象就可以分配在栈内存的栈帧中,随着方法执行结束被回收,不用分配到堆上,减少GC
-XX:+DoEscapeAnalysis(打开逃逸分析); JDK7以后默认打开
-XX:-DoEscapeAnalysis(关闭逃逸分析)

JVM垃圾回收

  • Minor GC/Young GC: 收集新生代的垃圾对象,minor GC非常频繁,回收速度一般也比较快
  • Major GC/Full GC: 收集老年代,年轻代和方法区的垃圾,Full GC的垃圾回收速度一般比Minor GC慢10倍以上。
/**
 * 垃圾回收示例
 * 打印垃圾回收信息参数: -XX:+PrintGCDetails
 * 打印GC日志: -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log
 */
public class GCTest {
    public static void main(String[] args) {
        byte[] allocation1new = new byte[1024 * 60000];
    }
}

运行结果:
Heap
 PSYoungGen      total 76288K, used 65246K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
  eden space 65536K, 99% used [0x000000076ab00000,0x000000076eab7bc0,0x000000076eb00000)
  from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
  to   space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
 ParOldGen       total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
 Metaspace       used 3308K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 365K, capacity 388K, committed 512K, reserved 1048576K

进入老年代的情况:

  1. 大对象会直接被放入老年代
    • -XX:PretenureSizeThreshold=1000000 -XX:+UserSerialGC 设置大对象的大小为1M,单位为字节,超过1m的会直接放入老年代
    • 为了避免大对象在minor GC时再From和To之间不停挪动
  2. 长期存活的对象将进入老年代(默认15岁)
    • -XX:MaxTenuringThreshold=5 设置年龄
  3. 对象动态年龄判断:执行minor GC时,JVM会判断survivor区中的对象:将所有对象根据年龄大小,从小到大相加,当加到n时已经超过了survivor区(一块survivor区:s0或者s1)的50%,则将n及n以上年龄的对象全部直接挪到老年代;
    • 比如:survivor区总共为200m(s0和s1分别为200m), 将所有对象按年龄排序相加: 1+2+…+n, 例如加到n=10时,总大小超过了100m,那么将10及10以上年龄的对象全部挪到老年代
  4. minor GC时,Survivor区放不下的对象会被直接放入老年代
  5. 老年代空间分配担保机制: 触发minor GC之前,JVM会执行以下逻辑,来保证老年代有足够的空间:
    • 老年代剩余可用空间 < 年轻代里现有的所有对象所占空间: 如果是,则继续执行下一个判断,否则直接执行minor GC
    • 是否配置 -XX:-HandlePromotionFailure(1.8已默认配置);没配置,直接Full GC, 否则执行下一个判断
    • 老年代剩余可用空间 < 每次minor GC后进入老年代的对象的平均大小, 如果为真,则执行Full GC, 否则执行minor GC
      在这里插入图片描述

如何判断对象可以被回收

  1. 引用计数器: 对象每被引用一次(赋值一次)计数器加1,当引用失效,计数器减1,当计数器为0时,则可回收
    • 相互引用问题,目前JDK不使用这种方式
  2. 可达性分析:基本思想就是通过一系列的称为"GC Roots"的对象作为起点, 从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,其他对象都是垃圾对象;
    • 常用的GC Roots根节点: 线程栈的本地变量,静态变量,本地方法栈的变量等
    • 目前JDK默认使用方式
  3. 常见引用类型, java的4种引用类型:
    • 强引用:普通的变量引用
      User user = new User();
      
    • 软引用:当GC做完后发现释放不出空间存放新对象,则会把这些软引用的对象回收掉
      SoftReference<User> user = new SoftReference<User>(new User());
      
      可用来实现内存敏感的高速缓存
    • 弱引用:WeakReference, GC会直接回收
    • 虚引用:几乎不用
  4. finalize()方法最终判定对象是否存活
    1. 第一次标记并进行一次筛选; 判断是否覆盖了finalize()方法
    2. 第二次标记
  5. 如果判断一个类是无用的类, 方法区主要回收无用的类,判断一个类是无用的类的条件:
    • 该类所有的实例都已被回收,也就是Java堆中没有该类的实例
    • 加载该类的ClassLoader已经被回收
    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

垃圾回收算法

  1. 标记-清除算法
    • 效率问题
    • 空间问题(会产生大量不连续的碎片)
  2. 复制算法
    • 将内存分为两块,一部分是使用区,一部分是保留区,收集时将不能回收的对象复制到保留区(连续存放),然后将剩下的全部清理掉
    • 空间利用率低
  3. 标记整理算法
  4. 分代收集算法,年轻代用一种算法,老年代用一种算法

垃圾收集器

  1. Serial收集器
    • -XX:+UseSerialGC (新生代:复制算法)
    • -XX:+UseSerialOldGC (老年代:复制整理算法)
    • 历史最悠久的垃圾收集器。单线程的收集器, 执行垃圾收集时,停止所有用户线程,只执行垃圾收集线程
  2. ParNew收集器
    • -XX:+UseParNewGC:SerialGC的多线程版本,
    • 收集时停止所有的用户线程,只执行垃圾收集线程,适用于多核的处理器,默认的收集线程数跟cpu核数相同;
    • 目前用得较多的垃圾收集器。通常和CMS搭配使用
  3. Parallel Scavenge收集器
    • -XX:+UseParallelGC(年轻代:复制算法),
    • -XX:+UseParallelOldGC(老年代:标记整理算法):
    • 类似于ParNew收集器,是server模式下的默认收集器(内存大于2G, CPU大于2)
    • 可以设置STW的时间,但是如果时间不够就不一定能将堆中的垃圾收集干净,未收集的下次再回收
  4. CMS收集器(Concurrent Mark Sweep)
    • 只能用在老年代
    • 标记清除算法,
    • -XX:+UseConcMarkSweepGC
    • 是一种以获取最短停顿时间为目标的收集器, STW时间尽量短
    • HotSpot虚拟机第一款真正意义上的并发收集器,他第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
    • 收集过程分为以下4个阶段:
      1. 初始标记:暂停所有的其他线程,并记录下gc roots直接能引用的对象(不会深入遍历,因此速度很快,深入遍历会在并发标记阶段),该阶段会STW, 但速度很快;
      2. 并发标记:GC线程与用户线程同时执行,用一个闭包结构记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程在这个阶段可能更新引用域,所以GC线程无法保证可达性分析的实时性,这个阶段会记录发生引用更新的地方,用于下一个阶段重新标记;
      3. 重新标记:重新标记阶段就是为了修正并发标记期间因为用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段也会STW, 停顿时间一般比初始标记阶段稍长,但远远小于并发标记阶段的时间
      4. 并发清理:开启用户线程,同时GC线程开始对未标记的区域做清理;
    • 缺点:
      • 对CPU资源敏感
      • 无法收集浮动垃圾(GC过程中产生的垃圾,在下一次GC时收集)
      • 标记清除算法,会产生内存碎片:通过设置-XX:UseCMSCompactAtFullCollection参数,可以让JVM执行完GC后做整理
      • 当CMS gc还未完成时,由于应用程序继续运行可能再次触发GC, 这时会出现"concurrent mode failure",此时会进入stop the world, 然后改用serial old垃圾收集器来回收
    • CMS相关参数:
      • -XX:+UseConcMarkSweepGC: 启用CMS
      • -XX:ConcGCThreads: 并发的GC线程数
      • -XX:+UseCMSCompactAtFullCollection: full GC后做压缩整理
      • -XX:CMSFullGCsBeforeCompaction: 多少次full GC后做压缩整理,默认为0, 每次完成都后整理(必须要设置-XX:+UseCMSCompactAtFullCollection参数后才生效)
      • -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发Full GC(默认是92, 这是百分比)
      • -XX:+UseCMSInitiatingOccupancyOnly: 只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
      • -XX:+CMSScavengeBeforeRemark: 在CMS GC前启动一次minor GC, 目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段的开销,一般CMS的GC耗时80%都在标记阶段
  5. G1收集器:
    • -XX:+UseG1GC
    • 是一款面向服务器的垃圾收集器,主要针对配置了多颗处理器及大容量内存的机器,以及高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。
    • 将Java堆划分为大小相等的独立区域(region), 最多可划分2048个region; 在这里插入图片描述
    • 每个region的大小就是总的堆内存/2048;例如堆大小4096m,那每个region为4096/2048=2m
    • 可通过-XX:G1HeapRegionSize设置region的大小
    • G1保留了年轻代和老年代的概念,但是不再物理隔离,他们都是region的集合(可以不连续)
    • 默认的年轻代占5%,例如堆内存4096m, 那么年轻代大约200m,对应大概100个region

      当年轻代满的时候,G1会在触发Young GC之前会比较本次收集的时间与设置的停顿时间,如果远远小于设置的时间,那么G1会增加年轻代的region

    • 可以通过-XX:G1NewSizePercent设置新生代初始占比,在系统运行中,JVM会不停给年轻代增加更多的region,但是最大不会超过60%,最大占比可以通过-XX:G1MaxNewSizePercent参数修改。
    • 年轻代中的eden和survivor区的比例和之前一样,也是8:1:1
    • 一个region可能之前是年轻代,但是region经过垃圾回收后,可能变为老年代,也就是说Region的区域功能可能动态变化。
    • G1垃圾收集器对于对象什么时候进入老年代的原则和之前一样,唯一不同的是对大对象的处理,G1有专门分配大对象的region叫Humongous区,而不是让大对象直接进入老年代Region区
    • G1对大对象的判定规则:一个对象的大小超过了一个Region大小的50%,比如之前Region大小为2m,那么超过1M的对象会被当做大对象放入Humongous区,而且如果一个对象太多,可能横跨多个连续的region来存放。
    • Humongous区专门存放短期巨型对象,不用直接放入老年代,可以节约老年代的空间,避免因老年代空间不够的GC开销
    • Full GC的时候处理收集年轻代和老年代之外,也会将Humongous区一并回收。
    • G1垃圾收集过程:
      1. 初始标记(Initial Mark, STW): 暂停所有其他线程,并记录下gc roots能直接引用的对象,速度很快
      2. 并发标记(Concurrent Mark): 同CMS的并发标记
      3. 最终标记(Remark, STW): 同CMS的重新标记
      4. 筛选回收(Cleanup, STW): 筛选回收阶段首先对各个region的回收价值和成本进行排序,根据用户所期望的GC停顿时间(可通过-XX:MaxGCPauseMillis指定)来制定回收计划

        比如有1000个region需要回收,但是回收需要200毫秒,如果用户指定的停顿时间为100毫秒,那么G1会通过之前的回收成本计算,可能只能收集800个region,那么本次就只会回收800个region

    • 回收算法用的是复制算法,不会产生太多的内存碎片
    • G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值大最大的region(这也就是它的名字Garbage First的由来)

      比如一个region花200毫秒能回收10m垃圾,另一个region花50毫秒可以回收20m垃圾,在回收时间有限的情况下,那么G1会优先回收后一个region

    • 常用参数汇总:
      • -XX:+UseG1GC:启用G1垃圾回收器
      • -XX:ParallelGCThreads: 垃圾回收开启的线程数
      • -XX:MaxGCPauseMillis: 目标暂停时间(默认200ms)
      • -XX:G1NewSizePercent: 新生代内存初始空间(默认整堆的5%)
      • -XX:G1MaxNewSizePercent: 新生代内存最大空间
      • -XX:TargetSurvivorRatio: Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+…+年龄n的所有对象)总和超过了Survivor区的50%,此时会把年龄为n及以上的对象都放入老年代的region中
      • -XX:MaxTenuringThreshold: 判定进入老年代的年龄阈值(默认15)
      • -XX:InitiatingHeapOccupancyPercent: 老年代占用空间达到整个堆内存阈值(默认45%),则执行Mixed GC,比如有2048个region,当有接近1000个region被老年代对象占用时,就会触发Mixed GC
      • -XX:G1HeapWastePercent:默认5%,gc过程中空出来的region是否充足阈值,在Mixed GC时,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象复制到其他region,然后这个region的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的region,一旦空闲出来的region数量达到了堆内存的5%,此时就会立即停止Mixed GC,意味着本次GC结束
      • -XX:G1MixedGCLiveThresholdPercent:默认85%,region中的存活对象低于这个值时才回收该region,如果超过这个值,存活对象过多,回收的意义不大。
      • -XX:G1MixedGCCountTarget: 在一次Mixed GC回收过程中指定做几次筛选回收(默认8次),在筛选回收阶段,可以回收一会,然后暂停回收,恢复系统运行,一会再回收,这样可以让系统不至于单次停顿时间过长
    • G1的垃圾收集分类:
      • Young GC: 当年轻代满的时候,G1会在触发Young GC之前会比较本次收集的时间与设置的停顿时间,如果远远小于设置的时间,那么G1会增加年轻代的region,否则就执行Young GC
      • Mixed GC: 不是full GC, 老年代的空间占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值时触发,回收所有的Young和部分old(根据期望的GC停顿时间确定old区回收多少,G1维护了region回收的顺序列表)以及大对象区,正常情况G1的垃圾回收是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region中去,拷贝过程中如果发现没有足够的空region来承载需要拷贝的对象就会触发Full GC.
      • Full GC: 停止所有用户线程,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批region供下次Mixed GC使用,这个过程非常耗时(类似CMS发生"concurrency mode failure"后使用Serial GC一样)
    • 使用G1的场景
      • 50%以上的堆被存活对象占用
      • 对象分配和晋升的速度变化非常大
      • 垃圾回收时间特别长,比如超过1秒
      • 8GB以上的对内存(推荐值)
      • 停顿时间需要控制在500ms以内

JVM工具使用

  1. jmap: 查看内存信息,包括实例个数及占用内存大小
    $ jmap -histo $pid # 查看实例数以及占用内存
    $ jmap -heap $pid # 查看堆内存情况
    $ jmap -dump:format=b,file=myapp.hprof $pid # 导出内存快照
    
  2. jstack: 查看死锁
  3. jinfo: 查看jvm参数
  4. jstat: 最重要的工具,查看内存及gc情况
    $ jstat -gc $pid
    # 每隔1000毫秒打印一次,总共打印5次, 可以根据频率来计算对象的增长速率,以及观察内存的使用情况
    $ jstat -gc $pid 1000 5
    
  5. jvisualvm:可视化工具

GC日志

  1. 打印GC日志参数:
    -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log
    
  2. GCeasy

常量池

  • 字面量
    • 类的全额限定名和描述符
    • 方法的全额限定名和描述符
    • 变量的名称和描述符
  • 常量池
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值