为什么要有标题0?不要问,问就是程序员数数都是从0开始的。
前段时间正在看《深入理解Java虚拟机》这本书,看完之后颇有感受,这本书写的非常好,我本人也很喜欢周志明老师的风格,真心佩服周老师对虚拟机的理解这么透彻,但从书的标题也可以看得出“深入”二字,如同书名,该书内容确实对新手来说有些晦涩。所以在这里我总结了一篇Java虚拟机的博文,大家可以把它当做阅读这本书的前奏,让自己在心里有一些Java虚拟机的概念,并有一定的理解。如果喜欢的话,可以点个赞和收藏哦!
本文章参考了博主陈树义的JVM专栏,并在已经过博主本人同意的情况下发布这篇文章。
我们知道不同的操作系统底层的实现是不一样的。因此在一个操作系统上编译的机器码不能在另一个操作系统上被识别。所以和其他语言不同,Java语言不直接编译成与系统有关的机器码,而是编译字节码,再通过不同的系统上提前安装好的Java虚拟机分别解释成机器码。
编译器:
前端编译器:源代码到字节码,代表:Sun的javac
JIT 编译器:从字节码到机器码,代表:HotSpot VM的C1、C2
分类
运行模式
-client
或 -server
打开-Xint
参数打开-Xcomp
参数打开AOT 编译器:源代码到机器码,代表:GNU Compiler for the Java(GCJ)
对比:
字节码文件由以下七个部分组成
字节码文件中的十六进制数字以若干位为单位,分别代表着以上的信息。
具体内容可查看这篇文章:https://www.cnblogs.com/chanshuyi/p/jvm_serial_05_jvm_bytecode_analysis.html
虚拟机内存结构(官方也叫运行时数据区)
公有:所有线程都共享一个,包含公有数据
私有:每个线程都有一个,包含私有数据
PC寄存器(Program Counter 寄存器):保存线程当前正在执行的方法
Java虚拟机栈
本地方法栈
当有一个对象需要分配时,先分配到年轻代的Eden区,等到Eden 区域内存不够时,Java 虚拟机会启动垃圾回收(GC)。此时 Eden 区中没有被引用的对象的内存就会被回收,而一些存活时间较长的对象则会进入到老年代。在JVM中-XX:MaxTenuringThreshold
参数用来设置晋升到老年代所需要经历的GC次数。即一个对象分配进来后,如果经历这么多次的GC,它都还没有被作为垃圾回收,也就是一直有被引用,那么这个对象到指定的GC次数之后就会晋升到老年代。
PC寄存器保存的是某个线程当前正在执行的方法,由于一个线程在某一时刻执行的方法只有唯一一个,而这个方法被叫做该线程的当前方法。
在JVM中除了这几个内存外,其实还有直接内存、栈帧等,但用的比较少。
问:为什么给对象分配空间也需要分为年轻代和老年代呢?意义是什么?
根据经验,有些对象的存活时间很长,而有些对象的存活时间很短,如果我们把它们混在一起,那么必然会导致有部分对象一直被扫描,但又一直不是垃圾,这就很浪费时间。那采取的措施就是扫描若干次之后,某个对象仍然不是垃圾,那就把它移动到老年区。
问:Eden:from:to分区的比例是多少?
默认的虚拟机配置是 Eden:from :to = 8:1:1。这是IBM公司统计的结果,他们发现80%的对象存活的时间都很短,于是将Eden区设置为80%。
问:什么是native方法?
看以下文章:https://blog.csdn.net/qq_23501635/article/details/78902721
JVM 虚拟机执行 class 字节码的过程可以分为七个阶段:加载、验证、准备、解析、初始化、使用、卸载。
cafe babe
开头,主次版本号是否在当前虚拟机处理范围之内等。我们都在说回收垃圾,那么到底什么是垃圾?
事实上,如果一个对象不可能再被引用,那么这个对象就是垃圾,应该被回收。
那么怎么找到垃圾并回收呢?
首先我们会想到,用计数的方式来判断。即当一个对象被引用时计数加一,被去除引用时减一。这样,当计数为0时,我们就认为是垃圾。
这种方法有一个弊端,就是当A 引用了 B,B 引用了 C,C 引用了 A,它们各自的引用计数都为 1。但是它们三个对象却从未被其他对象引用,只有它们自身互相引用。从垃圾的判断思想来看,它们三个确实是不被其他对象引用的,但是此时它们的引用计数却不为零。这就是引用计数法存在的循环引用问题。
所以现在Java虚拟机使用的是GC Root Tracing 算法。其大概的过程是这样:从 GC Root 出发,所有可达的对象都是存活的对象,而所有不可达的对象都是垃圾,最后形成一个被引用对象集合。
那么拥有了这种算法之后,如何回收垃圾呢?
这个时候就要用到垃圾回收算法了,主要有三种:
三者比较:
标记清除算法:会产生内存碎片,但是不需要移动太多对象,比较适合在存活对象比较多的情况。
复制算法:虽然需要将内存空间折半,并且需要移动存活对象,但是其清理后不会有空间碎片,比较适合存活对象比较少的情况。
标记压缩算法:标记清除算法的优化版,减少了空间碎片。
综上所述:每种算法都有自己的优缺点,最好的方法当然是分情况灵活使用它们。而其实JVM虚拟机正是如此。因此,出现了分代算法。
所谓分代算法,就是根据 JVM 内存的不同内存区域,采用不同的垃圾回收算法。
(举个例子:老年代中对象的存活率几乎可以是100%,这个时候如果使用复制算法,工作量巨大!而对于新生代来说,很多对象都是没有被引用的垃圾,所以适合使用复制算法。因此,像前面说到的,新生代是有分区的,即Eden 区域、from 区域、to 区域,并且比例是8:1:1,那么为什么要这么分呢?实际上前面已经讲过,因为很多对象都是垃圾,所以复制之后的对象其实很少,所以我们先在Eden 区域、from 区域使用GC算法,并将存活对象复制到to区域,然后删除Eden 区域、from 区域的所有对象,最后,交换from和to区域的角色并等待下一次GC)
实际上,除了分代的概念,还有分区思想。即将整个堆空间划分成连续的不同小区间,每一个小区间都独立使用,独立回收,这种算法的好处是可以控制一次回收多少个区间,可以较好地控制 GC 时间。
Java 虚拟机的垃圾回收器可以分为四大类别:
串行回收器
特点:单线程,在并发能力较弱的计算机上,性能较好,会触发 Stop-The-World 现象,即其他线程都需要暂停,等待垃圾回收完成。
开启命令:
-XX:UseSerialGC
:新生代、老年代都使用串行回收器-XX:UseParNewGC
:新生代使用 ParNew 回收器,老年代使用串行回收器-XX:UseParallelGC
:新生代使用 ParallelGC 回收器,老年代使用串行回收器分类:
新生代串行回收器
特点:最古老的一种、 JDK 中最基本的垃圾回收器之一
算法:复制算法
老年代串行回收器
特点:
算法:标记压缩算法
并行回收器
-XX:+UseParNewGC
:新生代使用 ParNew 回收器,老年代使用串行回收器。-XX:UseConcMarkSweepGC
:新生代使用 ParNew 回收器,老年代使用 CMS。-XX:ParallelGCThreads
:指定 ParNew 回收器的工作线程数量。-XX:+UseParallelGC
:新生代使用 Parallel 回收器,老年代使用串行回收器。-XX:+UseParallelOldGC
:新生代使用 ParallelGC 回收器,老年代使用 ParallelOldGC 回收器。CMS 回收器
G1 回收器
特点:是 JDK 1.7 中使用的全新垃圾回收器,依然使用了分代垃圾回收,但增加了分区算法,从而使得Eden 区、From 区、Survivor 区和老年代等各块内存不必连续。
目的:为了取代CMS回收器
开启命令:
打开 G1 收集器,我们可以使用参数:-XX:+UseG1GC
设置目标最大停顿时间,可以使用参数:-XX:MaxGCPauseMillis
设置 GC 工作线程数量,可以使用参数:-XX:ParallelGCThreads
设置堆使用率触发并发标记周期的执行,可以使用参数:-XX:InitiatingHeapOccupancyPercent
工作流程:
Minor GC:从年轻代空间回收内存被称为 Minor GC,有时候也称之为 Young GC。
Major GC:从老年代空间回收内存被称为 Major GC,有时候也称之为 Old GC。
Young GC:如上
Old GC:如上
Full GC:Full GC 是清理整个堆空间 —— 包括年轻代、老年代和永久代(如果有的话)
Stop-The-World:是指在进行垃圾回收时因为标记或清理的需要,必须让所有执行任务的线程停止执行任务,从而让垃圾回收线程回收垃圾的时间间隔。
堆空间:java -Xms20m -Xmx30m GCDemo
设置 JVM 的初始堆大小为 20M,最大堆空间为 30M
年轻代:java -Xms20m -Xmn10M GCDemo
设置 JVM 堆初始大小为20M,其中年轻代的大小为 10M,剩下的自然为老年代的,有10M。
Eden区: java -Xms20m -Xmn10M -XX:SurvivorRatio=2 -XX:+PrintGCDetails GCDemo
我们在前面说过,年轻代分为eden 空间、from 空间、to 空间。这里我们设置堆初始大小为 20M,年轻代大小为 10M,年轻代的 SurvivorRatio 比例为 2,意思是eden/from=eden/to=2。那么最终分配的结果将会是:年轻代 10M,其中 Eden 区 5M、From 区 2.5M、To 区 2.5 M,老年代 10M。
永久代:java -XX:PermSize10m -XX:MaxPermSize50m -XX:+PrintGCDetails GCDemo
设置永久代初始大小为 10M,最大大小为 50M。
元空间:java -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=50m -XX:+PrintGCDetails GCDemo
设置的是元空间发生 GC 的初始阈值为10M,设置元空间的最大大小为50M。
栈空间:java -Xss2m GCDemo
设置最大栈空间为 2M
直接内存:java -XX:MaxDirectMemorySize=50m GCDemo
设置直接内存最大值为 50M,默认为最大堆空间
程序运行时,打印虚拟机接收到的命令行显式参数 -XX:+PrintVMOptions
输入命令:
java -XX:+UseSerialGC -XX:+PrintVMOptions Demo
运行结果:
VM option '+UseSerialGC'
VM option '+PrintVMOptions'
Hello, I'm chenshuyi
程序运行时,打印传递给虚拟机的显式和隐式参数 -XX:+PrintCommandLineFlags
输入命令:
java -XX:+UseSerialGC -XX:+PrintCommandLineFlags Demo
运行结果:
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
Hello, I'm chenshuyi
程序运行时,打印所有系统参数-XX:+PrintFlagsFinal
输入命令:
java -XX:+UseSerialGC -XX:+PrintFlagsFinal Demo > jvm_flag_final.txt
运行结果放在了jvm_flag_final.txt 文件,打开后部分内容如下:
...
uintx InitialHeapSize := 134217728 {
product}
...
uintx MaxMetaspaceSize = 18446744073709547520 {
product}
...
uintx MetaspaceSize = 21807104 {
pd product}
跟踪类的加载和卸载-verbose:class
输入以下命令:
java -verbose:class Demo > class_load_info.txt
打开 class_load_info.txt 文件
...省略...
[Loaded java.util.ArrayList from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
...省略...
[Loaded java.util.HashMap from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
...省略...
[Loaded com.chenshuyi.ClassLoadDemo from file:/Users/yurongchan/Yosemite/Code/practice/target/classes/]
...省略...
跟踪类的加载-XX:+TraceClassLoading
跟踪类的卸载-XX:+TraceClassUnloading
Java 虚拟机的GC(Garbage Collection)日志系统。
参数 | 含义 |
---|---|
-XX:PrintGC | 打印GC日志 |
-XX:+PrintGCDetails | 打印详细的GC日志。还会在退出前打印堆的详细信息。 |
-XX:+PrintHeapAtGC | 每次GC前后打印堆信息。 |
-XX:+PrintGCTimeStamps | 打印GC发生的时间。 |
-XX:+PrintGCApplicationConcurrentTime | 打印应用程序的执行时间 |
-XX:+PrintGCApplicationStoppedTime | 打印应用由于GC而产生的停顿时间 |
-XX:+PrintReferenceGC | 跟踪软引用、弱引用、虚引用和Finallize队列。 |
-XLoggc | 将GC日志以文件形式输出。 |
查看虚拟机进程:jps 命令
虚拟机统计信息:jstat 命令
查看虚拟机参数:jinfo 命令
导出堆到文件:jmap 命令
堆分析工具:jhat 命令
查看线程堆栈:jstack 命令
远程主机信息收集:jstatd 命令
多功能命令行:jcmd 命令
性能统计工具:hprof 命令