Jvm(二)

一、类加载

 java类加载分为5个阶段,加载,验证,准备,解析,初始化,使用,销毁

        加载:会在内存中生成代表该类生成class对象。

        验证:校验class文件的字节流是否符合jvm虚拟机规范

        准备:为该类的变量设置初始值

        解析:解析阶段是值常量池中符号引用变成直接引用的过程

        符号引用:每种jvm的实现是不同的,但是他们识别的符合引用一定是相同的,应该他们被定          义在了java虚拟机class文件规范中

        直接引用:可以是一个指向目标的指针,可以是相对的偏移量,也可以是一个能间接定位到            目标的句柄。只要直接引用,那么一定会在内存中存在

        初始化:是类加载最后一个阶段,是执行类构造器<client>方法的过程,<client>是编译器自            动收集类中变量和静态代码块中代码合成出来的。

二、类加载机制

        Java中有三种类加载器,BootstrapClassLoader启动类加载器(负责加载java_home/lib中的   类),ExtensionClassLoader扩展类加载器(负责加载java_home/lib/etx中的类),ApplicationClassLoader应用程序类加载器(负责加载用户路径中的类)。

类加载机制源码

        当接收到一个类请求时,不会先使用自己的类加载器去加载,会先去找自己的父类去加载,每个阶层都是一样,当父加载提示无法完成请求时,才会尝试使用子类的加载器去加载,当所有的类加载器都无法完成请求时。会抛出classNotFound的异常。

ClassLoader#loadClass
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {//如果class没有被加载过
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//如果父级不等于null,递归调用父级的类加载器
                        c = parent.loadClass(name, false);
                    } else {//等于null,则调用BootstrapClassLoader这个来加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

三、JVM内存区域

1、虚拟机栈:线程私有的,每个方法都对应着一个虚拟机栈

2、堆:java堆中存放中运行中产生的大部分对象。

3、本地方法栈:线程私有,存放native方法

4、程序计数器 线程私有的 java中存放虚拟机字节码指定的地址,native存的是undifined

5、方法区:线程是共享的,方法区是jvm规范规定的,逻辑划分

        jdk1.7中方法区的实现叫永久代,1.8实现叫元空间

        每一个类的结构信息,eg运行时常量池

        1.6永久代,常量池也放到方法区中

        1.7永久代,把常量池和静态变量放到堆中

        1.8元空间 64位jvm默认元空间大小为21m,当元空间内存不足时会发生fullGc,当fullGc后内存空间还是不足时,会发生动态扩容,元空间属于本地内存,理论上系统内存有多大,元空间就有多大。

四、对象内存分配

(1)对象的创建方式:对象的创建方式,new,反射,序列化,clone

(2)对象存放在什么地方:大部分对象创建出来会分配在堆中,少部分对象在jvm开启逃逸分析后,发现没有方法逃逸的对象会在栈上分配。

public void demo(){
    User user = new User();
    user.sayHi();
    StringBuffer stringBuffer  = new StringBufer();
    new Thread(){
        stringBuffer.append("xxx");
    }
}


此时user对象没有逃逸到方法外,直接在栈上进行内存空间分配即可。stringBuffer 线程逃逸了
优化:
        2.1没有方逃逸,那么可以栈上分配

        2.2没有线程逃逸,那么可以同步策略擦除

        2.3标量、聚合量;标量替换

        2.4 JVM优化中关于逃逸分析的参数

                -XX:+DoEscapeAnalysit:开启了逃逸分析

                -XX:+PrintEscapeAnalysit:如果开启了逃逸分析,那查看逃逸分析的结果

                -XX:+EliminateAllocations:开启标量替换

                -XX:+ElimitnateLocals:开启同步擦除

3、如何分配空间?

        3.1 指针碰撞 如果被分配内存空间是连续的,只需向空闲内存方向移动一下指针

        3.2 空闲列表 如果被分配内存空间不是连续的,需要用一个空闲列表来记录哪些空间可用,并在可用的内存中分配足够的空间,并修改空闲列表

       在多线程环境中为了保证线程安全的,因为堆是线程共享的,所以在多线程环境中他的线程不是安全的。jvm会给每个线程在endn区分配一个私有的缓存区域,这一块也叫做TLAB(thread loacal allocation bufer),创建出来的对象首先会在TLAB中分配内存。如下图所示

        -XX:+UseTLAB,TLAB在endn区默认占比是百分之1

        -XX:+TLABWasteTargetPercent:TLAB占endn区的百分比

        当对象大小大于TLAB的大小时,会cas竞争的方式在堆中分配空间。

4、对象的结构?
        新创建的对象在堆中存储,那么对象内容的内存空间结构如何?new Object();
        对象结构=对象头+实例数据+对齐填充对象头=markword+kclass+[数组长度]实例数据:相同宽度的数据放到一起对齐填充:8字节整数倍填充

 对象指向

1、直接指向

        一次指向,对象发生变化时需要查询指向

2、句柄指向

        两次指向,对象发生变化时不用重新指向

 五、垃圾回收算法

判断对象存活

        1.引用计数法 任何有对象关联的引用,对象的引用计数都不为0,说明该对象不太可能被用到,变成可回收的目标 优点:标记到的就可以立刻被清。不会频繁触发,减少系统卡顿。缺点:循环引用无效

        2.可达性分析法 解决引用计数循环引用无效的问题 ,如果在GCroots和一个对象没有可达的路径,则该对象是不可达的,不可达对象变为可回收对象至少经过两次标记后才会是可回收对象,面临回收

垃圾回收算法

        1.标记清除算法 :先遍历一次对存货对象进行标记,在遍历一次把非存活对象回收,缺点会产生碎片

        2.复制算法:把内存分成相等容量的两份,每次只使用一份,当一份使用完后,把存活对象复制到另一份中,缺点浪费空间,复制算法的效率和存活对象的多少有关,存活对象数量越多,复制算法的效率就越低

        3.标记整理算法:首先对存活对象进行标记,然后使存活对象先一端移动,最后把端边界外对象全部清除

        4.分代收集算法:核心思想是对象存活分为多个区域,一般虚拟机中堆分为新生代和老年代,新生代中需要回收的对象比较多,老年代中需要回收的对象比较少,所以可以采用不同垃圾回收算法。

        (1)、新创建的对象尝试放到eden,,如果该对象比eden总量都大,那么直接放到老年代

        (2)、eden没有足够的空间,触发一次minorGC,讲enden和from区的存活对象,移动到to区,对象年龄+1。然后将enden和from区进行回收。最后from区和to区互换
        (3)、如果to去没有足够的空间,那么讲满足条件的对象移入到老年代,对象的年龄达到了一定数值默认值为15
        (4)、移动过程中老年代空间也不足了。需要回收老年代的mojorGC,往往回收老年代的时候需要将整个堆空间一并回收fullGC

六、垃圾回收器

 图中,线1的分代回收组合在jdk8中废弃,jdk9中移除。线2和CMS在jdk14中废弃。jdk8中默认

Parallel Scavenge和Parallel Old

CMS垃圾回收器


1、初始标记: stop-the-world,标记GCRoots直接关联的对象

2、并发标记:并发追溯标记,程序不会停顿

3、重新标记:暂停虚拟机,扫描CMS堆中的剩余对象>并发清理:清理垃圾对象,程序不会停顿

4、并发重置:重置CMS收集器的数据结构

CMS回收器耗时多的地方在于并发标记和并发清除,总体来说cms垃圾回收是和用户线程并发执行的。

缺点:1.会产生浮动垃圾,jvm默认8%的浮动垃圾预留空间,当预留空间满了之后会退化serialOld单线程回收。

2.因为和用户线程是并发执行的,所以对cpu资源比较敏感

3.因为使用的标记清除算法,会产生碎片,可以通过参数CMSFullGCsBeforeCompaction设置使其大于一定次数后进行碎片整理,默认值为0,就是默认每次fullGC后都需要进行碎片压缩。该参数设置减少full GC压缩的次数,节省了gc时间,也就更容易受碎片化问题的困扰。

G1回收器

1、G1垃圾收集器将整个 JVM 内存分为多个大小相等的region,年轻代和老年代逻辑分区 。

2、G1 是 Java9 以后的默认垃圾回收器了

3、G1 在整体上使用标记整理算法,局部使用复制算法

4、G1 的每个 Region 大小在 1-32M 之间,可以通过 -XX:G1HeapRegionSize=n 指定区大小。

5、总的 Region 个数最大可以存在 2048 个,即heap最大能够达到32M*2048=64G

6、obj<0.5 可以称为年轻代,0.5<obj<1,那么放到old区,old标记为H 1<obj<n,连续的n个region,作为H 

概念

rset:每个region都有一个叫做rset的小区,它代表了其他region引用了当前region

对象的记录

cset:本次GC需要清理的Region集合

mixGC的过程

1、初始标记:标记出GCRoot对象,以及GCRoot所在的Region(RootRegion)

      Root Region Scanning:扫表整个old的Region

2、并发标记:并发追溯标记,进行GCRootsTracing的过程

3、最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象

4、清理回收:根据时间来进行价值最大化的回收,重置rset

G1相关的参数配置

-XX:+UseG1GC :设置使用 G1 垃圾回收器

-XX:MaxGCPauseMillis=n :最大 GC 停顿时间,毫秒值

-XX:InitatingHeapOccupancyPercent=n:当堆空间占用到 n 兆时就触发 GC(45)

-XX:GoncGCThreads=n:并发 GC 使用的线程数

-XX:G1ReserverPercent=n:设置作为空闲空间的预留内存百分比(10%)

标记算法

CMS和G1使用的标记算法都是三色标记算法

三色标记算法

白色:没有访问到

黑色:本对象已经访问到,而且对象中的引用也访问到

灰色:本对象访问过,但是对象中的属性没有访问过

CMS 三色标记算法会有浮动垃圾 错标:写屏障+增量更新

浮动垃圾:并发标记时,业务线程把GC线程已经标记对象变为null,那么该对象的引用就没有意义了,但是已经被标记过,不会回收,会产生浮动垃圾。

错标:黑色节点出现重新指向了一个白色节点,需要把黑色节点变成灰色节点,不然白色节点会被回收 。 

错标解决方案:

var G = objE.fieldG; //读
objE.fieldG = null;  //写(G1增加写屏障,断开时,生成一个SATB快照)
objD.fieldG = G;  //写(CMS这重新指向时增加写屏障)

CMS:写屏障(重新指向时使用锁防止不被其他线程影响)+增量更新

G1: 写屏障+SATB(快照,根据快照可把对象都标记出来)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值