JVM——不断更新

什么是JVM

简称:java虚拟机
作用:编译器将java源代码.java转化为字节码.class文件 , jvm将字节码.class转化为其他机器二进制语言课直接运行
一次编译 到处运行
自动内存管理 垃圾回收
数组下标越界检测

在这里插入图片描述

内存结构

在这里插入图片描述

程序计数器

作用

如果当前线程正在执行java方法:
	程序计数器记录的是当前正在执行虚拟机字节码的指令地址
如果是Native(底层c++代码)
	程序计数器为空。

特点 线程私有的——每个线程都有自己的程序计数器——为了各线程切换时不影响
是虚拟机中唯一不会存在内存溢出的区域。——存储的是当前执行字节码的地址 所需空间小

虚拟机栈

含义:

每个线程时所需要的内存空间称为虚拟机栈;
虚拟机栈其描述的是java方法所需的内存模型。
每个方法执行的时候会创建一个栈帧
用于存储java方法中的局部变量表、操作数栈、动态链接和方法出口等信息。
java方法的调用到执行结束 就意味着 栈帧在虚拟机栈中 入栈到出栈的一个过程。

特点: 线程私有, 生命周期与线程保持一致

在这里插入图片描述
栈内存溢出

1.栈帧过多,超过虚拟机栈的大小————递归调用
2.栈帧过大,栈帧直接超过虚拟机栈的内存————出现较少

线程运行诊断——cpu占用过多
linux中 top定位哪个进程对cpu占用高
用ps命令定位线程占用过高
jstack进程id,找到源问题代码行号

线程运行诊断——迟迟没有结果
可能原因:发生死锁
jstack进程id,找到源问题代码行号

相关问题

01垃圾回收涉及栈内存吗?
答:不涉及,栈用来存储每个方法的内存空间,方法调用完后会自动弹出

02栈内存分配越大越好吗?
栈占据的内存大,物理内存不变,相对其他的线程可占用的内存就少了

03方法内的局部变量是否线程安全
私有变量:线程安全
static共享变量:线程不安全,要加另外的处理

本地方法栈

Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,
而本地方法栈则为虚拟机使用到的 Native 方法服务,即为了使其他平台运行java代码在jvm转化过程中使用的c或c++写的代码(底层代码)。

特点:数量多
在这里插入图片描述

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。
所有线程共享,
作用: 主要是存放 对象实例和数组

生命周期; java虚拟机启动时创建。

特点: java堆区域是 垃圾回收器的主要管理区域。 也称为GC堆

进一步划分堆区域

垃圾回收的分代回收时  堆可以分为:
新生代
	伊甸园
	From区域
	To区域

老年代

进一步划分堆空间是更好的回收 和分配内存。
堆内存溢出问题
(对象不是垃圾,过多造成)
在这里插入图片描述

线程安全问题

垃圾回收机制

方法区

各个线程共享的区域;
作用: 用来存储 编译编译后的代码数据如 :虚拟机已经加载的类信息、常量、静态变量等数据

垃圾回收时间
方法区可以被称为永久代:存放了类信息和常量池
垃圾回收时 主要是回收常量池中的垃圾 和 类的卸载。 但回收频率比较少

虚拟机启动时创建

方法区内存溢出:
类过多导致溢出。

常量池

常量池 是方法区的一部分,可以被看成一张表 ,虚拟机根据这张表查询到类名、方法名等信息。
存放编译生成的字面量和符号引用

String s1 = new String("a") + new String("bc");
s1.intern();
String s2 = "abc";
System.out.println(s1 == s2);  // true

intern()方法:
检测该字符串是否在常量池中存在, 如果不存在添加进去返回, 如果存储直接返回该引用。

直接内存

常用于NIO(new Input/IO)操作,用于数据缓存冲区
分配成本较高,但读写性能高
不受JVM内存回收管理,不是jvm运行数据区的一部分

在这里插入图片描述
在这里插入图片描述
相较于其他读取操作,直接内存在系统内存和堆内存中,少了一次缓冲区的复制操作。

直接内存过多也会造成内存溢出。
直接内存释放原理
通过unsafe管理内存的管理和回收,并且回收需要主动调用freeMemory()方法。
在这里插入图片描述

垃圾回收

如何判断

引用计数法——对象之间相互引用 造成使用次数不会归零,容易造成内存泄漏。

可达性分析算法——扫描堆中的对象,看能否沿着GC Root为起点的引用链找到该对象,找不到表示可以回收。
在这里插入图片描述

哪些对象可以被认定为GC ROOT对象

在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

在方法区中static修饰变量的引用对象。

在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

所有被同步锁(synchronized关键字)持有的对象。

java中的引用

强引用 软引用 弱引用 虚引用 ——是指GC Root对象的引用
强引用
是指创建一个对象并把这个对象赋给一个引用变量。
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

软引用
内存空间足够时,不会当初垃圾回收
如果空间不足 就会当初垃圾回收。

回收之后的,之前指向已经被回收的对象 的对象 将进入引用队列

弱引用
当JVM进行垃圾回收,无论内存是否充足,都会回收被弱引用关联的对象。

回收之后的,之前指向已经被回收的对象 的对象 将进入引用队列。

虚引用
虚引用主要用来跟踪对象被垃圾回收器回收的活动。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。——即得到一个通知
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。

垃圾回收算法

标记清除

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。
	标记 :从根节点开始标记引用的对象。
	清除 :未被标记引用的对象就是垃圾对象,可以被清理。

优点: 可以回收那些循环引用的对象(解决引用计数法)
缺点: 可能造成内存不连续;
效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。

标记整理

在这里插入图片描述
存活对象向前移动,死亡对象之间回收
缺点:效率低,地址移动,速度慢
优点:没有内存碎片

复制算法

需要双倍的内存空间, 将活着的对象复制到另外一块空间上, 然后清理使用过的对象。
优点: 实现简单(通过移动堆顶指针复制),高效
**缺点:**占用内存空间

堆区域的 伊甸园、To区域、From区域和这个类似——分代回收算法。

分代回收算法——堆对象回收

堆区域分为两大块:新生代和老年代

新生代
	伊甸园
	From
	TO
老年代

含义:
1、通常对象放在堆区域的 新生代中的 伊甸园区域。
2、发生垃圾回收时,伊甸园中存活对象放在TO区域中,死亡对象回收,然后TO区域和FROM区域指针交换。此时伊甸园和TO区域为空,FROM区域有对象。并且该对象计数+1.
3、第二次回收垃圾时, 伊甸园和FROM区域类似 检测 复制垃圾到TO区域, 当对象计数次数超过15次时转移到老年代中。
4、老年代区域有了死亡对象时,且内存不足,采用标记整理、标记清除算法处理。

方法区对象回收

常量回收条件

没有其他对象引用这个常量 便可以回收

类回收

01该类 没有其他实体类存在
02该类的类加载器已经被回收
03java.lang.Class对象没有被引用, 即该类没有通过反射方式访问。

垃圾回收器

垃圾回收器时内存回收的具体实现。

串行回收器——Serial收集器

在这里插入图片描述
开启语句: UseSerialGC
工作范围:

新生代
	标记复制算法——不会产生内存碎片,额外空间
老年代
	标记整理算法——不会产生内存碎片

特点:
单线程收集器——某个线程进行垃圾回收时,其他线程必须停止工作(简称STW——Stop The World)

对于单线程来说,效率很高

吞吐量优先——Parrllel 收集器

jdk1.8默认使用。
吞吐量:CPU运行程序时间/CPU总消耗时间。
吞吐量越大,垃圾回收消耗时间越小。
并行: 只能垃圾回收
在这里插入图片描述
开启语句:UseparallelGC或者UseparallelOldGC
工作范围:

新生代
	标记复制算法——不会产生内存碎片,额外空间
老年代
	标记整理算法——不会产生内存碎片

特点:
数量——多回收器数量 和 cpu核数相关。
CPU利用率——某时刻回收垃圾时利用率高

相关方法:
1、控制线程数ParallelGCThreads=n
2、动态调整新生代三个区域的比例UseAdaptiveSizePolicy
3、调整吞吐量GCTimeRatio

范围:0-100;
含义: 垃圾收集器时间占总时间的比率,相当于吞吐量的倒数
公式:1/(1+ratio)
	ratio越大,垃圾回收器消耗时间越小
默认值:99.

4、单次垃圾回收最大时间MaxGCPauseMills
即限制垃圾回收单次的耗时。
GC单次时间变少,是降低新生代空间和吞吐量。
即之前10秒收集一次垃圾,每次耗时100ms
限制时间之后,5秒回收一次,每次耗时70ms——因为新生代空间小了,垃圾容易堆满。

响应时间优先收集器——CMS收集器

开启: UseConcMarkSweepGC与之配合的是UseParNewGC+SerialOld
CMS:并发标记扫描
目的: 以获取最短回收停顿时间为目标
应用: B/S服务端上
特点: 重视响应速度,即希望系统停顿时间最短。 吞吐量有一定影响,分cpu给垃圾回收。
基于标记清除算法。

并发 垃圾回收器和其他线程可以同时运行
并行 多个垃圾回收器运行,不允许其他线程运行。
在这里插入图片描述
a初始标记
找到GCToot,和gcRoot能直接关联到的对象——有STW,但时间很短
b并发标记
沿着GCROOT标记对象,寻找到垃圾对象——并发执行,时间较长,但不影响其他线程工作
c重新标记
在并发标记过程中,其他线程的工作可能会有一些对象和GCROOT的关系发生变化,进行一个校正处理——时间比初始标记长,比并发标记短,有STW现象
d并发清理
垃圾回收线程和其他工作线程并发进行

特点 垃圾回收长的部分 ——寻找垃圾对象和清理垃圾 都能并发执行,所以STW总时间短,其他线程暂停时间短。

其他方法
1、并发标记线程数量ParallelGCThreads限制参与垃圾回收集的线程数量。
2、改变老年代使用空间激活CMS的占比值CMSInitiatingOccupancyFraction
CMS运行时也会有浮动垃圾产生,所以要进行预留空间。
初始值68%
含义 老年代内存占比超过68%就可以回收老年代的垃圾
回收老年代垃圾方法: 标记整理。
3、碎片过多时进行FULL GC——开关用法UseCMSCompactAtFullCollection
CMS使用标记清除的算法进行垃圾处理,会导致碎片存在。
当开启以上开关之后, 当发生n次不压缩的Full GC 时,来一次碎片压缩。

G1收集器

JDK9默认使用G1回收器
使用场景

同时注重吞吐量和低延迟,默认暂停目标是200ms————并发执行

超大堆内存,将堆划分为多个大小相等的区域————每个区是独立的伊甸园,幸存者区域,老年代区域。

整体上以标记-整理算法 。 两个区域之间是复制算法,不会产生碎片

特点

无碎片、高吞吐量、低延迟、区域大小相等、优先回收垃圾最多的区域

区域特点
新生代中的伊甸园区域、幸存者区域和老年代区域, 不再是物理隔离的,而是通过动态分配的方式实现逻辑上的连续
一个区域只能是一个角色——即一个区 region就可能是一个 伊甸园或者幸存者区域或者老年代区域。

幸存者区域又分为s0区域 和s1区域。
在这里插入图片描述
H区域放占内存较大的区域。

巨型对象
当一个对象的大小大于region的一半时,称为 巨型对象,G1不会对巨型对象进行拷贝,回收时会优先回收

对比以前: 如果一个大对象放到老年区中,而老年代的GC比较少,会浪费内存,所以G1设计了一个H区域, 如果一个对象太大,1个H都装不下,那么会找连续的H区域存储。

详细说明

G1回收过程————1 2 3 过程是循环进行的

01新生代回收
会进行根对象的初始标记
在这里插入图片描述
复制算法, 和之前的一样会有STW过程。
伊甸园区域对象多了之后,将对象复制到幸存区中

当幸存区的对象多了之后 时间长的复制到老年代中,短的到一个新的幸存区中
在这里插入图片描述

02新生代回收和并发标记过程

当老年代占堆空间比例到达阈值(%45)时,会进行一个并发标记(不会STW),即其他线程也可以进行。
在这里插入图片描述

03混合回收
对伊甸园、幸存者和老年代进行全面回收。

最终标记和拷贝存活都会STW。

伊甸园区域幸存对象 放到幸存者区域, 当然幸存者区域也会做一个筛选。
而老年代区域 会根据最大暂停时间 及垃圾大小 有选择的进行垃圾回收——回收垃圾最大的那些。
在这里插入图片描述

总结——粗略版
G1 GC过程:
   第一步:当堆区域中的伊甸园E区域快用完时,会对根对象进行初始标记,
   并且将E的存活对象复制到幸存者S区域中,而S区域中,存活次数多的会复制到老年代O区域。——有STW
   第二步:当老年代区域超过45%,会进行一个并发标记———没有STW
   第三步:混合回收,对E S O区域进行混合全面回收,O区域优先回收占内存较大的区域
   H对象超过一个区域的一半时,垃圾回收时优先处理, 对象超过H区域时,存放到连续的H区域中。

Full GC
G1中 如果垃圾回收的速度大于新垃圾产生的速度 ,不是FullGC ——是并发收集过程
否则
进行Full GC。

在这里插入图片描述

类加载

过程

加载–>验证–>准备–>解析–>初始化–>使用–>卸载
其中解析可以在有些时候初始化之后再进行解析。

初始化

1发生时机

遇到new、getstatic、putstatic和invokestatic过程会发生初始化
————即创建一个实例类或者读取、设置一个静态字段

反射调用一个类————如果该类没有 被实例化,则进行类的初始化

初始化类B时, 其父类A未被初始化 ,则先触发其父类初始化

jvm启动时,用户指定指向某个类A,该类 未被 初始化,则JVM先初始化这个类。

2特点
当一个类需要初始化时,其父类要进行初始化,但一个接口时,只要到调用其父接口时才需要 初始化其父接口。
底层
执行类构造器()

加载

加载只是类加载中的一个部分,其主要完成的工作为以下三点:
1主要功能:

01获取类的二进制字节流

02将该字节流的静态存储结构转化为方法区的运行时的数据

03在java堆中生成一个java.class.lang对象,作为访问这些方法区数据的访问入口。

2特点:
加载过程不限定与class文件,可以是ZIP包、applet文件、动态代理中的字节流以及数据库中读取。

验证

作用:
检验Class字节流中的包含的信息符合虚拟机的要求,并且不伤害虚拟机本身。
检验步骤:
01——文件格式检验

02——元数据检验

对字节码描述的信息进行语义分析
保证其信息符合java要求
a、该类是否有父类
b、该类的父类是否能有子类如父类是不是final修饰的类
c、该类是否与父类中的方法是矛盾
等

03——字节码检验
主要工作:
进行数据流和控制流的分析。

保证该字节码不会做出伤害虚拟机的行为。

a、检验数据使用前后数据类型是否保持一致
b、检验跳转指令是否跳到方法体以外的字节码指令上。
c、检验类型转换是否符合要求
等

04——符号引用检验

准备

为 类 在方法区中分配内存并设置变量初始值的阶段。

范围:被static修饰的类中的变量,在方法区中分配空间。

private static int value=123;————准备之前是0  准备之后是123

解析

作用:将常量池中的符号引用替换为直接引用

符号引用一组符号,用来描述内存中的目标,能无歧义找到目标,与jvm的内存布局无关,引用的目标不一定加载到内存中
直接引用直接引用是可以直接指向目标的指针、相对偏移量,与jvm内存布局相关。如果该对象 已经有了直接引用,那么该目标已经存在于内存中。

类或接口的解析

字段解析

类方法解析

接口方法解析

类加载器

作用: 通过类的权限定名称获取此类的二进制字节流的动作。——在java虚拟机外部实现
其他: 比较两个类相等与否,还要考虑,这两个类的类加载器是否相等。

双亲委派模型

启动类加载器
扩展类加载器
应用程序类加载器。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值