Java虚拟机(JVM)

1 JVM的主要组成

        类加载器:加载.class文件 -> 运行时数据区

        运行时数据区:

                堆:内存空间最大,new 出来的对象均存在此处;新生代(Eden ToSurvior FromSurvior 8:1:1) 老年代

                栈:线程栈;线程独享

                        局部变量表

                        操作数栈

                        动态链接:引入的方法等,这些需要获得直接引用(线程中 调用某一方法,只存储方法名,真正运行时需要解析成方法的直接引用处)

                        方法出口

                程序计数器:线程独享,标识当前程序运行位置

                方法区(元空间):类元信息、常量池、静态变量

                本地方法栈:其它编程语言交互接口

        字节码执行引擎:1 调度class文件中的指令 2 调用垃圾收集器

        本地接口:其它编程语言交互接口

2 对象创建流程

创建对象的几种方式:

        new关键字

        Class newInstance

        Constructor newInstance

        clone

        反序列化

流程:

        类加载检查:使用new关键创建对象时,jvm先判断该类是否被加载,没有被加载则调用类加载器加载当前类

        分配内存:给当前对象分配内存(内存规整:指针碰撞 内存不规整:空闲列表)

        初始化:静态变量、String常量存储在元空间,并对属性赋初值

        设置对象头:每个对象都会有monitor监视(锁状态:无锁、偏向锁、轻量锁、重量锁),hashCode,分代年龄,线程id;并且保有指向类元信息的指针(类加载时,属性类型、size,动态链接等信息是保存在元空间的)Java虚拟机(JVM)_第1张图片

         执行init:对属性赋值(程序员自己设置的值)

3 内存泄漏

        本质原因:长生命周期的对象一直存着短生命周期对象的引用(导致短生命周期的对象一直无法被GC回收)

        ThreadLocal为什么会发生内存泄漏:

                使用ThreadLocal:为了让每个线程的变量是独享的(线程栈创建ThreadLocal对象,堆内存存储,而每个Thread对象有个成员变量=ThreadLocalMap key为ThreadLocal对象,value为线程独享数据,key对ThreadLocal对象的引用为弱引用的关系,可能会被垃圾回收,使得这块value区域一直无法被访问,最终导致内存泄漏)

                单个线程若执行结束后,数据会被清空,但一般是使用线程池,让线程得到循环利用,若ThreadLocal使用后没进行remove,就会堆积很多无法访问的区域,导致内存泄漏

4 垃圾收集器

        1 在JVM中,释放内存的操作由垃圾回收程序实现(低优先级)

        2 垃圾回收原理:可达性分析,创建对象时,就将引用对象串联起来,标记哪些对象被使用,释放不被使用的垃圾;程序员本身不好把控内存的释放,使用Java提供的GC

        3  java中的引用类型

                强引用:new 对象,只有当对象置空时才回收

                软引用:有用非必须对象,内存不够会回收(浏览器缓存)

                弱引用:下次GC会回收

                虚引用:

        4 如何判断对象可以被回收:引用计数法(无法解决循环引用)、可达性分析GC Roots向下串联引用的对象(当对象没被GC Roots引用时,可以被回收)

        5 JVM垃圾回收算法

                标记-清除:标记无用对象并回收,会产生内存碎片、效率低

                标记-整理:在标记清除的基础上,对存活对象进行整理;解决内存碎片问题,但需要复制对象,存在效率问题

                复制:内存划分为两块,存货对象复制到另一块区域;可用内存减半

                分代:目前市面上JVM选择,将内存区域划分为新生代和老年代

        6 JVM垃圾收集器

                新生代:

                        Serial:串行,简单高效;复制算法

                        Parallel:追求高吞吐量=用户线程时间/(用户线程时间+垃圾回收时间);适用于交互不高的场景

                        ParNew:Serial的并行版本;为了配合老年代的CMS;复制算法

                        G1:并行收集器;标记整理

                老年代:

                        Serial Old:串行;标记整理算法

                        Parallel Old:追求高吞吐;标记整理

                        CMS:高并发、低停顿;标记清除

                        G1:并行收集器;标记整理

        7 分代回收理论

                新生代(占比1/3)(Eden FromSurvivor ToSurvivor 8:1:1):对象一开始分配在Eden区,当对象被调用时,分代年龄+1(对象头标识的分代年龄),触发MinorGC时,将Eden FromSurvivor存活的对象移动到ToSurvivor区;当分代年龄达到15时,移动到老年代

                老年代(占比2/3):当老年代内存不够时,触发FullGC,Stop The World;比MinorGC慢10倍,要避免经常触发FullGC

                新生代->老年代的潜规则:

                        大对象直接进入老年代:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC;新生代内存远小于老年代,为了避免大对象占用新生代内存,可以设置大的对象直接进入老年代

                        长期存活对象进入老年代:默认15,可以修改

                        对象动态年龄判断:Survivor区一批对象的总大小>50%(-XX:TargetSurvivorRatio)时,会将分代年龄为n的直接移动到老年代

                        老年代空间担保机制:在接收年轻代对象时,会对自己的内存进行判断(-XX:-HandlePromotionFailure),若没有足够空间,老年代会进行FullGC,若FullGC后还没内存,则会OOM

5 JVM类加载机制

1 类装载方式

        隐式装载:碰到new关键字

        显示装载:class.forname()

2 类加载器

        启动类加载器:java核心类库

        扩展类加载器:java的扩展库(导入的包等)

        系统类加载器:根据类路径加载类,应用类均是由其加载

        用户自定义加载器:继承ClassLoader

3 类装载步骤

        加载:根据路径找到相应的class文件后导入

        验证:检查class文件的正确性

        准备:静态变量分配内存空间并赋初值

        解析:常量引用替换为直接引用,如引用了其它类时需要直到其引用地址

        初始化:静态变量和静态代码块赋予期望值

4 双亲委派机制

        一个类加载器收到了类加载请求,自己不会加载该类,会交给父加载器区完成,只有当父加载器无法加载类时,才交给子加载器加载

        收到类加载请求时会遵循:自定义类加载器->应用程序类加载器->扩展类加载器->启动类加载器,上层类加载的优先级更高

        String、List等均属于高优先级的类,若我们想让类加载器加载我们自己定义的String类,必须打破双亲委派机制

6 JVM调优

1 什么情况下需要进行JVM调优

        应用程序响应时间过长,无法满足业务需求

        应用程序的吞吐量下降,无法满足业务需求(垃圾收集时间过长)

        应用程序内存使用率过高,甚至出现OOM(老年代内存不合理、分代回收时各参数设置不合理)

        应用程序频繁GC或GC时间过长,影响了程序的性能

2 JVM调优准备工作

        足够的测试和验证(分析内存占用情况、GC表现)

你可能感兴趣的:(JVM,jvm)