JVM基本概念

JVM

概述:

作用:把我们的一套程序在不同的平台上运行 可以实现自动的内存管理 自动的垃圾回收

JVM整体结构分为4大块:

1.类加载系统:

负责从硬盘上加载字节码文件

2.运行时数据区:

存储运行时的数据(5个区域 方法区、堆、栈、本地方法栈、程序技术器)

3.执行引擎:

负责将字节码 解释/编译 为真正的机器码

4.本地方法接口:

负责调用操作系统本地方法

类加载系统

1.什么是类加载?

字节码储存在硬盘上,需要时由类加载系统将类的信息加载到内存中(方法区)

为类创建一个Class类的对象,使用ClassLoader进行加载,充当一个快递员角色

2.类加载过程?

(1)加载:将硬盘字节码读入到内存中,生成此类的Class对象,把硬盘上的结构转为内存结构。

(2)验证:验证字节码的格式,是否被修改(污染)

验证语法,例如类是否继承final修饰的类

准备:在准备阶段为类中静态的变量赋予初始值

static int num =123;
在准备阶段static int num =0;在后面初始化阶段才改成123  在准备阶段不为静态常量进行赋值

解析:将符号应用(文件中的逻辑引用)转为直接引用(内存中的实际地址)

(3)初始化:对类中的静态成员进行赋值了。

类什么时候初始化:new对象、使用类的静态成员、反射动态加载类(Class.forName())、子类被加载

只访问了某一个类中的静态常量

3.负责加载类的类

类加载器分类:

站在JVM角度上分为:

1.启动类加载器(引导类加载器)这部分不是用java语言写的 负责加载java核心类

2.其他类加载器(这部分指的是用java语言写的类加载器)

从程序员角度分为:

1.启动类加载器 负责加载java核心类

2.扩展类加载器 负责加载\jre\lib\ext目录下的类,包括应用程序类加器

3.应用程序类加载器 包括自定义的类加载器,负责加载程序中的类(自己写的)

4.双亲委派机制

为了确保加载类的正确性、安全性,在类加载类时,采用双亲委派机制。当需要加载程序中一个类时,会先让加载器的父类去加载,直到最顶级的启动类加载器,如果父级找到了返回使用,如果依旧没有找到,那么就委派给子级去加载,找到了就返回,如果所有的类加载器都没有找到,报类加载器异常。

优点:安全避免了自己写的类替换系统中的类 避免类的重复加载

5.类何时加载

主动使用:将类加载的整个过程完成

new对象,使用类的静态成员,反射动态加载类Class.forName(),子类被加载

被动使用:不会加载初始化

访问类中的静态常量,将类作为类型,例如创建数组,用类作为类型使用。

6.如何打破双亲委派机制

java中提供一个Class.Loader类,定义哪些方法加载类

loadclass(String classpath) 建议 底层使用双亲委派机制加载类
findclass(String classpath) 如果需要自定义,可重写findclass()
defineclass() 将读到class文件的数据,构造出一个class对象 再有就是像Tomcat这种服务器软件,里面也会有自己定义的类加载器

JVM运行时数据区

程序计数器:记录线程运行的位置(行号),线程需要切换执行,所以记录执行位置。

虚拟机栈:运行java方法的区域,每个方法生成一个栈帧。

本地方法栈:java经常需要调用一些本地方法(操作系统的方法 hashCode(),read(),start(),arraycopy())

堆:存放程序中产生的对象,也是虚拟机中内存占比最大的一块

方法区:存放类信息

堆、方法区:是线程所共享的

程序计数器、虚拟机栈、本地方法栈:是线程私有的,线程独立的。

堆、方法区、栈、本地方法栈:会出现内存溢出错误。

程序计数器

是一块内存很小的的区域,主要用来记录每个线程中执行的执行位置,便于线程在切换执行记录位置。是线程私有的,生命周期与线程一样,运行速度快,不会出现内存溢出。

虚拟机栈

基本概念:栈是运行单位,存储一个一个的方法,当调用一个方法时创建一个栈帧,将方法中的信息存储到栈帧中。

操作只有两个:调用方法,入栈,方法执行完后,出栈(先进后出)

运行速度快,仅次于程序计数器,当入栈的方法过多时,会出现栈溢出(内存溢出)

线程是独立的,不同线程之间方法不能相互调用。

栈帧的内部结构:

1.局部变量表:方法内部声明的局部变量,方法参数。

2.操作数栈:运算区域 a+b

动态链表:调用的方法地址,字面量地址。

方法返回地址

本地方法栈

本地方法 native修饰的方法,没有方法体。 hashCode() read() start() arrayCopy()

本地方法不是用java语言写的,例如操作系统方法。

如果调用本地方法,那么本地方法在本地方法栈中运行,也会出现内存溢出

创建对象 对象引用

堆空间是jvm内存中一块空间,主要用来存储对象,是jvm中空间最大的块,是线程共享。

jvm启动,堆空间就创建了,大小确定了,但可以通过参数改变大小的,这就是jvm调优。物理上不连续,逻辑上连续的,堆就是垃圾回收的重点区域。

堆的区分

新生代: 伊甸园区 幸存者1 幸存者2

老年代

为什么要分区域?

不同的对象,它的生命周期不同,这样可以将不同的对象存储在不同的区域,不同的区域采用不同的垃圾回收算法。进行处理,扬长避短。

创建的对象在这些区域中如何分布?

当一个对象刚刚创建后,被存放在伊甸园区。

当垃圾回收进行时,会把伊甸园区中存活下来的对象,移动到幸存者区。

幸存者有两个 区域1和区域2

首先把伊甸园区存放的对象放在幸存者1区,当下次垃圾回收到来时,会将伊甸园区和幸存者1区的存活对象移到幸存者2区,清空幸存者1之后再次回收时会将伊甸园区和幸存者2区存活的对象一到幸存者1区,清空幸存者2,交替执行。

什么时候对象去老年代?

当垃圾回收每次对对象进行标记时,在对象头中有一个空间被用来记录被标记的次数。

在对象头中,记录分代年龄只有4bit位空间,只能记录15次。

堆各个空间比例

新生代与老年代默认比例是1:2,但可以通过参数设置

—XX:survivorRatio=8

一个对象通过15次的垃圾回收依然存活就去老年代。可以通过 XX:MaxTenuringThreshold=15设置,最大15次

分待收集思想

jvm垃圾回收可以分为不同的区域进行回收

针对新生代会频繁回收,称为 yong GC

较少回收老年代,称为old GC

当调用System.gc(),老年代内存不足时,方法区内存不足时,会触发 FULL GC(整堆收集)

尽量避免整堆收集,整堆收集,其他用户线程暂停的时间长。

堆的参数

设置整个堆的大小

各个区间比例

对象年龄

字符串常量池位置

jdk7之后将字符串常量池的位置从方法区转移到堆中。因为方法区只有在触发FULL GC时才会进行回收,回收效率低。所以将字符串常量池移到堆中,提高垃圾回收效率。

方法区

存储类信息

类信息:方法、属性、静态常量、静态变量,即时编译器编译后的代码,运行时常量池(字面量值)

方法区是线程共享的,也可能会出现内存溢出,也会涉及到垃圾回收。方法区的生命周期也是虚拟机启动就创建,虚拟机关闭就销毁。

方法区大小设置

—XX:MaxMataspaceSize windows方法中方法区默认最大值为21MB

如果达到21MB就会触发FULL GC 值也可设为-1,没有上限,占用整个计算机内存。

方法区是会涉及到垃圾回收的,主要回收的是静态常量,类信息,类信息何时被卸载,满足3个条件:

1.该类所产生的对象都被回收

2.该类对应的class对象,不在被其他地方引用

3.该类对应的类加载器被回收了

本地方法接口

通过本地方法接口模块来与操作系统接口进行访问

什么是本地方法?

使用native修饰的方法,不是java语言实现的,是操作系统实现的。

为什么要使用native Method

1.例如需要获得硬件的一些信息,如内存地址,启动线程,IO,调用本地方法接口就很方便

2.jvm底层进行字节码解释或编译部分也有用C语言实现

执行引擎

作用:负责装载字节码文件到执行引擎中,字节码不是机器码,只是jvm规范中定义的指令码。

执行引擎:需要将字节码 解释/编译为不同的平台识别的机器码。

Hellow.java-------jdk编译工具 javac-------.class 称为前端编译

.calss --------执行引擎 编译为机器码 称为后端编译

解释器:jvm运行程序时,逐行对字节码指令进行翻译,效率低

JIT(即使)编译期:对某段代码整体编译后执行 效率高 编译需要耗一段时间

为什么是半解释器半编译型

起初java中只是提供了解释执行的方法,但是解释执行效率低,后来引入编译器,可以对查询执行中的热点代码进行编译,并把编译后的内容缓存起来,后期执行效率高。

热点代码采用计数器方式来记录

程序启动后可以通过解释器立即对代码进行解释执行,不需要等待编译,提高响应速度,之后对热点代码采用编译器编译执行从而提高后续效率。

垃圾回收

概述:java语言提供自动垃圾回收功能的,C++没有垃圾回收,垃圾回收也不是java首创。java在垃圾回收这块一直不断升级。

什么是垃圾:没有被任何引用指向的对象。

例如:Object   obj= new Object;
obj.hashCode(); //引用
obj=null; //没有引用 垃圾

为什么回收:垃圾对象,如果不回收,他就会一直占用内存空间,垃圾对象越积越多,可能导致内存不够用。(内存溢出)

对内存空间中的内存碎片进行整理,如果不整理,需要存储像数组这样的对象,可能存储不了。

早期怎么回收:早期C++代码,需程序手动销毁对象。

不足:麻烦手动创建,手动回收。有时忘删除,那么就会造成内存泄漏。

内存溢出:内存不够用,报内存溢出错误 OOM

内存泄漏:有一些对象已经不再被使用了,但是垃圾回收对象又不能回收它,这种悄悄占用内存资源的现象,称为“内存泄漏”。OM(OutofMemory)

现在的语言引进了自动的内存管理。

优点:降低了程序员的工作量,降低了内存溢出和内存泄漏的风险。

担忧:自动的垃圾回收,降低了程序员对内存管理能力。一旦出现问题,不能下手解决。

哪些区域回收:方法区,堆,(频繁回收新生代,较少回收老年代,基本不回收方法区)。

垃圾回收算法

标记阶段

标记那些对象,已经是垃圾。

引用计数算法(没有使用)

对象内部有一个计数器,有一个引用指向 计数器+1

obj=null 计数器-1

实现简单,缺点:A里面包含B,B里面包含A(造成内存泄漏)。

不能解决循环引用问题。增加了空间、时间开销。需要维护计数器空间,赋值后对计数器进行更新。

根可达算法(可达性分析算法)

实现思路:从一组GCRoots对象(一组活跃的对象,当前栈帧中使用的对象)开始向下查找,如果与GCRoots对象相关联的,那么就不是垃圾对象,否则判定为垃圾对象。

GCRoots可以是哪些元素:

1.栈中所有使用的对象

2.静态的成员变量所指向的对象

3.Synchronized同步对象

4.JVM系统内部的对象

可达性分析避免对象循环引用的问题。

final finally{} finalize()

finalize()是Object类中定义的方法,子类可以重写,但是不要主动自己调用它。finalize()在对象被回收前由垃圾回收线程调用,只能调用一次。

一般情况下,不需要重写次方法。如果在对象销毁前,执行一些释放资源的操作,可以重写此方法。但是注意,不要将对象复活或出现死循环。

从垃圾回收角度,将对象分为三种状态:

1.可触及的,从根节点开始,可以到达这个对象

2.可复活的,对象标记为垃圾,finalize()方法没有执行(对象可以在finalize中复活)

3.不可触及的:对象中finalize()方法已经执行,而且对象没有被复活,那么进入不可触及状态。

对象回收的一个细节:如果一个对象第一次被标记为垃圾且finalize()没有被执行。将这些对象,放在一个队列中,调用它们的finalize(),如果在finalize()方法;对象与GCRoots中某个对象关联上,从队列中移出。当第二次被标记为垃圾对象时,那么直接就是不可触及的,被回收。

垃圾回收阶段:

复制算法 (针对新生代 对象存活较少,需要移动对象)

使用到两块内存空间(对标两个幸存者区),将正在使用区域中存活的对象复制到另一个区间,排放整齐,清除原来的空间。

优点:内存碎片少 效率快

缺点:使用两块内存,G1的垃圾回收器每一个区域又分成多个小的区域,需要来记录地址

标记清除(针对老年代,存活对象较多,不需要移动对象,不会整理内存)

清除:并非直接清理垃圾对象,而是将垃圾对象的地址记录下来,存在一个列表里面。如果所有新的对象需要分配空间,那么就从空闲列表判断空间是否够用,如果够用,那么用新对象替换垃圾对象。

优点:实现简单,不需要移动对象

缺点:会产生内存碎片

标记—压缩算法

针对于标记清除的不足,将存活的对象进行整理,然后清除垃圾对象,这样内存就不会产生内存碎片。

标记清除:不移动对象,产生碎片

标记压缩:移动对象,不产生内存碎片

分代收集

新生代存活对象生命周期短,需要频繁回收,复制算法效率高,适合新生代。

老年代对象生命周期长,不需要频繁回收,使用标记清除和标记压缩。

STW(stop the world)

在垃圾回收线程标记是,需要在某一个时间点上,让所有的用户线程暂停一下,保证在判定对象是否为垃圾时的准确性。

性能好的垃圾回收器,发生STW次数会少一些。

垃圾回收器

是对垃圾回收的落地实现

JVM中又分为不同种类的垃圾回收器,可以根据使用场景选择对应的垃圾回收器。

分类:

按照线程数量分为:

单线程垃圾回收器(垃圾回收线程只有一个,适用于小型场景)

多线程垃圾回收器(垃圾回收线程有多个同时执行,效率高)

按照工作模式分为:

独占式(垃圾回收线程执行时,用户线程全部暂停 STW)

并发式(垃圾回收线程执行时,可以不用暂停用户线程。从CMS这款回收器引入并发式)

按照内存工作区域分为:

年轻代(年轻代区域的垃圾回收器)

老年代(老年代区域的垃圾回收器)

CMS

CMS是之前的不管线程还是多线程的垃圾回收器,都是独占式的。

并发标记清除首创用户线程可以和垃圾回收线程并发执行,追求低停顿。

初始化标记:单线程独占标记对象。

并发标记:垃圾回收线程和用户线程并发执行。

重新标记:使用多线程独占进行标记对象。

并发清除:垃圾回收线程和用户线程并发执行。

优点:可以做到并发收集

缺点:用户线程和垃圾回收线程并发执行,导致吞吐量降低,无法处理浮动垃圾(并发标记时用户线程不暂停)标记完成后随时产生新的垃圾对象,无法处理,只能等到下次垃圾回收处理。

三色标记算法:将对象标记为不同的状态 黑、灰、白

黑:该对象已经被标记过的,不是垃圾对象且对象下的关联属性也标记过了。

灰:已经被垃圾回收器标记过了,但是还有没有标记过的。

白:没有被垃圾回收器扫描过的,标记是垃圾。

1.刚开始,确定GCRoots的对象为黑。

2.将GCRoots直接关联的对象设为灰。

3.遍历灰色对象所有引用,灰色本身变黑,下面关联对象是灰。

4.重复标记。

5.将白色对象清除。

可能会出现 漏标、错标问题。

漏标

A关联B B关联C 当B是灰色,此时A和B断开联系,但是B已经是灰色的。B和c是浮动垃圾,等待下一次回收。

错标

A关联B B关联C B为灰,B和C断开,A和C联系。A为黑色,不在扫描,C会被回收。

解决:就是将发生变化的关系进行记录,重新在标记。

G1回收器

G1也是使用到了并发标记和清除,将整个堆的每个区域又划分为更小的空间,回收时可以根据每个区域的优先级(由里面的垃圾数量),先回收优先级高的区间。降低了用户线程的停顿,提高了吞吐量,对整堆进行统一管理,没有新生代和老年代。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值