JVM
JVM

jvm内存区域

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA
堆、方法区】、直接内存。
-线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 HotspotVM 内, 每个
线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。
线程共享区域随虚拟机的启动/关闭而创建/销毁。

1.程序计数器( 线程私有)
一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数
器,这类内存也称为“线程私有”的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是
Native 方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任OutOfMemoryError 情况的区域。
2.JAVA虚拟机栈( 线程私有)
是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局
部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一
个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic
Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而
销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
3.本地方法区(线程私有)
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法
服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot
VM 直接就把本地方法栈和虚拟机栈合二为一。
4.堆(Heap- 线程共享)运行时数据区
是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收
集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生
代( Eden 区 、 From Survivor 区 和 To Survivor 区 )和老年代(jdk1.7)。
5.方法区/ 永久代 (线程共享)
即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编
译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区,
这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管
理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)。
运行时常量池
(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述
等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引
用,这部分内容将在类加载后存放到方法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(自
然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样
才会被虚拟机认可、装载和执行。
6.直接内存
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于
Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对
象作为这块内存的引用进行操作, 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景
中可以显著提高性能。
jvm运行时内存(jdk1.7)
Java 堆从 GC 的角度还可以细分为: 新生代( Eden 区 、 From Survivor 区 和 To Survivor 区 )和老年代

1.新生代
是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进
行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内
存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收。
ServivorFrom:上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
ServivorTo:保留了一次 MinorGC 过程中的幸存者。
MinorGC 的过程:(复制->清空->互换)MinorGC 采用复制算法。
eden 、 servicorFrom 复制到 ServicorTo,年龄+1
首先,把 Eden和 ServivorFrom区域中存活的对象复制到 ServicorTo区域(如果有对象的年龄以及达到了老年的(默认15岁,可以通过-XXMaxTenuringThreshold设置),则赋值到老年代区),同时把这些对象
的年龄+1(如果 ServicorTo 不够位置了就放到老年区)。
清空 eden 、 servicorFrom**
清空 Eden 和 ServicorFrom 中的对象;
ServicorTo 和 ServicorFrom 互换
最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。
2.老年代
主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间
分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。
MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般
需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of
Memory)异常。
元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,
元空间的大小仅受本地内存限制。类的元数据放入 nativememory, 字符串池和类的静态变量放入 java 堆
中,这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。
3.老年代(元空间)
-指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区
域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的
区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。
垃圾回收与算法

如何确定垃圾
1.引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法
是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他
们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
2.可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为
起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可
达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可
回收对象,则将面临回收。
在Java, 可作为GC Roots的对象包括:
方法区: 类静态属性引用的对象;
方法区: 常量引用的对象;
虚拟机栈(本地变量表)中引用的对象.
本地方法栈JNI(Native方法)中引用的对象。
算法
1.标记清除算法( Mark-Sweep )
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回
收被标记的对象所占用的空间。如图

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间
的问题。
2.复制算法(copying )
为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。
每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如
图:

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一
半。且存活对象增多的话,Copying 算法的效率会大大降低。
3.标记整理算法(Mark-Compact)
结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,
而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

但是效率低。
4.分代收集算法
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为
不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生(YoungGeneration)。老生
代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要
被回收,因此可以根据不同区域选择不同的算法。
1)新生代与复制算法
目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对
象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代划分为一块较大的
Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块
Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。

2)老年代与标记整理算法
而老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。
JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类,常量,方法描
述等。对永生代的回收主要包括废弃常量和无用的类。
对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的
那一块),少数情况会直接分配到老生代。
当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,EdenSpace 和 From
Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 FromSpace 进行清理。
如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。
当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代
中。
5.分区算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以
控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少
一次 GC 所产生的停顿。
补充: 空间分配担保
在执行Minor GC前, VM会首先检查老年代是否有足够的空间存放新生代尚存活对象, 由于新生代使用复制
收集算法, 为了提升内存利用率, 只使用了其中一个Survivor作为轮换备份, 因此当出现大量对象在Minor
GC后仍然存活的情况时, 就需要老年代进行分配担保, 让Survivor无法容纳的对象直接进入老年代, 但前
提是老年代需要有足够的空间容纳这些存活对象. 但存活对象的大小在实际完成GC前是无法明确知道的,
因此Minor GC前, VM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小, 如
果条件成立, 则进行Minor GC, 否则进行Full GC(让老年代腾出更多空间).
然而取历次晋升的对象的平均大小也是有一定风险的, 如果某次Minor GC存活后的对象突增,远远高于平均
值的话,依然可能导致担保失败(Handle Promotion Failure, 老年代也无法存放这些对象了), 此时就只好在
失败后重新发起一次Full GC(让老年代腾出更多空间).
GC 垃圾收集器
Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收 算法 ,年老代主
要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集
器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

新生代:
- Serial收集器
Serial收集器是Hotspot运行在Client模式下的默认新生代收集器, 它的特点是 只用一个CPU/一条收集线程
去完成GC工作, 且在进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World” -后面简称STW).

虽然是单线程收集, 但它却简单而高效, 在VM管理内存不大的情况下(收集几十M~一两百M的新生代), 停
顿时间完全可以控制在几十毫秒~一百多毫秒内.
- ParNew收集器
ParNew收集器其实是前面Serial的多线程版本, 除使用多条线程进行GC外, 包括Serial可用的所有控制参
数、收集算法、STW、对象分配规则、回收策略等都与Serial完全一样(也是VM启用CMS收集器-XX: +UseConcMarkSweepGC的默认新生代收集器).

由于存在线程切换的开销, ParNew在单CPU的环境中比不上Serial, 且在通过超线程技术实现的两个CPU的
环境中也不能100%保证能超越Serial. 但随着可用的CPU数量的增加, 收集效率肯定也会大大增加(ParNew
收集线程数与CPU的数量相同, 因此在CPU数量过大的环境中, 可用-XX:ParallelGCThreads参数控制GC线
程数).
- Parallel Scavenge收集器
与ParNew类似, Parallel Scavenge也是使用复制算法, 也是并行多线程收集器. 但与其他收集器关注尽可能
缩短垃圾收集时间不同, Parallel Scavenge更关注系统吞吐量:
系统吞吐量=运行用户代码时间(运行用户代码时间+垃圾收集时间)
停顿时间越短就越适用于用户交互的程序-良好的响应速度能提升用户的体验;而高吞吐量则适用于后台运
算而不需要太多交互的任务-可以最高效率地利用CPU时间,尽快地完成程序的运算任务. Parallel Scavenge
提供了如下参数设置系统吞吐量:

老年代:
- Serial Old收集器
Serial Old是Serial收集器的老年代版本, 同样是单线程收集器,使用“标记-整理”算法:
JDK 1.5之前与Parallel Scavenge收集器搭配使用;
作为CMS收集器的后备预案, 在并发收集发生Concurrent Mode Failure时启用(见下:CMS收集器).
- Parallel Old收集器
Parallel Old是Parallel Scavenge收老年代版本, 使用多线程和“标记-整理”算法, 吞吐量优先, 主要与Parallel Scavenge配合在 注重吞吐量 及 CPU资源敏感 系统内使用:

- CMS收集器
是一种以获取最短回收停顿时间为目标的收集器,适合应用在互联网站或者 B/S 系统的服务器上,这个类
应用尤其重视服务器的响应速度,希望系统停顿时间最短。
CMS 非常适合堆内存大、CPU 核数多的服务器端应用,也是 G1 出现之前大型应用首选收集器。
并发停顿比较少,并发指的是与用户线程一起执行。
过程
1.初始标记(initail mark):只是标记一下 GC Roots 能直接关联的对象,速度很快,需要暂停所有的工
作线程
2.并发标记(concurrent mark 和用户线程一起):进行 GC Roots 的跟踪过程,和用户线程一起工作,不
需要暂停工作线程。
3.重新标记(remark):为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分
对象的标记记录,仍然需要暂停所有的工作线程。
4.并发清除(concurrent sweep 和用户线程一起):清除 GC 不可达对象,和用户线程一起工作,不需要
暂停工作线程,基于标记结果,直接清除。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程和
用户线程可以一起并发工作,所以总体来看 CMS 收集器的内存回收和用户线程是一起并发地执行。
优缺点:
优点:并发收集停顿低
缺点:并发执行对 CPU 资源压力大,采用的标记清除算法会导致大量碎片
由于并发进行, CMS 在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS 必须要在老年代堆
用尽之前完成垃圾回收,否者 CMS 回收失败,将触发担保机制,串行老年代收集器将会以 STW 的方式进
行一次 GC,从而造成较大的停顿时间。
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐渐耗尽,最后将不得不通过担保机制对
堆内存进行压缩。CMS 也提供了参数 -XX:CMSFullGCsBeForeCompaction (默认0,即每次都进行内存整
理) 来指定多少次 CMS 收集之后,进行一次压

- G1收集器
以前收集器的特点
年轻代和老年代是各自独立且连续的内存块
年轻代收集器使用 eden + S0 + S1 进行复制算法
老年代收集必须扫描整个老年代区域
都是以尽可能的少而快速地执行 GC 为设计原则
G1 是什么
G1 是一种面向服务端的垃圾收集器,应用在多核处理器和大容量内存环境中,在实现高吞吐量的同时,
尽可能的满足垃圾收集器的暂停时间要求。
像 CMS 收集器一样,能与应用程序线程并发执行,整理空闲空间更快,需要更多的时间来预测 GC 停顿
时间,不希望牺牲大量的吞吐性能,不需要更大的 JAVA Heap。
G1 收集器的设计目的是取代 CMS 收集器,同时与 CMS 相比,G1 垃圾收集器是一个有整理内存过程的垃
圾收集器,不会产生很多内存碎片。G1 的 Stop The World 更可控,G1 在停顿上添加了预测机制,用户可
以指定期望的停顿时间。
G1 是在 2012 年才在 jdk.1.7u4 中可以呀用,在 jdk9 中将 G1 变成默认垃圾收集器来代替 CMS。它是以款
面向服务应用的收集器。
主要改变是 Eden、Survivor 和 Tenured 等内存区域不再是连续的,而是变成了一个个大小一样的 region,每个 region 从 1M 到 32M 不等,一个 region 有可能属于 Eden、Survivor 或者 Tenured 内存区
域。
特点
G1 能充分利用多 CPU、多核环境硬件优势,尽量缩短 STW。
G1 整体采用标记-整理算法,局部是通过是通过复制算法,不会产生内存碎片。
宏观上看 G1 之中不在区分年轻代和老年代,被内存划分为多个独立的子区域。
G1 收集器里面讲整个的内存区域混合在一起,但其本身依然在小范围内要进行年轻代和老年代的区分。保留了新生代和老年代,但她们不在是物理隔离,而是一部分 Region 的集合且不需要 Region 是连续的,
也就是说依然会采用不同的 GC 方式来处理不同的区域。
G1 虽然也是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的
Survivor to space 堆做复制准备。G1 只有逻辑上的分代概念,或者说每个分区都可能随 G1 的运行在不同
代之间前后切换。
底层原理

Region 区域化垃圾收集器:最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
G1的内存结构和传统的内存空间划分有比较的不同。G1将内存划分成了多个大小相等的Region(默认是
512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示
Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。
H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。当分配的对象大于等于
Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的
内存拷贝。通过如果发现堆内存容不下H对象的时候,会触发一次GC操作。
四部过程:

查看服务器默认垃圾收集器
1.怎么查看服务器默认垃圾收集器是哪个?
Java -XX:+PrintCommandLineFlags
Java 的 GC 回收的类型主要有:
UseSerialGC,UseParallelGC,UseConcMarkSweepGC,UseParNewGC,UseParallelOldGC,UseG1GC
Java 8 以后基本不使用 Serial Old
对oom的认识
1.java.lang.StackOverflowError
在一个函数中递归调用就会产生这个错误
2.java.lang.OutOfMemoryError : Java heap space
new 一个很大对象
3.java.lang.OutOfMemoryError : GC overhead limit exceeded
执行垃圾收集的时间比例太大, 有效的运算量太小,默认情况下,,如果GC花费的时间超过 98%, 并且
GC回收的内存少于 2%, JVM就会抛出这个错误。
4.java.lang.OutOfMemoryError : Direct buffer memory
配置参数:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
public class DirectBufferDemo {
public static void main(String[] args) {
System.out.println("maxDirectMemory : " + sun.misc.VM.maxDirectMemory() / (1024 * 1024) + "MB");
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
输出
maxDirectMemory : 5MB
[GC (System.gc()) [PSYoungGen: 1315K->464K(2560K)] 1315K->472K(9728K), 0.0008907 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 464K->0K(2560K)] [ParOldGen: 8K->359K(7168K)] 472K->359K(9728K), [Metaspace: 3037K->3037K(1056768K)], 0.0060466 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.cuzz.jvm.DirectBufferDemo.main(DirectBufferDemo.java:17)
Heap
PSYoungGen total 2560K, used 56K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0e170,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 359K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 5% used [0x00000000ff600000,0x00000000ff659e28,0x00000000ffd00000)
Metaspace used 3068K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, res
5.java.lang.OutOfMemoryError : unable to create new native thread
创建线程数太多了
6.java.lang.OutOfMemoryError : Metaspace
Java 8 之后的版本使用元空间(Metaspace)代替了永久代,元空间是方法区在 HotSpot 中的实现,它与
持久代最大的区别是:元空间并不在虚拟机中的内存中而是使用本地内存。
元空间存放的信息:
虚拟机加载的类信息
常量池
静态变量
即时编译后的代码
jvm参数:

1.参数类型

1)标配参数
-version
-help
2)X 参数(了解)
-Xint:解释执行
-Xcomp:第一次使用就编译成本地代码
-Xmixed:混合模式
3)XX 参数
Boolean 类型:-XX:+ 或者 - 某个属性值(+ 表示开启,- 表示关闭)
-XX:+PrintGCDetails:打印 GC 收集细节
-XX:-PrintGCDetails:不打印 GC 收集细节
-XX:+UseSerialGC:使用了串行收集器
-XX:-UseSerialGC:不使用了串行收集器
KV 设置类型:-XX:key=value
-XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
jinfo 举例,如何查看当前运行程序的配置
public class HelloGC {
public static void main(String[] args) {
System.out.println("hello GC...");
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们可以使用 jps -l 命令,查出进程 id
1923 org.jetbrains.jps.cmdline.Launcher
1988 sun.tools.jps.Jps
1173 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
32077 com.intellij.idea.Main
1933 com.cuzz.jvm.HelloGC
32382 org.jetbrains.idea.maven.server.RemoteMavenServer
在使用 jinfo -flag PrintGCDetails 1933 命令查看
-XX:-PrintGCDetails
可以看出默认是不打印 GC 收集细节
也可是使用jinfo -flags 1933 查看所以的参数
两个经典参数:-Xms 和 - Xmx(如 -Xms1024m)
-Xms 等价于 -XX:InitialHeapSize
-Xmx 等价于 -XX:MaxHeapSize

2.查看 JVM 默认值

1)查看初始默认值:-XX:+PrintFlagsInitial
[Global flags]
intx ActiveProcessorCount = -1 {product}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
uintx AdaptiveSizePausePolicy = 0 {product}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product}
uintx AdaptiveSizePolicyOutputInterval = 0 {product}
uintx AdaptiveSizePolicyWeight = 10 {product}
...
2)查看修改更新:-XX:+PrintFlagsFinal
bool UsePSAdaptiveSurvivorSizePolicy = true {product}
bool UseParNewGC = false {product}
bool UseParallelGC := true {product}
bool UseParallelOldGC = true {product}
bool UsePerfData = true {product}
bool UsePopCountInstruction = true {product}
bool UseRDPCForConstantTableBase = false
注意:
= 与 := 的区别是,一个是默认,一个是人物改变或者 jvm 加载时改变的参数
2)打印命令行参数(可以看默认垃圾回收器):-XX:+PrintCommandLineFlags
cuzz@cuzz-pc:~/Project/demo$ java -XX:+PrintCommandLineFlags
-XX:InitialHeapSize=128789376 -XX:MaxHeapSize=2060630016 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
3.常用基本配置参数

1.-Xms
初始大小内存,默认为物理内存 1/64
等价于 -XX:InitialHeapSize
2.-Xmx
最大分配内存,默认为物理内存的 1/4
等价于 -XX:MaxHeapSize
3.-Xss
设置单个线程栈的大小,一般默认为 512-1024k
等价于 -XX:ThreadStackSize
4.-Xmn
设置年轻代的大小
整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
5.-XX:MetaspaceSize

设置元空间大小(元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现,不过元空间于永久代之间最大区别在于,元空间并不在虚拟中,而是使用本地内存,因此默认情况下,元空间的大小仅受本地内存限制)
元空间默认比较小,我们可以调大一点
6.-XX:+PrintGCDetails
输出详细 GC 收集日志信息
设置 JVM 参数为: -Xms10m -Xmx10m -XX:+PrintGCDetails
打印结果:
[GC (Allocation Failure) [PSYoungGen: 1231K->448K(2560K)] 1231K->456K(9728K), 0.0015616 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 448K->384K(2560K)] 456K->392K(9728K), 0.0016999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 384K->0K(2560K)] [ParOldGen: 8K->358K(7168K)] 392K->358K(9728K), [Metaspace: 3028K->3028K(1056768K)], 0.0066696 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 358K->358K(9728K), 0.0005321 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 358K->340K(7168K)] 358K->340K(9728K), [Metaspace: 3028K->3028K(1056768K)], 0.0051543 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 81K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd14668,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 340K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 4% used [0x00000000ff600000,0x00000000ff655188,0x00000000ffd00000)
Metaspace used 3060K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.cuzz.jvm.HelloGC.main(HelloGC.java:12)

7.-XX:SurvivorRatio

设置新生代中 eden 和 S0/S1 空间比例
默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 : 1
8.-XX:NewRatio

配置年轻代和老年代在堆结构的占比
默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
9.-XX:MaxTenuringThreshold

jvm指令

jps 看java进程
jconsole:

可以查看老年代、Eden区、survior区

jstat:
1.jstat -gc +pid 垃圾回收统计

S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
2.jstat -gccapacity +pid 堆内存统计

NGCMN:新生代最小容量
NGCMX:新生代最大容量
NGC:当前新生代容量
S0C:第一个幸存区大小
S1C:第二个幸存区的大小
EC:伊甸园区的大小
OGCMN:老年代最小容量
OGCMX:老年代最大容量
OGC:当前老年代大小
OC:当前老年代大小
MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代gc次数
FGC:老年代GC次数
3.jstat -gcnew +pid 新生代垃圾回收统计

S0C:第一个幸存区大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
TT:对象在新生代存活的次数
MTT:对象在新生代存活的最大次数
DSS:期望的幸存区大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
4.jstat -gcnewcapacity +pid 新生代内存统计
5.jstat -gcold +pid 老年代垃圾回收统计
6.jstat -gcoldcapacity+pid 老年代内存统计
7.jstat -gcpermcapacity +pid JDK7 下 永久代空间统计
8.jstat -gcmetacapacity +pid JDK8 下 元数据空间统计

MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
9.**总结垃圾回收统计 **
jstat -gcutil +pid

S0:幸存1区当前使用比例
S1:幸存2区当前使用比例
E:伊甸园区使用比例
O:老年代使用比例
M:元数据区使用比例
CCS:压缩使用比例
YGC:年轻代垃圾回收次数
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
jstack
查看线程
jmap
1.jmap -dump:file=a pid 把内存信息dump到文件a中
2.jmap -heap 打印当前堆内存信息

3.jmap -histo:live +pid 可以查看当前Java进程创建的活跃对象数目和占用内存大小。

生产环境中,cpu占用过高排查

1.先用top找出占用cpu过高的进程pid
2.ps -ef或者jps进一步定位,得知是一个怎样的后台程序

3.定位到具体线程或代码
ps -mp 25468 -o THREAD,tid,time
参数解释:
-m 显示所有的线程
-p pid进程使用cpu的时间
-o 该参数是用户自定义格式

找不那个都是- 不正常的线程,上图即为Tid 25469
4.将Tid转换为16进制格式(英文小写)
printf "%x\n" 25469
上述25469 为637d
5.jstack pid |grep Tid -A60 打印前60行

看报错信息,可以确定是第81行报错
或者是输出到日志文件中
jstack -l pid >jstack.log
然后在log中搜索Tid对应错误进行分析
生产环境中,内存占用过高排查
1.top找出占用内存过高程序
2.使用jmap -dump:file=xxx 把内存信息dump下来
3.使用内存分析软件visual vm或者MAT来具体分析
(做了软连接 直接同visualvm命令打开)
idea设置参数可以oom时候自动dump

将dump导入visualvm中:


强引用、软引用、弱引用和虚引用分别是什么?
1.强引用
特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
2.软引用
特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
代码验证
我设置 JVM 参数为 -Xms10m -Xmx10m -XX:+PrintGCDetails
public class SoftReferenceDemo {
public static void main(String[] args) {
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);
obj = null;
try {
// 分配 20 M
byte[] bytes = new byte[20 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("软引用:" + softReference.get());
}
}
}
输出
[GC (Allocation Failure) [PSYoungGen: 1234K->448K(2560K)] 1234K->456K(9728K), 0.0016748 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 448K->384K(2560K)] 456K->392K(9728K), 0.0018398 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 384K->0K(2560K)] [ParOldGen: 8K->358K(7168K)] 392K->358K(9728K), [Metaspace: 3030K->3030K(1056768K)], 0.0057246 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 358K->358K(9728K), 0.0006038 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 358K->340K(7168K)] 358K->340K(9728K), [Metaspace: 3030K->3030K(1056768K)], 0.0115080 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
软引用:null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.cuzz.jvm.SoftReferenceDemo.main(SoftReferenceDemo.java:21)
Heap
PSYoungGen total 2560K, used 98K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd18978,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 340K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 4% used [0x00000000ff600000,0x00000000ff6552f8,0x00000000ffd00000)
Metaspace used 3067K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
发现当内存不够的时候就会被回收。
3.弱引用
特点:弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用同样可用于内存敏感的缓存。
代码验证
public class WeakReferenceDemo {
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<>(obj);
System.out.println(obj);
System.out.println(weakReference.get());
obj = null;
System.gc();
System.out.println("GC之后....");
System.out.println(obj);
System.out.println(weakReference.get());
}
}
输出
java.lang.Object@1540e19d
java.lang.Object@1540e19d
GC之后....
null
null
值得注意的是String name = "cuzz" 这种会放入永久代,以及 Integer age = 1 在 int 中 -128 到 127 会被缓存,所以是强引用,然后 GC 也不会被回收。
4.引用队列
public class ReferenceQueueDemo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue);
System.out.println(obj);
System.out.println(weakReference.get());
System.out.println(weakReference);
obj = null;
System.gc();
Thread.sleep(500);
System.out.println("GC之后....");
System.out.println(obj);
System.out.println(weakReference.get());
System.out.println(weakReference);
}
}
输出
java.lang.Object@1540e19d
java.lang.Object@1540e19d
java.lang.ref.WeakReference@677327b6
GC之后....
null
null
java.lang.ref.WeakReference@677327b6
会把该对象的包装类即weakReference放入到ReferenceQueue里面,我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。
5.虚引用
特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。

浙公网安备 33010602011771号