JVM--你想要的都在这里

一 是什么--功能

1.1 软件层面机器码的翻译--》代码到机器指令的一次转换

1.2 内存管理

image.png

 

二 JVM运行时数据区

image.png     image.png

2.1程序计数器(线程独享:一个线程一个):指向当前线程正在执行的字节码指令的地址,行号(原因:线程执行是在CPU上,CPU是抢占式的,线程有可能没有执行完就被抢夺了,所以需要一个地方去存储线程执行地址)

 

2.2虚拟机栈(线程独享):存储当前线程运行所需要的数据,指令,返回地址。

栈帧存放一个方法的全部信息,一个栈帧一个方法,局部变量存放变量信息,操作数栈存放引用地址,以及需要运算操作的数。动态链接指的是例如@autowired注入service,指向service实例。出口就是返回地址,正常 /异常:走异常处理流程

image.png

 

tip:递归调用会有多少个栈帧存在--只有一个栈帧存在,递归调用栈帧太多,容易导致栈溢出

 

2.3本地方法栈:native方法就是本地方法,使用c/c++语言编写的。

 

2.4 方法区(线程共享):类信息(类的源信息),常量,静态变量,JIT(just in time,即时编译技术,动态生成类)

运行时常量池:存放编译器生成的各种字面量和符号引用。

 

2.5 堆heap(线程共享):
image.png

a.对象的生命周期不一样,所以要分代,新生代和老年代的默认比例是1:2.

 

b.1.8版本之前有永久代,1.8版本之后替换为了meta space,是一个链表,能够动态扩容。但是并不一定就好。因为动态扩容很可能会导致内存溢出。

1.7版本之前,java虚拟机会将存放class和meta的信息存放在方法区,即永久代指的就是方法区

1.7版本讲这些数据放到了堆上。

1.8版本使用了meta space(放在了主存,而不是虚拟机中)存放这些数据

 

为什么会用meta space取代永久代:永久代经常内存溢出,报错java.lang.outofmemoryError:permGen

 

c.新生代区域划分:eden:s0:s1----8:1:1,参数-XX:SurvivorRatio设置

为什么:复制回收算法,8:1:1是因为有90%的空间可利用

复制回收算法是对象先放在eden中,eden不够用了,将新数据放到s0或s1中,eden清空。

 

d.98%的对象会在minor gc的时候被回收掉,如果新生代空间不够,会向老年代分配担保

 

e.major GC(老年代的GC)要比minor GC慢十倍,所以最好在新生代进行回收。

 

f.新生代到老年代的条件

image.png

例外:大对象直接分配到老年代中。

 

g 老年代内存不够怎么办

分配担保:minor GC之前,判断老年代最大可用连续空间是否大于新生代所有对象总空间,如果不满足,判断老年代最大可用连续空间是否大于历代晋升老年代的平均大小,如果不满足或者HandlePromotionFailure设置不允许担保失败,则进行full GC(minor GC + major GC)

 

h.各代大小设置

image.png

 

 

三 什么样的对象需要被GC

1.判断算法:

a.引用计数法:不会去用因为存在相互引用

b.可达性分析:GC Root到对象有没有链接,所以下图:B不能被回收,C可以被回收

 

2.GC Root可以有以下组成:

虚拟机栈中本地变量表引用的对象

方法区中类静态变量引用的对象、常量引用的对象

本地方法栈中JNI引用的对象  

image.png

 

3.不可达进入finalize方法,可以再次挽回(可以用GC Root再指向一下)

 

4.引用--强引用,软引用,弱引用,虚引用

1、强引用:

   java默认的引用就是强引用,尽管jvm内存不足,在gc的时候也不会进行回收,会抛出内存溢出的异常。

   Person p = new Person();就是一个强引用

 

 2、软引用:

   软引用在jvm内存充足的时候,不会被回收,在不充足的时候会被回收,例子如下

   WeakReference<Person> personSoftReference = new WeakReference<Person>(new Person());

 

 3、弱引用:

   弱引用在gc的时候会被回收。

 

 4、虚引用

   虚引用创建后就被回收,感觉没啥子用,回收的时候只会通知一下;

 

四.回收算法--垃圾回收器就是算法的实现

1.标记-清除算法

image.png

缺点:a.标记和清除过程比较复杂  b.内存碎片比较多

 

2.复制回收算法

 

3.标记-整理算法:边标记边整理--没有内存碎片

image.png

 

五 垃圾回收器

image.png

image.png

 

年轻代:

1.serial:单线程

2.parnew:多线程

3.parallel Scavenge(全局):以提高吞吐量为主

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)

image.png

 

老年代:

1.serial old:serial的老年代版本,CMS备用预案,可以和任何新生代回收器配合,concurrent mode failusre时使用标记-整理算法

 

2.parallel old:只能和parallel新生代配合,标记-整理算法

 

3.CMS:标记-减少回收停顿时间,使用标记-清除算法

a.初始标记:找到GC ROOT直接能关联到的对象,

b.并发标记: 继续往下找(GC tracing)

c.重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

d.并发清理

 

image.png

 

 

老年代和新生代:G1

 

六 堆内存分配流程

1.每个线程在堆上的内存由指针引用,堆上已分配内存区与空闲区会用一个指针隔开,在堆上分配内存就是将指针向空闲区移一个对象大小的区域,这个过程就是指针碰撞。

而指针碰撞的方式不适用于多线程,于是采用freeList--空闲列表方式,将空闲区通过一个列表管理起来--freelist.

具体采用哪种方式要看使用的是哪个垃圾回收器--也就是这款垃圾回收器是否带有compact功能--整理压缩算法,如果是标记-整理算法,那么就可以使用指针碰撞的形式。如果是标记-清除算法,则使用空闲列表方式。

 

 

2.对象一开始一般会在新生代分配内存,如果是多个线程并发,则会通过CAS也就是加锁去分配内存。

而如果线程太多·CAS竞争太激烈,也会消耗CPU。所以JVM提供TLAB,在堆里每个线程都有一个自己的thread local allocation buffer(TLAB).所以线程为对象创建内存,会在thread local allocation buffer分配内存

image.png                                                                                                                                                                                                                                                                                                                                            七.应用线程与GC线程的交互:因为GC是STW

1.安全点:时间周期比较长的点,例如循环跳转,异常跳转,方法调用等。

方式:a.抢占式:发出GC时,查看当前点是否在安全点,如果不在安全点,继续运行,等待安全点

b.主动式:GC运行时,设置一个标志flag,当应用线程走到安全点时,查看flag,如果GC在跑,就停下来,如果不在跑,就继续走。

2.安全区域saferegion,应用线程没有在走,就设置一个saferegion,如果发生GC,是处于sageregion中,则继续GC.