14.javase-基础加强-JVM内存模型

在这里插入图片描述

参考文章:
文章1:https://www.toutiao.com/i6797907413220459019/
文章2:https://www.toutiao.com/i6740803737603801604/
文章3: https://www.toutiao.com/i6799522958990639628/?group_id=6799522958990639628
文章4: https://www.toutiao.com/i6909308345472254476/

一.java代码的编码,运行过程

① Java 源文件—->编译器—->字节码文件 (字节码是相同的)
② 字节码文件—->JVM—->机器码 (不同操作系统的JVM是不同的,需要将字节码解释为相应的机器码)

二.jvm内存区域

1.每一种操作系统平台的JVM是不同的,不同平台的JVM把相同的字节码编译成对应系统的机器码运行.
2.一个java应用程序代表着一个JVM虚拟机实例,一个JVM中可以运行多个线程,但是不同的进程即不同的JVM之间不能共享数据.

以下是JVM内存模型
在这里插入图片描述
JVM 内存区域分类在这里插入图片描述
运行时区内存模型
在这里插入图片描述
2.2.1. 程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的 程序计数器,这类内存也称为“线程私有”的内存。
正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如 果还是 Native 方法,则为空。
这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

2.2.2. 虚拟机栈(线程私有)

是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)
用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成 的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、
方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异 常)都算作方法结束

2.2.3. 本地方法区(线程私有)

本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务,
如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM
直接就把本地方法栈和虚拟机栈合二为一。

2.2.4. 堆(Heap-线程共享)-运行时数据区

是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域。由于现代 VM
采用分代收集算法, 因此 Java 堆从 GC 的角度还可以 细分为: 新生代(Eden 区、From Survivor 区和 To
Survivor 区)和老年代。

2.2.5. 方法区/永久代(线程共享)

即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静
态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java 堆的永久代来实现方法区, 这样
HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,
而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型 的卸载, 因此收益一般很小)。
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版
本、字段、方法、接口等描述等信息外,还有一项信息是常量池 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加 载后存放到方法区的运行时常量池中。 Java 虚拟机对
Class 文件的每一部分(自然也包括常量 池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会
被虚拟机认可、装载和执行

三.jvm堆内存模型

在这里插入图片描述
2.3.1. 新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。新生代又分为
Eden 区、ServivorFrom、ServivorTo 三个区。
2.3.1.1. Eden 区 Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老 年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行 一次垃圾回收。
2.3.1.2. ServivorFrom 上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
2.3.1.3. ServivorTo 保留了一次 MinorGC 过程中的幸存者。
2.3.1.4. MinorGC 的过程(复制->清空->互换) MinorGC 采用复制算法。 13/04/2018 Page 25 of 283 1:eden、servicorFrom 复制到 ServicorTo,年龄+1 首先,把 Eden 和 ServivorFrom
区域中存活的对象复制到 ServicorTo 区域(如果有对象的年
龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不 够位置了就放到老年区); 2:清空
eden、servicorFrom 然后,清空 Eden 和 ServicorFrom 中的对象; 3:ServicorTo 和
ServicorFrom 互换 最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC
时的 ServicorFrom 区

2.3.2. 老年代

主要存放应用程序中生命周期长的内存对象。 老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行
了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足
够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。 MajorGC
采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC
的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减
少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的 时候,就会抛出 OOM(Out of
Memory)异常。

2.3.3. 永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被
放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的 Class
的增多而胀满,最终抛出 OOM 异常。

四.垃圾回收与算法

1.如何确定垃圾?
1.1. 引用计数法

在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关 联的引用,即他们的引用计数都不为0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。

1.2. 可达性分析

为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC
roots”和一个对象之间没有可达路径,则称该对象是不可达的要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

gc root对象包括:
1.虚拟机栈中引用的对象.(存放在堆中)
2.类静态变量中引用的对象.(存放在方法区中)
3.方法区中引用的常量对象.(存放在方法区的常量池中)
4.本地方法栈中引用的对象.()

2.java中四种引用类型
2.1. 强引用(发生任何gc都不会被回收)

在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即 使该对象以后永远都不会被用到 JVM也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之 一。

2.2. 软引用(发生gc并且内存不足时会被回收.)

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

2.3. 弱引用(只要发生gc就会被回收)

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象 来说,只要垃圾回收机制一运行,不管 JVM
的内存空间是否足够,总会回收该对象占用的内存。

2.4. 虚引用(随时可能被回收.)

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚
引用的主要作用是跟踪对象被垃圾回收的状态。

3.垃圾清除的算法
3.1 标记清除算法
缺点:清理后的垃圾,内存碎片化.
应用场景:无
在这里插入图片描述

3.2 复制算法

为了解决 Mark-Sweep
算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:
缺点:只能用一半的内存
应用场景:堆内存新生代采用.
在这里插入图片描述

3.3 标记整理算法

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep
算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图: 缺点:需要消耗计算能力
应用场景:堆内存的老年代和元空间(方法区)

在这里插入图片描述
3.4 分代收集算法
即综合了上述两种算法,然后结合起来,现在一般的JVM都是采用的分代手机算法

在新生代-复制算法

每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集.

在老年代-标记整理算法

因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.

4.垃圾回收器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,因此
java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:
在这里插入图片描述

  • Serial 垃圾收集器(单线程、复制算法)
  • ParNew 垃圾收集器(Serial+多线程)
  • Parallel Scavenge收集器(多线程复制算法、高效)
  • Serial Old 收集器(单线程标记整理算法 )
  • Parallel Old收集器(多线程标记整理算法)
  • CMS 收集器(多线程标记清除算法)
  • G1 收集器

年轻代收集器
Serial
ParNew
老年代收集器
Serial Old
Parallel Old
CMS 收集器
整个堆收集
G1 收集器

G1收集器

从jdk1.9开始,默认使用G1收集器了.
空间划分:
在这里插入图片描述
原理分析:
不在物理位置上区分新生代和老年代了.充分的将大空间分成小空间region,减少STW时间.
优势:
「并行与并发」:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。

「分代收集」:G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。

「空间整合」:G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。

「可预测的停顿」:G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
步骤:
在这里插入图片描述

五.JDK自带的指令

在cmd界面输入一下指令则可以检测JVM内存情况.

jps

1.jps
执行jps命令可以获取到当前虚拟机中正在运行的java程序,以及对应的进程号,输出格式如下

C:\Users\13570>jps
10420
3204 Jps

2.jps-v
查看所有进程及相应的jvm参数
在这里插入图片描述

jstat

jmap使用主要是打印某一时刻,某个进行的jvm堆信息,使用步骤如下

获取进行pid ps -ef|grep 对应进程
或者使用jps获取
语法:
jstat -gc -pid

C:\Users\13570>jstat -gcutil 10420 3000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
 49.84   0.00  23.37  60.88  94.92      -     68    0.786     3    0.303    1.089
 49.84   0.00  23.37  60.88  94.92      -     68    0.786     3    0.303    1.089
 49.84   0.00  23.37  60.88  94.92      -     68    0.786     3    0.303    1.089
C:\Users\13570>jstat -gc 23856
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
5120.0 4608.0  0.0   2976.0 10240.0   5232.5   136192.0   75007.1   52736.0 49901.8 7424.0 6826.5  24787   47.981   2      0.067   48.048

s0、s1:表示两个survior区域的使用百分比
e:eden区域使用百分比
o:老年代使用百分比
m:metaspace(元数据空间)使用百分比
ygc:新生代gc次数
ygct:新生代gc累计总时间
fgc:full gc次数
fgct:full gc累计总时间
gct:gc累计总时间

jconsole

可视化监控jvm,基本上看到啥就是啥,但是生产比较少会用到,JVM可视化界面
在这里插入图片描述

jinfo

jinfo:查看java程序详情
jinfo -flags pid #查看该进程的jvm参数
在这里插入图片描述

六.JVM参数

1.什么是JVM参数?

jvm参数是jvm启动时,输入的参数,用于控制jvm运行过程的.

2.jvm参数的作用

jvm的参数主要作用有三个:

1.分配jvm内存
2.设置gc回收算法和设置使用gc回收器.
3.设置gc日志,包括日志格式即输出模式及地址.

3.jvm参数的分类

3.1 标准参数(-)
所有的JVM实现都必须实现这些参数的功能,而且向后兼容;

verbose
-verbose:class
输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断。
-verbose:gc
输出每次GC的相关情况。
-verbose:jni
输出native方法调用的相关情况,一般用于诊断jni调用错误信息。

3.2 非标准参数(-X)
默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容;

-Xms 初始堆内存
-Xmx 最大堆内存
-Xmn 年轻代大小(1.4or lator)
-Xss 每个线程的堆栈大小
-Xloggc:file gc日志输出文件
若与verbose命令同时出现在命令行中,则以-Xloggc为准。

3.3 非stable参数(-XX)
此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用;

-XX:-DisableExplicitGC
禁止调用System.gc();但jvm的gc仍然有效
-XX:+MaxFDLimit
最大化文件描述符的数量限制
-XX:+ScavengeBeforeFullGC
新生代GC优先于Full GC执行
-XX:+UseGCOverheadLimit
在抛出OOM之前限制jvm耗费在GC上的时间比例
-XX:+UseConcMarkSweepGC
对老生代采用并发标记交换算法进行GC
-XX:+UseParallelGC
启用并行GC
-XX:+UseParallelOldGC
对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用
-XX:+UseSerialGC
启用串行GC
-XX:+UseThreadPriorities
启用本地线程优先级

注意
+加号表示开启,-减号表示关闭
如:
-XX:+PrintGC #打印gc日志
-XX:-PrintGCTimeStamps #关闭打印gc日志的时间戳

参考文章:
https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
https://www.cnblogs.com/zkyefei/p/9334562.html

4.怎样设置JVM参数

1.eclipse中设置JVM参数
在这里插入图片描述
2.idea中设置JVM参数
方法1:在运行的configuration中设置
在这里插入图片描述
方法2:在idea的安装目录bin目录下的配置文件中设置
在这里插入图片描述

七.JVM性能优化

1.目标

所有运行的程序都存在调优的策略,不止是JVM,包括mysql,redis,mq都是这样的,并且所有的性能调优的目标都是两个:

1.加快相应速度.
2.增加并发量.

2.原理

1.majorGC和fullGC会触发STW,即暂停应用程序,故应该减少这两个GC
2.这两个GC触发的地方是老年代回收或者元空间回收
JVM性能优化,指的是为了增加JVM响应速度,因为fullGC会导致应用线程暂停(STW),所以JVM优化即为了减少fullGC,因为老年代的回收会触发fullGC,所以尽量减少老年代或者方法区回收:

3.majorGC和fullGC产生原因

1)调用System.gc时,系统建议执行Full GC,但是不必然执行;
2)老年代空间不足;
3)metaSpace空间不足;
4)通过Minor GC后进入老年代的平均大小 > 老年代的可用内存;
5)由Eden区、From Space区向ToSpace区复制时,对象大小大于ToSpace可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。即老年代无法存放下新年代过度到老年代的对象的时候,会触发Full GC。

4.策略
1.检查是否新生代空间太小,大对象无法创建直接存入了老年代
2.检查老年代空间是否太小,应用产生的对象过快,频繁触发GC
3.使用的垃圾回收器是否合适
5.步骤

说明,如果使用的tomcat作为服务器,一般将JVM参数配置在tomcat的启动脚本文件中.具体配置可查看tomcat相关博客.
1.jvm参数中增加gc日志
-Xloggc:file #gc日志写入本地文件
-XX:+PrintGC #打印gc日志
-XX:+PrintGCDetails #打印GC日志详情
-XX:-PrintGCTimeStamps #打印GC日志时间搓
2.分析gc日志
方法1:java自带的命令检测

方法2:其他三方工具检测
参考文章: https://blog.csdn.net/u012988901/article/details/102791020
GC view
jar包工具.
GC easy
官网:https://gceasy.io/
3.调整gc算法或者jvm各个区域内存大小
-Xms 1.堆初始值
-Xmx 2.堆最大值
3.老年代大小
4.新生代中Survivor区域大小
5.经过多少次minorGC对象进入老年代

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值