JVM学习笔记

(本人文本笔记上粘贴过来,格式有点乱,后续会进行排版整理)

jvm: 跨语言的平台,支持js,kotlin等
类加载过程:class字节码文件->加载->链接(验证,准备,解析)->初始化

  1.     验证:验证格式是否符合虚拟机要求
  2.     准备:变量分配内存,变量分配初始值(赋0值),常量在编译时已分配值
  3.     解析:常量池中符号引用转换为直接引用
  4.     初始化:构造器方法clinit按所有类变量(静态变量)的赋值动作和静态代码块的语句按出现顺序执行。若有父类,先执行父类的clinit方法、

类加载器:只责加载class文件,Execution engine决定是否可以运行,加载的类信息存放于方法区,方法区还会放常量池信息

  •     分类:引导类加载器(加载java核心类库);扩展类加载器;系统类加载器;自定义加载器

    ClassLoader类是一个抽象类,其后所有的加载器都继承自ClassLoader(不包括引导类加载器,因为C编写)

  •     继承关系:ClassLoader->URLClassLoader->扩展类加载器/系统类加载器/自定义加载器
  1.     引导类加载器:c/c++编写,加载java核心类库;没有父加载器;是 扩展类加载器和应用程序类加载器(系统类加载器)的父加载器 注:父加载器并非继承关系
  2.     扩展类加载器:java编写,加载扩展目录下的类库,父类加载器为引导类加载器
  3.     应用程序类加载器(系统类加载器):java编写,是程序中默认的加载器,一般的java应用的类均有她来加载;父类加载器是扩展类加载器
  4.     自定义类加载器:作用:防止源码泄露,修改类加载方式等;继承ClassLoader类(抽象类)重写findClass()方法,若没有复杂要求可直接继承URLClassLoader类,这样可避免自己写findClass()方法。

双亲委派机制:避免类重复加载;防止核心类被篡改(若自定义核心类如java,lang.String,则仍由引导类加载器加载,无视自定义的String类)
    如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;(注意是父类加载器)
    如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归请求最终将到达顶层的启动类加载器;
    如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

在jvm中识别两个class对象是否是同一个类的两个必要条件:
    1.类的完整类名必须一致,包括包名;
    2.类加载器对象必须相同;

java对类的使用:主动和被动:

  •     主动:

    1.创建类的实例
    2.访问某个类或接口的静态变量,或者对该静态变量赋值
    3.调用类的静态方法
    4.反射(比如: Class.forName ( "com.atguigu . Test") )
    5.初始化一个类的子类
    6.Java虚拟机启动时被标明为启动类的类
    7.JDK 7开始提供的动态语言支持:  java . lang.invoke.MethodHandle实例的解析结果;REF_getstatic、REF_ putstatic、REF_invokestatic句柄对应的类没有初始化,则初始化

  •     被动使用: 不会导致类的初始化

方法区在jdk8后叫元空间

进程资源:程序计数器,栈,本地栈,堆,堆外内存(元空间等)
线程独有:程序计数器,栈,本地栈
线程共享:堆,堆外内存(元空间等)

jvm允许一个应用有多个线程并行运行: 
    hotspot中每个线程和os中本地线程直接映射,java线程和本地线程同时创建,同时回收;
    os负责所有线程调度到可用的cpu上,一旦本地线程初始化成功,就会调用java线程中run()方法

  • PC寄存器:用来存储指向一个java虚拟机栈帧(非本地方法栈,本地方法栈中是调用C时使用的)中下一条指令的地址,由执行引擎读取下一条代码,方便cpu在调度不同线程时,每个线程都能记住自己该执行哪一条指令了。(唯一一个不会出现outofmemoryerror的区域,其他4个区域会发生oom;只有方法区和堆会执行垃圾回收)
  • 虚拟机栈:也叫java栈,线程创建时会创建一个虚拟机栈,每个栈帧对应每个方法,保存方法的局部变量(8种基本数据类型,对象的引用地址),部分结果,并参与方法的调用和返回;栈的生命周期与线程一样  

    设置栈大小:-Xss256k; -Xss1M
    正常return或者抛出异常均会导致栈帧被弹出

栈帧: 局部变量表,方法返回地址(正常或者异常退出 ),操作数栈(表达式栈 ),动态链接(指向运行时常量池的方法引用),附加信息

局部变量表:相当于一个数组,在编译期已确定最大深度,存放编译器可知的8种基本数据类型,引用类型,返回地址类型,byte,short,char存储前会转换为int,bool也转化为int,0代表false,非0代表true;基本单位为槽slot, long和double占8个字节,占用两个slot,其他基本数据类型和引用类型占用1个slot。(注:若是实例方法,即非静态方法,比如非静态成员方法和构造方法,需要在局部变量表索引为0处放入this的引用作为局部变量)
    slot也可重复利用,若一个局部变量过了其作用域,他的slot槽就可被其他局部变量占用。 
    变量的分类:

  •         按照数据类型:基本数据类型;引用数据类型
  •         按照在类中声明的位置分类:

                     成员变量:使用前都有默认初始化赋值

                                类变量:在链接的准备阶段会默认赋值;在初始化阶段显式赋值
                                实例变量:随着对象的创建,在堆中分配实例变量空间,并默认赋值
                    局部变量:使用前必须显式赋值


操作数栈:在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,一般用于保存计算的中间结果,复制,交换,求和等
            操作数栈的最大深度在编译器就定义好了,4字节占用一个单位深度,8字节占用2个单位深度 
            作用实例: byte i=15; int j=8; int k=i+j;   执行流程:将15压入操作数栈中,弹出15并存入局部变量表索引为1处(索引0为this),将8压入操作数栈中,弹出8并存入局部变量表索 引为2处,分别将15,8压入栈,弹出栈由执行引擎执行加操作,将结果23压入栈,弹出23保存在局部变量表索引为3处,return

动态链接 :

     源文件被编译到字节码文件中时,所有的变量和方法引用都保存在方法区的运行时常量池中(在执行指令中保存的是符号引用,直接引用保存在常量池中,方便该类的其他方法进行引用,所以在执行时需要将符号引用转化为直接引用),动态连接的作用就是为了将这些符号引用转换为调用方法的直接引用
    静态链接:若被调用的目标方法在编译期可知,且运行期保持不变,这种情况下调用方法的符号引用转换为直接引用的过程称之为静态链接
    动态链接:若被调用的方法在编译期无法被确定下来,只能够在运行期将调用方法的符号引用转换为直接引用,这个过程被称为动态链接
    非虚方法(确定方法):无法被重写的方法(静态方法,私有方法,final方法,实例构造方法),通过super调用的父类方法        
    虚方法(不确定方法):除了以上方法    
    普通调用指令:invokestatic:   调用静态方法,在解析阶段确定方法
                            invokespecial:   调用构造方法,私有方法和super调用的父类方法,解析阶段确定方法
                            invokevirtual: 调用虚方法(除了final方法)
                            invokeinterface:  调用接口方法:     
方法重写的本质  :找到操作数栈顶的第一个元素所执行对象的类,记为C,若在C的常量池中找到与符合的方法,通过权限校验后返回这个方法的直接引用。否则按照继承关系对C的各个父类进行上一步的搜索
虚方法表:每个类都有一个虚方法表,表中存放着该类各个方法的实际入口;虚方法表在类的链接阶段被创建并开始初始化,类变量初始值准备完成后,jvm会把该类的方法表也初始化完

方法返回地址:存放调用该方法的pc寄存器的值
    方法结束方式:正常完成;异常非正常退出
    无论哪种方式退出,方法推出后都应返回到该方法被调用的位置,正常退出时,pc寄存器的值作为返回地址,异常退出时返回地址通过异常表来确定;正常完成和异常完成的区别在于,通过异常完成退出不会给他上层调用者产生返回值。
=================================================================================================

线程安全:若只有一个线程才能操作的数据,则必是线程安全的; 若有多个线程操作此数据,即共享数据,若不考虑同步机制的话,会存在线程安全问题

堆:jdk7前新生代,老年代,永久代  jdk8:新生代,老年代,元空间(jdk8中堆大小一般不包括元空间)

设置堆空间大小:-Xms600m表示堆区起始内存大小为600m;-Xmx700m表示堆区最大内存大小700m;一旦堆中内存大小超过-Xmx的值,将会抛出oom;通常会将这两个参数设置相同的值,为了在垃圾回收后不需要重新计算堆区大小

ygc/Minor GC(只能由伊甸园区满了触发该回收,幸存者区也会被回收,但是不会触发回收):新创建的对象放伊甸园区,当伊甸园区满时,jvm垃圾回收将伊甸园进行垃圾回收,将伊甸园中不再被引用的对象销毁,同时将伊甸园中幸存者放入幸存者0区,此时伊甸园被清空,加载新的对象放入伊甸园,若再触发垃圾回收,幸存者0区中未被回收的对象放入幸存者1区,伊甸园幸存者也放入1区,再次回收时,再放入幸存者0区,以次循环,满足一定次数时,放入养老区
        若伊甸园空着,对象太大放不下,直接放入老年代,若老年代内有对象导致放不下新对象,则执行majorGC/old gc(老年代垃圾回收),若老年代本来就不够装,则报OOM
        若幸存者区放不下,也会直接进入老年代
        调优就是尽量减少GC,因为GC过程中,用户线程就会阻塞
full GC:  收集整个java堆和方法区的垃圾回收
jdk7之前方法区被称为永久代,jdk8开始元空间取代永久代
======================================================================
方法区:存储类型信息(包含域信息,方法信息,这些信息中都用的是运行时常量池中的索引),运行时常量池(常量池中的符号引用转化为真实地址),字符串常量池,静态变量等(jdk7 以后,字符串常量池和静态变量放入堆中)
类型信息:对每个加载的类型(类class、接口interface、枚举enum、注解annotation),jvm须在方法区中存储以下类型信息:
    1.这个类型的完整有效名称(全名=包名.类名)
    2.这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
    3.这个类型的修饰符(public, abstract, final的某个子集)
    4.这个类型直接接口的一个有序列表
    域(成员变量)信息:所有域的相关信息以及声明顺序,包括:域名称,类型,修饰符(public,static等)
    方法信息:名称,返回类型,参数数量顺序,修饰符,方法的字节码,异常表
常量池:字面量(数值,字符串值),类引用,域引用,方法的引用;每个类都有对应的常量池


方法区GC:常量池中废弃变量和不再使用的类型:
    常量回收:只要常量池中常量没有被任何地方引用,就可以被回收
    类回收:1.该类的实例已经被回收,堆中不存在该类和任何派生子类的实例;2.加载该类的类加载器被回收;3.该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法。

垃圾:运行程序中没有任何指针指向的对象

垃圾回收:垃圾标记;垃圾清除
    垃圾标记:对象不再被活对象引用时称为垃圾;判断对象存活的两种方式:引用计数法和可达性分析
        引用计数法:对堆中每个对象保存一个整形的引用计数器属性,记录被栈中引用情况;引用了就加1,失效了就减1,若计数器值为0,便可回收;优点简单,判断效率高;缺点增加开销(时间空间),无法处理循环引用,循环引用时一直计数器都为1无法回收,故GC未采用该算法
        可达性分析算法(根搜索算法):以根对象集合(GC Roots)为起始点,按照从上至下搜索被根对象所链接的目标对象是否可达。GC Root包括:虚拟机栈中引用的对象;本地方法栈内引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;同步锁所持有的对象。(即若一个指针保存了堆中的对象,但自己又不在堆内存里,那他就是一个root)
        
    finalization机制:使用finalization机制允许对象销毁前自定义处理逻辑(用于资源释放,关闭套接字数据库连接等)
    垃圾清除:标记-清除算法(mark-sweep);复制算法;标记-压缩算法
        标记-清除算法(mark-sweep):从根节点开始遍历,标记所有被引用的对象,从堆内存从头到尾进行线性遍历,若发现未被标记则将其回收(其实是留在原地,地址写入空闲列表,新数据来时可直接覆盖)。缺点:清理后内存不连续,内存有碎片;gc时需要停止用户线程
        复制算法(copying):直接把内存分为两份A区B区,A用来存储对象,另一部分B空闲,当垃圾回收开始从根节点遍历时,发现被引用的对象直接按序放入B区,遍历完后清除A区,以次交换。缺点:移动对象需要调整引用的地址,内存和时间开销大,需要两倍的内存空间,但是不会出现碎片问题。适用于垃圾多,存活对象少,垃圾多的情况(伊甸园)
        标记-压缩算法(mark-compact):从根节点开始遍历,标记所有被引用的对象,第二次将所有存活对象压缩到内存的一端,再清理边界外的区域。缺点:移动对象需要调整引用的地址,需要STW
    一般复制算法用于年轻代,标记算法用于老年代
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值