JVM总结

一.Java虚拟机

1.JVM是Java的基石,JVM是JRE的一部分

2.JRE是Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,一般JDK中包含JRE。

3.JDK是面向开发人员使用的SDK。

三种JVM

① Sun公司的HotSpot;

② BEA公司的JRockit;

③ IBM公司的J9 JVM;

在JDK1.7及其以前我们所使用的都是Sun公司的HotSpot,但由于Sun公司和BEA公司都被oracle收购,jdk1.8将采用Sun公司的HotSpot和BEA公司的JRockit两个JVM中精华形成jdk1.8的JVM。



java的运行过程

        java代码源文件(.java文件)经过java的编译器编译成了java字节码文件(.class文件),然后被装入内存中,然后被java的解释器和jit(即时编译)编译成机器码,然后被运行。

        运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。

        早在Java1.0版本的时候,Sun公司发布了一款名为Sun Classic VM的Java虚拟机,它同时也是世界上第一款商用Java虚拟机,在当时这款虚拟机内部只提供解释器,用今天的眼光来看待必然是效率低下的,因为如果Java虚拟机只能够在运行时对代码采用逐行解释执行,程序的运行性能可想而知。但是如今的HotSpot VM中不仅内置有解释器,还内置有先进的JIT(Just In Time Compiler)编译器,在Java虚拟机运行时,解释器和即时编译器能够相互协作,各自取长补短。在此大家需要注意,无论是采用解释器进行解释执行,还是采用即时编译器进行编译执行,最终字节码都需要被转换为对应平台的本地机器指令。或许有些开发人员会感觉到诧异,既然HotSpot VM中已经内置JIT编译器了,那么为什么还需要再使用解释器来“拖累”程序的执行性能呢?比如JRockit VM内部就不包含解释器,字节码全部都依靠即时编译器编译后执行,尽管程序的执行性能会非常高效,但程序在启动时必然需要花费更长的时间来进行编译。对于服务端应用来说,启动时间并非是关注重点,但对于那些看中启动时间的应用场景而言,或许就需要采用解释器与即时编译器并存的架构来换取一个平衡点。


1、解释器

JVM可以加载字节码即.class文件,然后边翻译边执行,因而被称为解释型编程语言(但是解释的过程就是编译一条机器码执行一条,且JVM中存在即时编译器编译热点代码,所以也被称为半解释半执行的编程语言)

2、即时编译(Jit)

JVM中还存在着即时编译器优化代码执行,HotSpot中的即时编译器分为client模式与server模式,又称为c1、c2编译器(jdk1.7默认server模式),他会检测代码中的热点代码(即多次调用的方法或循环的代码块),这些代码如果每次都通过解释器解释执行无疑大大降低了运行效率,因此Jit编译器将他们编译成本地代码,则下次调用时就不需要解释器再次解释执行。

Jit编译器检测热点代码:

1、方法计数器:记录方法调用的次数 

2、回边计数器:记录代码块循环次数 

当计数器数值大于默认阈值或指定阈值时,方法或代码块会被编译成本地代码。 



java代码的编译过程



编译成字节码后执行

java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,最后再转换为不同平台的机器码,最终得到执行。

二、JVM的内存结构


控制参数

· -Xms设置堆的最小空间大小。

· -Xmx设置堆的最大空间大小。

· -XX:NewSize设置新生代最小空间大小。

· -XX:MaxNewSize设置新生代最大空间大小。

· -XX:PermSize设置永久代最小空间大小。

· -XX:MaxPermSize设置永久代最大空间大小。

· -Xss设置每个线程的堆栈大小。


JVM的内存结构(Java的运行时数据区)有五个主要结构:

线程独享:

程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;

Java 虚拟机栈(Java Virtual Machine Stacks):里面的栈帧存储着:用于存储局部变量表、操作数栈、动态链接、方法出口等信息;

本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;

线程共享:

Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存,

方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。


JVM内存结构



1)堆:

堆是占用内存最大的一块,储存所有的对象实例,它有着自己的

复制算法:

使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的活动区间与另外80%中存活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部释放,以此类推。 


堆结构

年轻代:

1:eden、servicorFrom 复制到 ServicorTo,年龄+1

首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);

2:清空 eden、servicorFrom

然后,清空 Eden 和 ServicorFrom 中的对象;

3:ServicorTo 和 ServicorFrom 互换

最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom

区。

老年代

放置长生命周期的对象,通常都是从Survivor区域拷贝过来的对象。当然,也有特殊情况,我们知道普通的对象会被分配在TLAB上;如果对象较大,JVM会试图直接分配在Eden其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代。

永久代

这部分就是早期Hotspot JVM的方法区实现方式了,储存Java类元数据、常量池、Intern字符串缓存,在JDK 8之后就不存在永久代这块儿了。

2)本地方法栈

结构图

①栈里面主要有很多歌线帧,每一个线帧中又有自己的:

局部变量表、操作栈、动态链接和返回地址。

②若单个线程请求的栈深度大于虚拟机允许的深度,则会抛出StackOverflowError(栈溢出错误)。

OutOfMemoryError指的是当整个虚拟机栈内存耗尽,并且无法再申请到新的内存时抛出的异常。

JVM会为每个线程的虚拟机栈分配一定的内存大小(-Xss参数),因此虚拟机栈能够容纳的栈帧数量是有限的,若栈帧不断进栈而不出栈,最终会导致当前线程虚拟机栈的内存空间耗尽,典型如一个无结束条件的递归函数调用。


③java虚拟机栈也是线程私有的,虚拟机栈描述的是java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。

局部变量表:存放了编译期可知的各种基本数据类型、对象引用和returnAddress(指向了一条字节码指令的地址)。

操作栈:存放了用于计算和操作的数值和符号,将他们放置在一个栈中(入栈),然后计算的时候将他们取出(弹栈),然后交给CPU计算。

动态链接:

返回地址:存储返回值。

三、常见的垃圾收集器

1.Serial 收集器

这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。



2.ParNew 收集器

可以认为是 Serial 收集器的多线程版本。

3.Parallel Scavenge 收集器

这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。

作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

4.CMS 收集器

CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除 算法实现。

运作步骤:

初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象

并发标记(CMS concurrent mark):进行 GC Roots Tracing

重新标记(CMS remark):修正并发标记期间的变动部分

并发清除(CMS concurrent sweep)

5.G1 收集器

面向服务端的垃圾回收器。是Oracle JDK 9以后的默认GC选项

优点:并行与并发、分代收集、空间整合、可预测停顿。

运作步骤:

初始标记(Initial Marking)

并发标记(Concurrent Marking)

最终标记(Final Marking)

筛选回收(Live Data Counting and Evacuation)


四、垃圾回收算法

1.复制(Copying)算法

我前面讲到的新生代GC,基本都是基于复制算法,过程就如专栏上一讲所介绍的,将活着的对象复制到to区域,拷贝过程中将对象顺序放置,就可以避免内存碎片化。

这么做的代价是,既然要进行复制,既要提前预留内存空间,有一定的浪费;另外,对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,这个开销也不小,不管是内存占用或者时间开销。

2.标记-清除(Mark-Sweep)算法

首先进行标记工作,标识出所有要回收的对象,然后进行清除。这么做除了标记、清除过程效率有限,另外就是不可避免的出现碎片化问题,这就导致其不适合特别大的堆;否则,一旦出现Full GC,暂停时间可能根本无法接受。

3.标记-整理(Mark-Compact)

类似于标记-清除,但为避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。


五、JVM调优

1).类的生命周期

流程图

1.加载:将.class文件从磁盘读取到内存

2.连接:

①验证:验证字节码的正确性

②准备:给类的静态变量分配内存,并赋予默认值

③解析:类装载器装入类所引用的其他所有类

3.初始化

为类的静态变量赋值正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的默认值,此处赋予的才是程序编写者为变量分配的真正的初始值,执行静态代码块。

4.使用

5.卸载

2)类加载器的种类

启动类加载器(Bootstrap  ClassLoader):负责加载JRE的核心类库,如JRE目标下的rt.jar,charsets.jar等。

扩展类加载器(Extension ClassLoader):负责加载jre扩展目录ext中的jar

系统类加载器(Application ClassLoader):负责加载classpath路径下的类包

用户自定义加载器(User ClassLoader):负责加载用户自定义路径下的类包


向上委托的关系

3)类加载机制

1.全盘负责委托机制

当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入。

例如,系统类加载器AppClassLoader加载入口类(含有main方法的类)时,会把main方法所依赖的类及引用的类也载入,依此类推。“全盘负责”机制也可称为当前类加载器负责机制。显然,入口类所依赖的类及引用的类的当前类加载器就是入口类的类加载器。

2.双亲委派机制(模型)parent delegate model

指先委托父加载器寻找目标类,在找不到的情况下在自己的路径中查找并载入目标类

双亲委派模式的优点:

沙箱安全机制:比如自己写的String.class类不会被加载,这样可以防止核心库被随意篡改

避免类的重复加载:当父ClassLoader已经加载了该类的时候,就不需要子ClassLoader再加载一次

如何打破双亲委派机制

Tomcat就打破了双亲委派机制,SPI Service Provider interface 服务提供者接口

SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader,Java提供接口,由厂商去实现。

4)JVM调优

JVM调优主要就是调整下面两个指标:

停顿时间:垃圾收集器做垃圾回收中断应用执行的时间。-XX:MaxGCPauseMillis

吞吐量:垃圾收集的时间和总时间的占比:1/(1+n),吞吐量为1-1/(1+n)。-XX:GCTimeRatio=n

可调优策略:

第一次调优,设置Metaspace大小:增大元空间大小 ()-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

第二次调优,增大年轻代动态扩容增量(默认是20%,增加到30%),可以减少YGC的执行次数: -XX:YoungGenerationSizeIncrement=30

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Java 虚拟机(Java virtual machine,JVM)是运行 Java 程序必不可少的机制。JVM实...
    Rick617阅读 856评论 0 0
  • 1. 大概内容 内存区域 内存泄漏和内存溢出 类型擦除 对象创建,分配和访问 GC的判定 GC实现方法 类加载过程...
    yufeiyang1995阅读 309评论 0 0
  • 内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的...
    wustor阅读 371评论 0 2
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,062评论 1 34
  • 第二部分 自动内存管理机制 第二章 java内存异常与内存溢出异常 运行数据区域 程序计数器:当前线程所执行的字节...
    小明oh阅读 1,130评论 0 2