天天看点

浅谈Jvm虚拟机Jvm虚拟机笔记

Jvm虚拟机笔记

1.类加载器、.堆、栈、

JVM整体结构

1.1类加载器:

类加载子系统(类加载器)

作用 : 类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.

一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。

ClassLoader的分类

Java中的ClassLoader有三种:

Bootstrap ClassLoader 、

Extension ClassLoader、

App ClassLoader。

  1. Bootstrap ClassLoader

由C++写的,由JVM启动.

启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

2.Extension ClassLoader

Java类,继承自URLClassLoader 扩展类加载器,

对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等

3.App ClassLoader

Java类,继承自URLClassLoader 系统类加载器,

对应的文件是应用程序classpath目录下的所有jar和class等

1.2线程私有区域

java****栈

作用 : 线程私有,它的生命周期与线程相同。存储局部变量, 动态链接, 方法出口等信息.

注 : java栈也是重要内容, 内容比较多, 稍后章节详细讲解.

本地方法栈

作用 : 用于本地方法调用, JDK源码中好多使用了Native关键字, 也就是调用底层C语言编写的方法.

注意: (Sun HotSpot虚拟机)直接就把本地方法栈和Java栈合二为一.
1.2.1栈:

栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一-结 束该栈就Over,生命周期和线程一致,是线程私有的。8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配。

栈存储什么?

栈帧中主要保存3类数据:

本地变量(Local Variables) :输入参数和输出参数以及方法内的变量;

栈操作(Operand Stack) :记录出栈、入栈的操作;

栈帧数据(Frame Data) : 包括类文件、方法等等。

栈的内部结构如下:

栈由一个一个栈帧组成, 栈帧中的结构又由局部变量表, 操作数栈, 帧数据区组成.

局部变量表 : 保存函数参数,局部变量(当前函数有效,函数执行结束它销毁)

操作数栈 : 存中间运算结果, 临时存储空间

帧数据区 : 保存访问常量池指针, 异常处理表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vq9L804z-1605170450426)

浅谈Jvm虚拟机Jvm虚拟机笔记

​ 栈模型

1.3线程共享区域

1.3.1方法区:

供各线程共享的运行时内存区域,它存储了每一个类的结构信息,例如运行时的常量池、字段和方法数据、构造方法和普通方法的字节码内容。方法区也叫规范,不同的虚拟机里头实现的不一样,最典型的就是永久代和元空间。

实际而言,方法区(MethodArea)和堆–样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的- -个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。

1.3.2堆:
浅谈Jvm虚拟机Jvm虚拟机笔记

​ 堆模型

一个JVM实例只存在一个堆内存,堆内存的大小可调节,类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三个部分:

  • 新生区:
  • 养老区:
  • 永久区:

堆内存逻辑上分为三部分:新生+养老+永久(元空间)

当Eden满了,产生GC也叫轻GC;

当Old满了,产生Full GC. 也叫FGC

当Full GC多次回收之后,发现养老区也没有空间了,这时产生OOM

1.4JVM对象模型:

Java是一种面向对象的语言,而Java对象在JVM中的存储也是有一定的结构的。而这个关于Java对象自身的存储模型称之为Java对象模型。

HotSpot虚拟机中(Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机),设计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。

每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass对象,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了对象头以及实例数据。

浅谈Jvm虚拟机Jvm虚拟机笔记

新生区

新生区是类的诞生、成长、消亡的区域,一一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。新生区又分为两部分:伊甸区(Eden space) 和幸存者区(Survivor pace) , 所有的类都是在伊甸区被new出来的。幸存区有两个:

0区(Survivor 0 space)和1区(Survivor 1 space) 。当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(MinorGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩余对象移动到幸存0区。若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到养老区。若养老区也满了,那么这个时候将产生Ma jorGC (Fu1GC) ,进行养老区的内存清理。若养老区执行了Full GC之后发现依然无法进行对象的保存,就会产生00M异常“OutOfMemoryError"。如果出现java.lang.QutOfMemoryError; Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:

(1)Java虛拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。

(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

From区和to区位置和名分不是固定的,每次GC后会交换 谁空闲谁是to区

Java堆从GC的角度还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年

堆(Heap)

Young

Old

Eden

From

To

(8/10)

(1/10)(1/10)

新生代(1/3)堆空间

老年代(2/3)堆空间

MinorGC的过程(复制->清空->互换)

1: eden、 SurvivorFrom 复制到SurvivorTo,年龄+1

当产生GC回收时候,要求所有Eden区都要清空,因此那些没有被死的对象需要将其复制到from区或者to区。年龄+1

首先,当Eden区满的时候会触发第一次GC,把还活 着的对象拷贝到SurvivorFrom区,当Eden .

区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回

收后还存活的对象,则直接复制到To区域( 如果有对象的年龄已经达到了老年的标准,则赋

值到老年代区),同时把这些对象的年龄+1

  1. 假设第一轮产生100个对象1…100住在Eden区,并且只存活了2个对象记住A、B。那么经过GC之前将在Eden区的存活的这两个对象A.B复制到幸存区假设from区(to区也可以)。
  2. 当第二次又产生1…100个新对象住在Eden区的的时候,假设存活了一个对象即为C.那么这一轮GC时候,会将这100个对象和上一轮的AB对象一起进行处理。假设这102个对象只存活了三个对象(ABC)。那么在GC处理之前会将这三个对象复制到当前空闲的幸存区。GC之后有交换,哪个幸存区空闲哪个是to区。由于第一轮AB对象存在from区,那么当前空闲的幸存区即为to区。此时便将这三个对象复制到to区来。并且需要注意的是,每经过一次GC之后,存活下来的对象都会+1,在这里也就是AB的年龄加了2,C年龄加了1.
  3. 之后重复上述步骤。。。

2:清空eden、SurvivorFrom

然后,清空Eden和SurvivorFrom中的对象, 也即复制之后有交换,谁空谁是to

B: SurvivorTo和 SurvivorFrom互换

最后,SurvivorTo和SurvivorFrom. 互换,原survivorTo成为 下一次GC时的SurvivorFrom区。部

分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold

决定,这个参数默认是15),最终如果还是存活,就存入到老年代

1.5永久代、元空间、方法区

误区:

方法区相当于一种规范,我们都知道天上飞的理念必须要有落地的实现,那么有规范(接口)就必须有实现类,

而不同的虚拟机中实现完方法区之后,叫法不同,有的叫原空间有的实现叫永久代。

但是在jdk1.7中实现方法区中的叫永久代。

​ 在jdk1.8中实现方法区中的叫元空间。

永久代和元空间本质区别:

①:在1.7的永久代中,永久代是属于堆内存中的一部分。

②:在1.8的元空间中,元空间是属于堆外内存的一部分,也就是占用的是你本机拥有的物理内存。

永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class, Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

在Java8中, 永久代已经被移除,被一个称为元空间的区域所取代。元空间的本质和永久代类似元空间与永久代之间最大的区别在于:永久带使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

误区: 方法区在jdk7中叫永久代

​ 在jdk8中叫元空间

​ 方法区在堆中,常量池存在方法区中。

​ jdk7版本中,字符串常量池位于堆中的永久代中,

​ 在JDK1.8 hotspot移除了永久代用元空间取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(堆外内存)

[GC (Allocation Failure) [PSYoungGen: 506K->488K(1024K)] 506K->512K(1536K), 0.0005064 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[Full GC (Ergonomics) [PSYoungGen: 1006K->222K(1024K)] [ParOldGen: 504K->426K(512K)] 1511K->648K(1536K), [Metaspace: 3291K->3291K(1056768K)], 0.0044686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[外链图片转存失败,源

浅谈Jvm虚拟机Jvm虚拟机笔记
浅谈Jvm虚拟机Jvm虚拟机笔记

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sdt29Q3G-1605170450434)(…/AppData/Roaming/Typora/typora-user-images/image-20201112132630174.png)]

浅谈Jvm虚拟机Jvm虚拟机笔记

​ jvm一览图

2.GC:(四大回收算法)

  • 分区算法

原理

上面介绍的分代收集算法是将对象的生命周期按长短划分为两个部分, 而分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间. 在相同条件下, 堆空间越大, 一次GC耗时就越长, 从而产生的停顿也越长. 为了更好地控制GC产生的停顿时间, 将一块大的内存区域分割为多个小块, 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次GC所产生的停顿.

浅谈Jvm虚拟机Jvm虚拟机笔记
  • GC分带算法;
  • 次数上频繁收集Young区
  • 次数上较少收集old区
  • 基本不动元空间
浅谈Jvm虚拟机Jvm虚拟机笔记

JVM在进行GC时,并非每次都对上面三个内存区域- - 起回收的,大部分时候回收的都是指新生代。

因此GC按照回收的区域又分了两种类型,一种是普通GC (minor GC),一种是全局GC (major GC or Full GC)

Minor GC利和Fu/l GC的区别

普通GC (minor GC) :只针对新生代区域的GC,指发生在新生代的垃圾收集动作,因为大多数Java对象存活率都不高,所以Minor GC非常频繁,- -般回收速度也比较快。

全局GC(majorGCorFullGC):指发生在老年代的垃圾收集动作,出现了MajorGC,经常会伴随至少–次的MinorGC(但

并不是绝对的)。Major GC的速度一般要比Minor GC慢上10倍以上

为什么FullGC要比GC慢?

答:FullGC发生在old区 old区占堆空间的2/3.因此回收的速度慢于GC

HotSpot JVM把年轻代分为了三部分: 1 个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1:1,- -般情况下,新创建的

对象都会被分配到Eden区(- - 些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在

Survivor区中每熬过-次Minor GC,年龄就会增加1岁,当它的年龄增加到一-定程度时,就会被移动到年老代中。因为年轻代中的对象

基本都是朝生夕死的(90%以上:), 所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只

用其中-一块,当这一-块内存用完,就将还活着的对象复制到另外- - 块上面。复制算法不会产生内存碎片。

所谓的复制算法===》就是讲内存分为两块,一块是eden+from 一块是to区。

浅谈Jvm虚拟机Jvm虚拟机笔记

复制算法它的缺点也是相当明显的。

1、它浪费了一半的内存,这太要命了。

2、如果对象的存活率很高,我们可以极端一一点,假设是100%存活,那么我们需要将所有对象都复制-遍,并将所有引用地址重

置一遍。复制这一.工作所花费的时间,在对象存活率达到一定程度时, 将会变的不可忽视。所以从以 上描述不难看出,复制算法要想使

用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

2.1.引用计数法:

原理

引用计数算法很简单,它实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。

浅谈Jvm虚拟机Jvm虚拟机笔记
浅谈Jvm虚拟机Jvm虚拟机笔记

引用计数垃圾收集机制,它只是在引用计数变化为0时即刻发生,而且只针对某一个对象以及它所依赖的其它对象。所以,我们一般也称呼引用计数垃圾收集为直接的垃圾收集机制.垃圾收集的开销被分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。

优点:

实时性较高, 不需要等到内存不够时才回收

垃圾回收时不用挂起整个程序, 不影响程序正常运行.

缺点:

回收时不移动对象, 所以会造成内存碎片问题.

2.2标记清除法:(标垃圾 清除垃圾)

原理

它的做法是当堆中的有效内存空间被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

标记:标记的过程其实就是,从根对象开始遍历所有的对象,然后将所有存活的对象标记为可达的对象。

清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。

优点 : 实现简单

缺点 :

效率低, 因为标记和清除两个动作都要遍历所有的对象

垃圾收集后有可能会造成大量的内存碎片, 垃圾回收时会造成应用程序暂停.

浅谈Jvm虚拟机Jvm虚拟机笔记

用通俗的话解释一下标记清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将要回收的对象标记–遍,最终统画收这些对象,完成标记清理工作接下来便让应用程序恢复运行。

2.3标记压缩法:

原理

既然叫标记-压缩算法,那么它也分为两个阶段,一个是标记(mark),一个是压缩(compact).

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决 了碎片化的问题。

标记 : 标记的过程其实就是,从根对象开始遍历所有的对象,然后将所有存活的对象标记为可达的对象。

压缩 : 移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。

优点 :

标记压缩算法是对标记清除算法的优化, 解决了碎片化的问题

缺点 :

还是效率问题, 在标记清除算法上又多加了一步, 效率可想而知了

浅谈Jvm虚拟机Jvm虚拟机笔记

2.4复制算法

原理

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,并以此排列, 然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。

小结

优点:

在垃圾多的情况下(新生代), 效率较高

清理后, 内存无碎片

优点:

在垃圾多的情况下(新生代), 效率较高

清理后, 内存无碎片

所谓的复制算法===》就是讲内存分为两块,一块是eden+from 一块是to区。

复制算法它的缺点也是相当明显的。

1、它浪费了一半的内存,这太要命了。

2、如果对象的存活率很高,我们可以极端一一点,假设是100%存活,那么我们需要将所有对象都复制-遍,并将所有引用地址重

置一遍。复制这一.工作所花费的时间,在对象存活率达到一定程度时, 将会变的不可忽视。所以从以 上描述不难看出,复制算法要想使

用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

jvm