JVM

目录

1、内存模型

1.1、程序计数器

1.2、native方法

1.3、VM Stack

1.4、本地方法栈

1.5、堆

1.6、方法区

1.7、对象内布局

2、垃圾回收

2.1、回收机制

2.2、GC Root

2.3、常见GC回收算法

2.4、垃圾回收器

2.5、JVM命令

2.6、元空间

2.7、垃圾回收器搭配

2.8、JVM参数

2.9、何时触发GC


1、内存模型

1.1、程序计数器

Program Counter Register 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。(概念模型中:字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令)

线程私有:多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。

状态:①执行java方法,计数器记录虚拟机字节码指令的地址;②执行native方法,计数器为空(undefined)

唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。

注意:Java线程总是需要以某种形式映射到OS线程上。映射模型可以是1:1(原生线程模型)、n:1(绿色线程 / 用户态线程模型)、m:n(混合模型)。以HotSpot VM的实现为例,它目前在大多数平台上都使用1:1模型,也就是每个Java线程都直接映射到一个OS线程上执行。此时,native方法就由原生平台直接执行,并不需要理会抽象的JVM层面上的“pc寄存器”概念——原生的CPU上真正的PC寄存器是怎样就是怎样。就像一个用C或C++写的多线程程序,它在线程切换的时候是怎样的,Java的native方法也就是怎样的。

1.2、native方法

简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。用于跨平台支持。

1.3、VM Stack

Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法;

在栈帧中包括:局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)(运行时常量池的概念在方法区部分会谈到)、方法返回地址(Return Address)和一些额外的附加信息;

1.4、本地方法栈

本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

1.5、堆

Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。另外,堆是被所有线程共享的,在JVM中只有一个堆。

内存分配:优先分配到eden区、大对象,直接进入到老年代、长期存活的对象分配到老年代、空间分配担保

空间分配担保:在JVM的内存分配时,也有这样的内存分配担保机制。就是当在新生代无法分配内存的时候,把新生代的对象转移到老生代,然后把新对象放入腾空的新生代

新生代:Eden区和Survivor区的比例是8:1:1,当GC线程启动时,会通过可达性分析法把Eden区和From Space区的存活对象复制到To Space区,然后把Eden Space和From Space区的对象释放掉。当GC轮训扫描To Space区一定次数后,把依然存活的对象复制到老年代,然后释放To Space区的对象

1.6、方法区

方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

  • 方法区是规范层面的东西,规定了这一个区域要存放哪些东西
  • 永久带或者是metaspace(元空间)是对方法区的不同实现,是实现层面的东西。

1.7、对象内布局

对象头:包含两部分数据,一部分是运行时数据,包含了对象的hash值,GC分代年龄,锁状态,线程持有的锁,偏向锁等信息
一部分是对象的类型指针,虚拟机通过这个指针确定对象是那个类的实例,如果是数组,还必须包括数据的长度信息

实例数据:接下来就是对象的成员变量的内容,包括了从父类继承下来的内容

对齐补白:JVM要求Java对象的起始地址必须是8的倍数,所以这部分内容用来对齐

2、垃圾回收

2.1、回收机制

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因此这几个区域的内存分配和回收都具备确定性。

Java堆区和方法区则不一样,这部分内存的分配和回收是动态的,正是垃圾收集器所需关注的部分。

2.2、GC Root

可达性分析法中,从GC Root出发,不可达的是可以被回收的对象。

  • Java虚拟机栈局部变量表中引用对象。
  • 本地方法栈JNI中引用的对象。
  • 方法区中类静态变量引用的对象。
  • 方法区中常量引用的对象。

2.3、常见GC回收算法

引用

2.4、垃圾回收器

新生代的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器

老年代的垃圾收集器有:Serial Old收集器、Parallel Old收集器、CMS收集器、G1收集器

  • Serial 收集器/Serial Old 收集器:是单线程的,使用“复制”算法。当它工作时,必须暂停其它所有工作线程。特点:简单而高效。对于运行在Client模式下的虚拟机来说是一个很好的选择
  • ParNew 收集器:是Serial收集器的多线程版。是运行在Server模式下的虚拟机中首选的新生代收集器,但它不是默认收集器。除了Serial收集器外,目前只有它能与CMS收集器配合工作
  • Parallel Scavenge 收集器:也是使用“复制”算法的、并行的多线程收集器。这些都和ParNew收集器一样。但它关注的是吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值),而其它收集器(Serial/Serial Old、ParNew、CMS)关注的是垃圾收集时用户线程的停顿时间
  • Parallel Old 收集器:是Parallel Scavenge收集器的老年代版本
  • CMS 收集器:是一种以获取最短回收停顿时间为目标的收集器,从Mark-Sweep上可以看出,CMS是基于“标记-清除”算法实现的。缺点:吞吐量较低
  • G1 收集器:基于“标记-整理”算法,可以非常精确地控制停顿。特点:空间整合:基于标记-整理和复制,不会产生内存空间碎片;可预测的停顿:也可以并发执行。

2.5、JVM命令

  • JPS(JVM process Status)
  • JSTAT(JVM Statistics Monitoring Tool)监视虚拟机信息 
  • JMAP(Memory Map for Java)查看堆内存信息 
  • Jconsole、Jvisualvm分析内存信息(各个区如Eden、Survivor、Old等内存变化情况)
  • Jhat(JVM Heap Analysis Tool) 命令来分析内存快照

2.6、元空间

JDK8之后代替了永久代,为什么移除持久代

  • 它的大小是在启动时固定好的——很难进行调优。-XX:MaxPermSize,设置成多少好呢?
  • HotSpot的内部类型也是Java对象:它可能会在Full GC中被移动,同时它对应用不透明,且是非强类型的,难以跟踪调试,还需要存储元数据的元数据信息(meta-metadata)。
  • 简化Full GC:每一个回收器有专门的元数据迭代器。
  • 可以在GC不进行暂停的情况下并发地释放类数据。
  • 使得原来受限于持久代的一些改进未来有可能实现

-XX:MaxMetaspaceSize=n 设置元空间的最大值

2.7、垃圾回收器搭配

 吞吐量优先的并行收集器
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20: 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC: 配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100 :设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC  -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

 响应时间优先的并发收集器
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction: 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

2.8、JVM参数

  • -Xms:初始堆大小,默认物理内存的1/64
  • -Xmx:最大堆大小,默认物理内存的1/4
  • -Xmn:新生代内存大小,官方推荐为整个堆的3/8
  • -Xss:线程堆栈大小,jdk1.5及之后默认1M,之前默认256k
  • -XX:NewRatio=n:设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • -XX:SurvivorRatio=n:设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
  • -XX:PermSize=n:永久代初始值,默认为物理内存的1/64
  • -XX:MaxPermSize=n:永久代最大值,默认为物理内存的1/4
  • -XX:+PrintGC:打印GC日志
  • -XX:+PrintGCDetails:打印详细GC日志
  • -XX:MaxMetaspaceSize=n 设置元空间的最大值
java -Xmx15G \
-Xms10G \
-Xmn3G \
-Xss512k \
-XX:MaxPermSize=512M \
-XX:PermSize=512M \
-XX:+PrintFlagsFinal \
-XX:MaxTenuringThreshold=1 \
-XX:SurvivorRatio=23 \
-XX:TargetSurvivorRatio=80 \
-Xnoclassgc \
-XX:+UseParNewGC \
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=80 \
-XX:ParallelGCThreads=24 \
-XX:ConcGCThreads=24 \
-XX:+CMSParallelRemarkEnabled \
-XX:+CMSScavengeBeforeRemark \
-XX:+ExplicitGCInvokesConcurrent \
-XX:+UseTLAB \
-XX:TLABSize=64K, -verbose:gc \
-XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintGCTimeStamps \
-XX:+PrintGCApplicationStoppedTime \
-Xloggc:./gc.log

Java HotSpot VM的官方文档中将启动参数分为如下两类:

配置 参数类型说明举例
-Xnon-standard非标准参数。<br/><br/>这些参数不是虚拟机规范规定的。因此,不是所有VM的实现(如:HotSpot,JRockit,J9等)都支持这些配置参数。-Xmx、-Xms、-Xmn、-Xss
-XXnot-stable不稳定参数。<br/><br/>这些参数是虚拟机规范中规定的。这些参数指定虚拟机实例在运行时的各种行为,从而对虚拟机的运行时性能有很大影响。-XX:SurvivorRatio、-XX:+UseParNewGc

补充: -X和-XX两种参数都可能随着JDK版本的变更而发生变化,有些参数可以能会被废弃掉,有些参数的功能会发生改变,但是JDK官方不会通知开发者这些变化,需要使用者注意。

-XX参数被称为不稳定参数,是因为这类参数的设置会引起JVM运行时性能上的差异,配置得当可以提高JVM性能,配置不当则会使JVM出现各种问题, 甚至造成JVM崩溃。

一些有用的-XX配置:对于-XX类型的配置选项,虚拟机规范有一些惯例,针对不同的平台虚拟机也会提供不同的默认值。

  • 对于布尔(Boolean)类型的配置选项,通过-XX:+<option>来开启,通过-XX:-<option>来关闭。
  • 对于数字(Numberic)类型的配置选项,通过-XX:<option>=<number>来配置。<number>后面可以携带单位字母,比如: 'k'或者'K'代表千字节,'m'或者'M'代表兆字节,'g'或者'G'代表千兆字节。
  • 对于字符串(String)类型的配置选项,通过-XX:<option>=<string>来配置。这种配置通过用来指定文件,路径或者命令列表。

2.9、何时触发GC

Minor GC的触发条件为:当产生一个新对象,新对象优先在Eden区分配。如果Eden区放不下这个对象,虚拟机会使用复制算法发生一次Minor GC,清除掉无用对象,同时将存活对象移动到Survivor的其中一个区(fromspace区或者tospace区)。虚拟机会给每个对象定义一个对象年龄(Age)计数器,对象在Survivor区中每“熬过”一次GC,年龄就会+1。待到年龄到达一定岁数(默认是15岁),虚拟机就会将对象移动到年老代。如果新生对象在Eden区无法分配空间时,此时发生Minor GC。发生MinorGC,对象会从Eden区进入Survivor区,如果Survivor区放不下从Eden区过来的对象时,此时会使用分配担保机制将对象直接移动到年老代。

我们知道在HotSpot虚拟机中存在三种垃圾回收现象,minor GC、major GC和full GC。对新生代进行垃圾回收叫做minor GC,对老年代进行垃圾回收叫做major GC,同时对新生代、老年代和永久代进行垃圾回收叫做full GC。许多major GC是由minor GC触发的,所以很难将这两种垃圾回收区分开。major GC和full GC通常是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是major GC。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值