JVM的一点小总结

虚拟机:

把一套程序在不同平台上运行,

可实现自动内存管理,自动垃圾回收。

JVM整体分为四大块:

1.类加载系统 :

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

2.运行时数据区:

① 方法区

② 堆

③ 栈

④ 本地方法栈

⑤ 程序计数器

3.执行引擎:

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

4.本地方法接口:

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

垃圾回收

1.类加载系统

1.什么是类加载?

字节码存储在硬盘上,需要运行时,由类加载的系统负责的信息加载到内存中,为每一个类创建一个class类的存储对象,使用的时classloader进行的加载(充当的快递员角色)。

2.类加载的过程

 

① 加载:

将硬盘上的字节码读入到内存中,生成此类的class对象,吧硬盘上的结构转换为内存结构。

② 链接:

(1) 验证: 1.验证字节码格式,是否被需改。

2. 验证语法。 eg:类是否继承final的类。

(2)准备:在准备阶段为类中的静态变量赋初始值。

eg: static in num = 123 在准备阶段 static int num = 0 初始化后才为123,

在准备阶段不为静态常量进行初始化。

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

③ 初始化:

为类中的静态成员赋值。

类何时初始化? new对象;使用类的静态成员;反射时动态加载类Class.forName() ;子类被加载

之访问类中的某个静态的常量。

类的初始化顺序:

父类static--子类的static--父类构造--子类构造

3.类加载器

负责加载类的类。

类加载器的分类:

从jvm角度上分:

① 启动类加载器(引导类加载的过程),不用java语言写的。

② 其他类加载器(java写的)

从程序员角度上分:

① 启动类加载器,负责加载java核心类。

② 扩展类加载器:负责加载jre/lib/ext 目录下的类包含了应用程序类加载器。

③ 应用程序类加载器:负责加载自己写的类。

4.双亲委派机制

为了确保类加载的正确性,安全性,在类加载的时候采用双亲委派机制。

双亲委派机制:当需要加载程序中的一个类时,首先会让加载器的父级去加载,直到最顶级的启动类,如果父类的加载器可以完成类加载的任务,就成功返回,如果不行,则子类就会尝试自己去加载,如果所有的类加载器都没有找到,会报类找不到的异常。

 

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

双亲委派机制能否被打破?

类何时加载?

主动使用 将类加载的整个过程完成, new 对象, 使用类的静态成员, 反射动态加载类 Class.forName(), 子类被加载。

被动使用: 不会加载初始化,访问类中的静态常量,将类作为类型,例如创建数组,用类作为类型使用。

双亲委派机制如何打破?

自定义类加载,重写loadclass方法:

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

loadClass(String classpath);//建议 底层使用的双亲委派机制加载类

findClass(String classpath); //如果需要自定义,可以重写findClass()

defineClass() 将读到class文件的数据,构造出一个Class的对象

再有就是像tomcat这种服务器软件,里面也是会自己定义类加载器

2.运行时数据区

运行时数据区F分为5大模块:

① 程序计数器:

记录线程运行的位置,因为线程需要切换执行,需记录位置

② 虚拟机栈:

运行java方法的区域每个方法生成一个栈帧。

③ 本地方法栈:

java经常要调用一些本地方法(操作系统的方法:线程/ start() IO/read() hashcode() Arraycopy())

④ 堆:

用来放程序中产生的对象,也是虚拟机内存中最大占比最大的。

⑤ 方法区:

存放类的信息。

堆,方法区是线程共享的, 其他3个为线程私有的。

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

1.程序计数器

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

2.虚拟机栈

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

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

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

栈帧的内部结构:

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

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

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

方法返回地址

 

3.本地方法栈

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

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

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

4.堆

创建对象 对象引用

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

在jvm启动时创建,大小也确定,可以同过参数进行改变(jvm调优)

物理上不连续,逻辑上连续的,堆是垃圾回收的重点区域.

堆的分区:

新生代区:

伊甸园区

幸存者1(from)

幸存者2 (to)

老年代

 

①为什么要分区?

不同的对象,他的生命周期不同,这样就可以将不同的对象存储到不同的区域,不同的区域采用不同的垃圾会收算法进行处理,扬长避短.‘

② 创建对象在这些区如何分布?

当一个对象被创建后,首先会放入到伊甸园区,当垃圾器进行回收时会把伊甸园中存活下来的对象放入幸存者区,幸存者区有两个区域1和2.首先把伊甸园区存活的对象放到幸存者1区,当下一次垃圾回收到来时,会将伊甸园区中存活下来的对象和幸存者1区中的对象放入到幸存者2区,清空幸存者1区;之后当垃圾回收器在次进行回收时,则会把伊甸园区和幸存者2区中的对象放入幸存者1区中,清空2区。交替执行。

③什么时候放入老年区?

当垃圾回收每次对对象进行标记时,在对象头中有一块空间回来记录被标记的次数.在对象头中,记录分代年龄只有4个bit位空间,只能记录15次.

 

④堆中各个空间的比例

新生代与老年代默认的比例是1:2 ,但是可以通过参数进行设置 *-XX:NewRatio**=2

伊甸园区和两个幸存者区的比例默认 8:1:1,也可以通过参数设置-XX:SurvivorRatio=8

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

分代收集思想

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

针对新生代会频繁的回收,成为yong gc().

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

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

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

堆的参数

设置整个堆的大小

各个区间的比例

对象年龄

字符串常量池位置

jdk7之后将字符串常量池的位置由方法区转移到了堆中

因为方法区只有在触发FULL GC时才会进行回收,回收效率低.

所以将字符串常量池移到堆中,提高垃圾回收效率.

5.方法区

存储类的信息

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

方法区时线程共享的,也可能会出现内存溢出,也会涉及到垃圾回收

方法区的生命周期也是虚拟机启动就创建,虚拟机关闭销毁.

方法区大小设置

-XX:MaxMataspaceSize windows 中方法区默认最大值是21MB 如果到达21MB就会触发full gc

值也可以设置为-1,没有上限,占用整个计算机内存.

方法区是会涉及到垃圾回收的:

主要回收的是静态常量, 类信息

类信息何时被卸载,满足3个条件:

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

2.该类对应的Class对象不再被其他地方引用

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

3.本地方法接口

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

什么是本地方法?

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

为什么要使用 Native Method

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

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

4.执行引擎

执行引擎的作用:

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

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

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

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

解释器,编译器

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

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

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

起初java中只提供了解释执行的方式,但是解释执行效率较低

后来引入编译器,可以对程序执行中的热点代码进行编译,并把编译后的内容缓存起来,后期执行效率高.

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

程序启动后可以通过解释器立即对代码进行解释执行,不需要等待编译,提高响应速度.

之后对热点代码采用编译器编译执行从而提高后续效率.

5.垃圾回收(GC)

概述:

java语言是提供自动垃圾回收功能的,c++没有自动的垃圾回收,垃圾回收不是java首创,

java在垃圾回收一直在不断的更新。

  1. 什么是垃圾?

没有被任何引用的对象成为垃圾。

     new  User().toString();
      Object  obj = new Object()
      obj.hashCode();
            Object o = obj;
                obj=null;
                 o=null
  1. 为什莫要回收?

① 垃圾对象如果不会收,就会一直占用内存空间,垃圾对象越来越多从而导致内存溢出。

② 对内存空间中的碎片进行整理,如果不整理就,需要存储想树组这样连续的对象时,就会没用空间。

3.早期时如何回收的?

早期想c++代码,需要程序员手动销毁垃圾。

不足:麻烦,当有事忘记删除时会造成内存泄露

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

优点:降低了人们的工作量,减低了内存溢出和内存泄漏的风险。

  1. 自动内存管理的担忧

    自动的垃圾回收,降低了人对内存管理的能力,一旦出现问题无从下手。

  2. 那些区域需要回收?

方法区 堆

在堆中,频繁回收新生代,较少回收老年代,基本不回收方法区。

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

内存泄漏:有些对象已经不再被使用了,但垃圾回收对象有不能把他回收,这种悄悄的占用内存资源的现象。

java怎莫作

垃圾回收算法

标记阶段算法:标记那些对象已经是垃圾。

1.引用计数算法

Object obj = new Object(); 对象内部有一个计数器 有一个引用指向 计数器+1

obj.hashCode();

Object o = obj; 又有一个引用指向对象 计数器+1

obj=null; 计数器-1

o=null 计数器-1

实现简单,

缺点: 不能解决循环引用问题, 需要维护计数器空间, 赋值后要对计数器进行更新操作.

 

2.可达性分析算法(跟可达算法)

main

List list= new ArrayList();

while(true){

list.add(new String());

}

实现思路:

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

 

① 那些对象可以作为GCRoots对象?

1.栈帧锁使用的对象。

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

3.synchronized同步锁对象。

4.jvm系统内部的对象。

② 跟可达性分析算法可以避免对象的循环引用的问题

finalize():finalize()方法是对象逃脱死亡的最后机会。

finailze()时Object类中定义的方法,子类可以重写,但是不要主动的去调用他,

finailze()在对象被回收前,由垃圾回收线程调用,只能被调用一次。

一般情况下不需要重写此方法,如果在对象销毁前需要执行以些释放资源的操作可以重写此方法。

但是要注意 不要在此方法将对象复活,或者出现死循环,因为finailze()会影响GC的性能。

③ 从垃圾回收的角度,对象可以分为3种状态。

可触及的:从根结点开始,可以到达这个对象。

可复活的:已将标记为垃圾,finailze()方法还没有执行(对象可能在finailze()中复活)

不可触及的:当对象finailze()已经执行,而且对象没有复活,则对象进入不可触及状态,会被回收。

④ 对象回收的流程:

判定一个对象objA是否可回收,至少要经历两次标记过程:

1.如果对象objA到GCRoots没有引用链,则进行一次标记。

2.进行筛选,判断对象是否有必要执行finalize()方法

(1) 如果对象objA中没有重写finailze或者 finalize()方法已经被虚拟机

调用过,则虚拟机视为“没有必要执行”,objA 被判定为不可触及的。

(2) 如果重写了finailze()方法,且还没有执行,则会将objA对象放入一个队列中,

稍后 GC 会对 F-Queue 队列中的对象进行第二次标记,如果objA对象在finailze()

方法中与引用链上的任何一个对象建立联系,则会从队列中移出。

3.垃圾阶段回收算法

1. 复制算法

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

 

优点:内存碎片少

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

适用于少量的对象 eg:新生代

2.标记清除算法

清除:并非直接将垃圾对象清除,而是将垃圾对象的地址记录在一个列表里,若有新对象需要分配空间,就从空闲列表中判断空间是否够用,如果空间都用就会用新对象替换垃圾对象。

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

缺点:会产生内存碎片

 

复制算法是针对新生代,对象较少的情况,需要移动对象效率高,不会产生内存碎片但是对于老年代,对象较多情况不适应。

标记清除算法 是针对于老年代存活对象较多,不需要移动对象,但是不会整理内存,会产生内存碎片。

3.标记压缩算法

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

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

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

 

标记-压缩算法与标记-清除算法的比较

标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理,因此,也可以把它称为标记-清除-压缩算法。

二者的本质差异在于标记-清除算法是一种非移动式的回收算法(空闲列表记录位置),标记-压缩是移动式的。是否移动回收后的存活对象是一项优缺点并存的风险决策。

 

标记-清除(非移动式)

 

标记-压缩(移动式)

4.分代收集

年轻代存活的对象生命周期短,需要频繁回收,复制算法效率高,适合用于年轻代.

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

STW:

在垃圾回收线程标记时,需要在某个时间点上,让所有的用户线程暂停一下,

保证在判定对象是否垃圾时的准确性,性能好的垃圾回收器,发生STW次数少.

4.垃圾回收器

垃圾回器是对垃圾回收的落地实现

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

垃圾回收器分类:

按照线程数量分为:

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

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

按照工作模式分为:

独占式: 垃圾回收线程工作时,用户线程全部暂停 STW

并发式: 垃圾回收线程执行时可以不用暂停用户线程 从CMS这款垃圾回收开始引入并发执行.

按照内存工作区域分为:

年轻代区域的垃圾回收器

老年代区域的垃圾回收器

1.CMS(并发标记清除):

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

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

执行过程:

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

并发标记: 垃圾回收线程和用户线程并发执行(用户线程不用暂停)

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

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

 

CSM的优点:

可以做到并发收集。

缺点:

用户线程和垃圾回收线程并发执行导致吞吐量降低。

无法处理浮动垃圾(垃圾回收并发标记时用户线程不暂停,标记完成后随时产生新的垃圾,无法处理只能等到下次垃圾回收处理)

三色标记:

将对象的不同状态分为 黑色 灰色 白色

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

灰色:对象已经被垃圾回收器扫描过,但是对象中还存在灭有扫面的对象。

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

三色标记的过程:

1.刚开始,确定为GC Roots的对象为黑色

2.将GC Roots 直接关联的对象设置为黑色

3.遍历灰色的对象,如果下面还有关联的对象,灰色变为黑色,而下面关联的对象变为灰色

4.重复标记

5.将白色对象清除

三色标记可能出现漏标或错标的问题

漏标:

A关联B , B关联C B为灰色 此时A和B断开联系,但是B为灰色,B和C就为浮动垃圾,需要等到下次回收。

 

本来执行了 A.B=null 之后,B、D、E 都可以被回收了,但是由于 B 已经变为灰色,它仍会被当做存活对象,继续遍历下去。最终的结果就是本轮 GC 不会回收 B、D、E,留到下次 GC 时回收,也算是浮 动垃圾的一部分。

错标:

A关联B , B关联C B为灰色 B于C断开链接 但是A于C建立了新的链接 A为黑色不会再次扫描,就会将C

清除。

 

假设 GC 线程已经遍历到 B 了,此时用户线程执行了以下操作: B.D=null;//B 到 D 的引用被切断 A.xx=D;//A 到 D 的引用被建立

此时A为黑色不会在扫描A,尽管A再次引用了D,但A为黑色不会在去扫描,则D会被当做为垃圾进行清除。

解决:

就时将发生变化的关系进行记录并且重新标记。

2.G1垃圾回收器:

G1也是使用并发标记和清除的.

将整个堆的每个区域又划分为更小的区间,回收时可以根据每个区间的优先级(由里面的垃圾数量),先回收优先级较高的区间.

降低了用户线程的停顿,提高了吞吐量.

对整堆进行统一的管理,没有年轻代和老年代.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值