一文了解JVM的前世今生

这里写自定义目录标题

  • 源码到类文件
    • 源码
  • 类文件到虚拟机(类加载机制)
    • 装载(Load)
    • 链接(Link)
  • 类装载器ClassLoader
    • 分类
    • 加载原则
    • 双亲委派原则
  • 运行时数据区(Run-Time Data Areas )
    • Method Area(方法区)
    • Heap(堆)
    • java Virtual Machine Stacks(虚拟机栈)
    • The pc Register(程序计数器)
    • Native Method Stacks(本地方法栈)
  • 结合字节码指令理解java虚拟机栈和栈帧
    • 栈指向堆
    • 方法区指向堆
    • 堆指向方法区
  • java对象内存布局
  • 内存模型
    • 图解
    • 对象创建所在的区域
    • Survivor区详解
    • Old区详解
    • 对象的一辈子理解
    • 常见问题
      • 为什么需要Survivor区,只有Eden不行吗 ?
      • 为什么需要两个Survivor区?
      • 新生代中Eden:s1:s2为什么是8:1:1?
  • Garbage Collect(垃圾回收)
  • 垃圾收集算法
    • 标记-清除(Mark-Sweep)
    • 复制(Copying)
    • 标记-整理(Mark-Compact)
    • 分代收集算法
  • 垃圾收集器
    • Serial收集器
    • ParNew收集器
    • Parallel Scavenge收集器
    • Serial Old 收集器
    • Parallel Old收集器
    • CMS收集器
  • G1收集器
    • 工作过程:
  • 垃圾收集器分类
    • 吞吐量和停顿时间
  • JVM实战
    • -X参数
    • -XX参数
    • 其他参数
  • 常用命令
    • 查看java进程 jps
    • jinfo 实时查看和调整jvm配置参数
    • jstat 查看虚拟机性能统计信息
    • jstack 查看线程堆栈信息
    • jmap
  • 常用工具
    • jconsole
    • jvisualvm
    • Arthas
  • G1调优与最佳指南
    • 调优
  • JVM性能优化指南
    • 常见问题思考
      • 内存泄漏和内存溢出的区别
      • young gc 会有stw吗?
      • major gc和full gc的区别
      • g1和cms的区别
      • 什么是直接内存
      • 不可达的对象一定要被回收吗?
      • 方法区中的无用类回收
      • 不同的引用

源码到类文件

源码

public class Person {
    private String name;
    private int age;
    private static String address;
    private final static String hobby = "Programming";

    public void say() {
        System.out.println("person say...");
    }

    public int calc(int op1, int op2) {
        return op1 + op2;
    }
}

编译: javac Person.java —> Person.class

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器
-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

类文件到虚拟机(类加载机制)

装载(Load)

查找和导入class文件
1)通过一个类的全限定名获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在java堆中生成一个代表这个类的java.lang.class对象,作为对方法区中这些数据的访问入口

链接(Link)

验证(Verify):保证被加载类的正确性
文件格式验证
元数据验证
字节码验证
符号引用验证
准备(Prepare)
为类的静态变量分配内存,并将其初始化为默认值
解析(Resolve)
把类中的符号引用转化为直接引用
初始化(Initialize)
对类的静态变量,静态代码块执行初始化操作
一文了解JVM的前世今生_第1张图片

类装载器ClassLoader

在装载(Load)阶段,其中第1)步,通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的。
1)通过一个类的全限定名获取定义此类的二进制字节流

分类

1)BootStrap ClassLoader 负责加载 J A V A H O M E 中 j r e / l i b / r t . j a r 里 所 有 的 c l a s s 或 X b o o t C l a s s o a t h 选 项 指 定 的 j a r 包 。 由 C + + 实 现 , 不 是 C l a s s L o a d e r 子 类 。 2 ) E x t e n s i o n C l a s s L o a d e r 负 责 加 载 j a v a 平 台 中 扩 展 功 能 的 一 些 j a r 包 , 包 括 JAVA_HOME中 jre/lib/rt.jar里所有的class或XbootClassoath选项指定的jar包。由C++实现,不是ClassLoader子类。 2)Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括 JAVAHOMEjre/lib/rt.jarclassXbootClassoathjarC++ClassLoader2ExtensionClassLoaderjavajarJAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
3)App CLassLoader负责加载classpath中指定的jar包及Djava.class.path所指定目录下的类和jar包
4) Custom ClassLoader通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
一文了解JVM的前世今生_第2张图片

加载原则

检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader
到BootStrap ClassLoader逐层检查,只要某个Classloader已加载,就视为已加载类,保证此类只所有ClassLoader加载一次。

加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

双亲委派原则

定义:如果一个类加载器在接到加载类请求时,它首先不会自己尝试去加载这个类,而是把这个请求委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才会自己加载。

优势:java类随着加载它的类加载器一起具备了一种带有优先级的层次关系,比如,java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己去加载的话,那么系统中会存在多种不同的Object类。
1,每个 类都只会被加载一次,避免了重复加载。
2,每一个类都会尽可能的加载(从引导类加载器往下,每个加载器都可能会根据优先次序尝试加载它)
3,有效避免了某些恶意类的加载(比如自定义了Java.lang.Object类,一般而言在双亲委派)

破坏:可以继承CLassLoader类,然后重写其中的loadClass方法,其他方式大家可以扩展一下

运行时数据区(Run-Time Data Areas )

  1. 在装载阶段的第2)、3)步可以发现有运行时数据、堆、方法区等名词
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
    说白了就是类文件被加载器装载进来之后,类中的内容(比如变量、常量、方法、对象等这些数据得要有个去处,也就是要存储起来,存储的位置肯定是在JVM中有对应的空间)

官网链接

一文了解JVM的前世今生_第3张图片

Method Area(方法区)

方法区是各个线程共享的内存区域,在虚拟机启动时创建。
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做-Non-Heap(非堆),目的是与java堆区分开来。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is created on virtual machine start-up. Although the method area is logically part of the heap,...... If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

如果这时候把从class文件装载的第1和第2步合并起来理解的话,可以画个图。
一文了解JVM的前世今生_第4张图片
值得说明的是:
1)方法区在JDK中就是Metaspace,在JDK6或者7中就是PermSpace
2)Run-Time Constant Pool

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载进入方法区的运行时常量池中放入。

Heap(堆)

java堆是java虚拟机所管理的最大的一块,在虚拟机启动时创建,被所有线程共享。java对象实例以及数组都在堆上分配。

The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated. The heap is created on virtual machine start-up.

此时回看装载阶段的第三步:3)在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
一文了解JVM的前世今生_第5张图片

java Virtual Machine Stacks(虚拟机栈)

经过上面的分析,类加载机制的装载过程已经完成,后续的链接,初始化也会相应的生效。

虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态,换句话说,一个java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定线程私有的,独有的,随着线程的创建而创建。

每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。

调用一个方法,就会向栈中压入一个栈帧,一个方法调用完成,就会把该栈帧从栈中弹出。
一文了解JVM的前世今生_第6张图片

The pc Register(程序计数器)

我们知道一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。

假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢? 这就是需要在线程中维护一个变量,记录线程执行到的位置。

程序计数器占用的内存空间很小,由于java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有),如果正在执行的是Native方法,则这个计数器为空。

The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.

Native Method Stacks(本地方法栈)

如果当前线程执行方法是Native类型的,这些方法就会在本地方法栈中执行。

结合字节码指令理解java虚拟机栈和栈帧

栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间

每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。

**局部变量表:**方法中定义的局部变量以及方法的参数放在这张表中
局部变量表中的变量不可直接使用,如果需使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
操作数栈: 以压栈和出栈的方式存储操作数的。
**动态链接:**每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)
**方法返回地址:**当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令,一种是遇见异常,并且这个异常没有在方法体内得到处理。

一文了解JVM的前世今生_第7张图片

一文了解JVM的前世今生_第8张图片
一文了解JVM的前世今生_第9张图片

栈指向堆

如果在栈帧中有一个变量,类型为引用类型,比如Object obj=new Object(),这时候就是典型的栈中元素指向堆中的对象。
一文了解JVM的前世今生_第10张图片

方法区指向堆

方法区中会存放静态变量,常量等数据。如果是如下情况,就是典型的方法区中元素指向堆中的对象。

private static Object obj=new Object();

一文了解JVM的前世今生_第11张图片

堆指向方法区

方法区中会包含类的信息,堆中会有对象,那怎么知道对象是哪个类创建的呢?
一文了解JVM的前世今生_第12张图片

java对象内存布局

一个对象在内存中包含三个部分:对象头、实例数据和对齐填充
一文了解JVM的前世今生_第13张图片

内存模型

图解

一块是非堆区,一块是堆区
堆区分为两大块,一个是old区,一个是Yound区。
Young区分为两大块:一个是Survivor区(S0+S1),一个是Eden区。Eden:s0:s1=8:1:1,s0和s1一样大,也可以叫from和to。
一文了解JVM的前世今生_第14张图片
根据之前对于Heap的介绍可以知道,一般对象和数组的创建会在堆中分配内存空间,关键是堆中有这么多区域,那一个对象的创建到底在哪个区域呢?

对象创建所在的区域

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。

比如有对象A、B、C等创建在Eden区,但是Eden区的内存空间肯定有限,比如有100M,假如已经使用了100M或者达到一个设定的临界值,这时候就需要对Eden区内存空间进行清理,即垃圾收集(Garbage Collect),这样的GC我们称之为Minor GC,Minor GC指的是Young区的GC。

经过GC之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活着的对象需要将其复制到Survivor 区,然后再清空Eden区中的这些对象。

Survivor区详解

Survivor区分为两块S0和S1,也可以叫做From和To。
在同一个时间点上,S0和S1只能有一个区有数据,另一个是空的。

eg:比如一开始只有Eden区和From中有对象,To是空的。
此时进行一次GC操作,From区中对象的年龄就会+1,我们知道Eden区中所有存活的对象都会被复制到To区,From区中还能存活的对象会有两个去处。
若对象年龄达到之前设置的年龄阈值,此时对象会被移动到Old区,如果Eden区和From区没有达到阈值的对象会被复制到To区。此时Eden区和From区已经被清空(被GC的对象肯定没了,没有被GC的对象都有了各自的去处)
这时候From和To交换角色,之前的From变成了To,之前的To变成了From。
也就是说无论如何都要保证名为To的Survivor区域是空的。
Minor GC会一直重复这样的过程,知道To区被填满,然后会将所有对象复制到老年代中。

Old区详解

一般Old区都是年龄比较大的对象,或者年龄相对超过了某个阈值的对象。
在Old区也会有GC的操作,Old区的GC我们称作为Major GC

对象的一辈子理解

我是一个普通的java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区玩了挺长时间,有一天Eden区的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始飘了,有时候在Survivor的“From”区,有时候在Survivor的“to”区,居无定所。直到18岁的时候,爸爸说我成人了,该去社会上闯闯。
于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里了也认识了很多老人。在年老代里,我生活了20年(每次GC加一岁),然后被回收
一文了解JVM的前世今生_第15张图片

常见问题

如何理解Minor/Major/Full GC

Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代

为什么需要Survivor区,只有Eden不行吗 ?

如果没有Survivor,Eden区每进行一次Minor GC,并且没有年龄限制的话,存活的对象就会被送到老年代。
这样一来,老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。
老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。
执行时间长有什么坏处?频发的Full GC消耗的时间很长,会影响大型程序的执行和响应速度。
可能你会说,那就对老年代的空间进行增加或者较少咯。
假如增加老年代空间,更多存活对象才能填满老年代。虽然降低Full GC频率,但是随着老年代空间加大,一旦发生Full
GC,执行所需要的时间更长。
假如减少老年代空间,虽然Full GC所需时间减少,但是老年代很快被存活对象填满,Full GC频率增加。
所以Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16
次Minor GC还能在新生代中存活的对象,才会被送到老年代。

为什么需要两个Survivor区?

最大的好处是解决了碎片化,也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区,假设现在只有一个Survivor区,我们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中存活对象就会被移动到Survivor区,这样循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的空间是不连续的,也就导致了内存碎片化

新生代中Eden:s1:s2为什么是8:1:1?

Garbage Collect(垃圾回收)

如何确定一个对象是垃圾
要想进行垃圾回收,得先知道什么样的对象是垃圾

引用计数法
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对齐引用,他就是垃圾。
弊端:如果AB相互持有引用,导致永远不能被回收。

可达性分析
通过GC Root的对象,开始向下寻找,看某个对象是否可达
**能作为GC root:**类加载器、Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量等。

垃圾收集算法

已经能够确定一个对象为垃圾之后,接下来要考虑的就是回收,怎么回收呢?得有相应的算法。

标记-清除(Mark-Sweep)

  • 标记
    找出内存中需要回收的对象,并且把他们标记出来
    此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时
    一文了解JVM的前世今生_第16张图片
  • 清除
    清除掉被标记需要回收的对象,释放出对应的内存空间
    一文了解JVM的前世今生_第17张图片
  • 缺点
  • 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
    1) 标记和清除两个过程都比较耗时,效率不高
    2)会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集的动作。

复制(Copying)

将内存划分为相等的区域,每次只适用其中一块,如下图所示

一文了解JVM的前世今生_第18张图片
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
一文了解JVM的前世今生_第19张图片
缺点: 空间利用率降低

标记-整理(Mark-Compact)

标记过程仍然与“标记-清除”算法一样,但是后续步骤不是直接对可回收进行整理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
一文了解JVM的前世今生_第20张图片
让所有存活的对象都向一端移动,清理掉边界以外的内存

一文了解JVM的前世今生_第21张图片

分代收集算法

既然上面介绍了3中垃圾收集算法,那么在堆内存中到底用哪一个呢?
Young区:复制算法(对象在被分配之后,可能生命周期比较短,Young区复制效率比较高)
Old区:标记清除或标记整理(Old区对象存活时间比较长,复制来复制去没必要,不如做个标记再整理)

垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现,说白了就是落地
一文了解JVM的前世今生_第22张图片

Serial收集器

serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。

它是一种单选程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成来及收集工作,更重要的是其在运行垃圾收集的时候需要暂停其他线程。

优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
使用范围:新生代
应用:Client模式下的默认新生代收集器
一文了解JVM的前世今生_第23张图片

ParNew收集器

可以把这个收集器理解为Serial收集器的多线程版本。

优点:在多CPU时,比Serial效率高
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器

一文了解JVM的前世今生_第24张图片

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,它也是适用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel
Scanvenge更关注 系统的吞吐量。

  • 吞吐量
    运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)
    eg:虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%.
    若吞吐量越大,意味着垃圾收集时间越短,则用户代码可以充分利用cpu资源,尽快完成程序的运算任务。

-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GCTimeRatio直接设置吞吐量的大小。

Serial Old 收集器

serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用“标记-整理算法”,运行过程和Serial收集器一样。
一文了解JVM的前世今生_第25张图片

Parallel Old收集器

parallel Old 收集器是Paralle Scavenge收集器的老年代版本,使用多线程和“标记-整理算法”进行垃圾回收

CMS收集器

cms(Concurent Mark Sweep)收集器是一种以获取 (最短回收停顿时间)为目标的收集器。采用的是“标记-清除算法”
一文了解JVM的前世今生_第26张图片
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程与用户线程是一起并发的执行。

优点:并发标记、低停顿
缺点:产生大量的空间碎片、并发阶段会降低吞吐量
一文了解JVM的前世今生_第27张图片

G1收集器

G1特点

并行与并发
分代收集(仍然保留了分代的概念)
空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)

使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然保留有新生代和老年代的概念,但新生代和老年代不在物理隔离的了,它们都是一部分Region(不需要连续)的集合。

工作过程:

  • 初始标记(Initial Marking)
    标记一下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程。
  • 并发标记(Concurrent Marking)
    从GC Roots进行可达性分析,找出存活的对象,与用户线程并发执行
  • 最终标记(Final Marking)
    修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
  • 筛选回收 (Live Data Counting and Evacuation)
    对各个region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划。

一文了解JVM的前世今生_第28张图片

垃圾收集器分类

  • 串行收集器–》Serial 和Serial Old
    只能有一个垃圾回收线程执行,用户线程暂停(适用于内存比较小的嵌入式设备)
  • 并行收集器 吞吐量优先
    Parallel Scanvenge,Parallel Old
    多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。(适用于科学计算、后台处理等若交互场景)
  • 并行收集器 停顿时间优先 --》CMS ,G1
    用户线程和垃圾收集器线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。 适用于相对时间有要求的场景,比如web

吞吐量和停顿时间

停顿时间: 垃圾收集器 进行 垃圾回收终端应用执行响应时间
吞吐量:运行用户代码时间/(运行用户代码时间+垃圾收集时间)

停顿时间越短就越适合需要和用户交互的程序,良好的响应速度能提升用户体验。
高吞吐量则可以高效的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务

JVM实战

标准参数
-version
-help
-server
-cp

在这里插入图片描述

-X参数

非标准参数,也就是在JDK各个版本中可能会变动

-Xint 解释执行
-Xcomp 第一次使用就编译成本地代码
-Xmixed 混合模式,JVM自己来决定

-XX参数

使用最多的参数类型
非标准化参数,相对不稳定,主要用于JVM调优和Debug

1,Boolean类型
格式:-XX:[±] +或-表示启用或者禁用的name属性
比如:-XX:+UserConcMarkSweepGC 表示启用CMS类型的垃圾回收期
-XX:+UseG1GC 表示启用G1类型的垃圾回收器

2,非Boolean类型
格式: -XX= 表示name属性的值是value
eg : -XX:MaxGCPauseMillis=500

其他参数

-Xms1000等价于-XX:InitialHeapSize=1000
-Xmx1000等价于-XX:MaxHeapSize=1000
-Xss100等价于-XX:ThreadStackSize=100

常用命令

查看java进程 jps

The jps command lists the instrumented Java HotSpot VMs on the target system. 
The command is limited to reporting information on JVMs for which it has the 
access permissions.

在这里插入图片描述

jinfo 实时查看和调整jvm配置参数

The jinfo command prints Java configuration information for a specified Java process or core file or a remote debug server. The configuration information includes Java system properties and Java Virtual Machine (JVM) command-line flags.
  • jinfo -flag name PID 查看某个java进程的name属性的值
jinfo -flag MaxHeapSize PID 
jinfo -flag UseG1GC PID

在这里插入图片描述

jinfo -flags PID

一文了解JVM的前世今生_第29张图片

jstat 查看虚拟机性能统计信息

The jstat command displays performance statistics for an instrumented Java HotSpot VM. The target JVM is identified by its virtual machine identifier, or vmid option.
  • jstat -class PID 1000 10 查看某个java进程的类装载信息,每1000毫秒输出一次,共输出10 次
  • jstat -gc PID 1000 10
    一文了解JVM的前世今生_第30张图片

jstack 查看线程堆栈信息

-jstack PID
一文了解JVM的前世今生_第31张图片

jmap

生成堆转储快照
-jmap -heap PID 打印出堆内存相关信息

一文了解JVM的前世今生_第32张图片

常用工具

jconsole

JConsole工具是JDK自带的可视化监控工具。查看java应用程序的运行概况、监控堆信息,永久区使用情况、类加载情况等,。

jvisualvm

  • 监控本地Java进程
    可以监控本地的java进程的CPU,类,线程等
  • 监控远端Java进程

Arthas

https://github.com/alibaba/arthas

Arthas 是Alibaba开源的Java诊断工具,采用命令行交互模式,是排查jvm相关问题的利器。
一文了解JVM的前世今生_第33张图片

G1调优与最佳指南

调优

是否选用G1垃圾收集器的判断依据
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases

1>50%以上的堆被存活对象占用
2>对象分配和晋升速度变化非常大
3>垃圾回收时间比较长
  1. 使用G1GC垃圾收集器:-XX:+UseGGC
    -修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
    在这里插入图片描述
  2. 调整内存大小再获取gc日志分析
-XX:MetaspaceSize=100M 
-Xms300M 
-Xmx300M

在这里插入图片描述
官网建议
一文了解JVM的前世今生_第34张图片
一文了解JVM的前世今生_第35张图片

JVM性能优化指南

https://www.liuchengtu.com/swdt/#R24be239c522eb4747eac7a01b699012a
一文了解JVM的前世今生_第36张图片

常见问题思考

内存泄漏和内存溢出的区别

内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成空间的浪费
内存溢出: 内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的

young gc 会有stw吗?

不管什么gc,都会stop-the-world,只是发生时间长短而已

major gc和full gc的区别

major gc指的是老年代的gc,而full gc等于young + old+metaspace的gc

g1和cms的区别

CMS用于老年代的回收,而G1用于新生代和老年代的回收
G1使用了Region方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生

什么是直接内存

直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于java堆。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。

不可达的对象一定要被回收吗?

即使在可达性分析中不可达的对象,也并非是“非死不可”的,这时候他们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize方法,或finalize方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。

被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

方法区中的无用类回收

方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?

判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类”
1,该类所有的实例都已经被回收,也就是java堆中不存在该类的实例
2,加载该类的classLoader已经被回收
3,该类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收

不同的引用

JDK1.2以后,java对对象进行了扩充:强引用、软引用、弱引用和虚引用

你可能感兴趣的:(jvm,java)