注该项目源于记录前辈的讲解
目录
类加载机制
1、类加裁过程
类.class字节码文件,运行靠的是JVM,JVM(虚拟机)加载得过程叫作类加载过程
一个Java文件从编码完成到最终执行,一般主要包括两个过程:编译和运行,是把我们写好的Java文件,通过java的命令编译成字节码文件,也就是我们常说的.Class文件,运行则是把编译声好的.class文件交给Java虚拟机(JVM)执行。JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象(反射对象)的过程。 举个通俗点的例子来说,JVM在执行某段代码时,遇到了classA,然而此时内存中并没有classA的相关信息,于是JVM就会到相应的class文件中去寻找classA的类信息,并加载进内存中,这就是我们所说的类加载过程。由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。
反射
用反射,反射的效率并不高,但是好多的地方都会用到
用反射动态获取类的情况 反射类:.class 反射获得.class文件,进入到内存就是一个对象 类似于我们需要动态的设置一个类,通过反射将类的属性加载到内存当中,可以理解反射就是一面镜子
类是对象的模板,对象是类的序列化的对象
类加载
卸载:1、被垃圾回收机制回收(gc)
2、运行结束,自己回收
初始化:实例化对象
验证:看是否符合规范
3.2加载的过程
3.2.1、加载
简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。这里的字节码文件的来源,一般包括从本地路径下编译生成的.class文件(class_path:存放字节码的路径),从jar包中的.class文件,从远程网络以及动态代理实时编译。
3.2.2、在加载阶段,虚拟机需要完成以下三件事情?
1、通过一个类的全限定名(包名.类名)来获取其定义的二进制字节流。 2、将这个字节流所代表的静态存储结构转化为方法区的运行时的数据结构。 3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。
3.3验证阶段
3.3.1验证合法性
验证是连接阶段的第一步, 这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作。 1.文件格式验证:验证字节流是否符合Class文件格式的规范; 例如一是否以QxCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 2.元数据验证:对字节码描述的信息进行语义分析(注意:对比java编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。 3.字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如循环、分支等 4.符号引用验证:确保解析动作能正确执行,比如不能访问引用类的私有方法、全限定名称是否能找到相关的类。
3.3.2准备阶段
在准备阶段为静态变量赋予初始值,这里的类变量初始值通常是指数据类型的零值。而不是我们在程序中设定的初值。 1.基本类型(int. long. short. char. byte、 boolean、 float. double) 的默认值为0或0.0 2.引用类型的默认值为null 3.被final和static共同修改的变量,我们通常称为常量,然后常量的默认值为我们程序中设定的值。比如: public static final int value = 123,那么初始值就是123。
3.3.3解析阶段
这一阶段的任务就是把的符号引用转换为直接引用。 1、符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一 个方法,一个变量,一个类的相关信息。 2、直接引用:可以理解为一个内存地址,或者-个偏移量。比如类方法,类变量的直接引用是指向方法区的指针。 在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。 举个例子来说,现在调用方法hello),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
3.4初始化阶段
在这里调用构造器 JVM负责主要对类变量(类变量就是static修饰的变量)进行初始化,也正是这个阶段,JVM才真正开始执行类中定义的Java程序代码。 ●new-个对象、读取一个类静态字段、调用一个类的静态方法的时候 ●对类进行反射调用的时候 ●初始化一个类,发现父类还没有初始化,则先初始化父类 main方法开始执行时所在的类
3.5类加载初始化的时间
只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种: -创建类的实例,也就是new的方式 -访问某个类或接口的静态变量,或者对该静态变量赋值 -调用类的静态方法 -反射(如Class.forName("com.shengsiyuanTest") -初始化某个类的子类,则其父类也会被初始化 - Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主 类
JVM内存机制
字符串常量放在堆
新生代:刚刚new出来
老年代:存放时间久
JVM栈:每当启动一个新线程的时候,java虚拟机都会为它分配一个iava栈。 java以栈帧为单位保存线程的运行状态。虚拟机只会对java栈执行两种操作:以栈帧为单位的压栈或者出栈 堆内存:存储的全部是对象信息。(自己new的对象) 方法区:在方法区中存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。(存放内部结构:属性等) 本地方法栈:和iava栈的作用差不多,只不过是为JVM使用到的native方法服务的(线程) 线程级别都是操作系统级别的,java是启动不了线程的,是java让操作系统启动的 程序计数器:用于保存某个线程的字节码执行位置。
5.内存泄露
5.1什么是内存泄露
内存泄露也称作"存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收即所谓内存泄漏)。
5.2为什么会导致内存溢出
编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理(会用到gc机制),当iava对象不再被引用时,等到堆内存不够用时,jvm会进行垃圾回收,清除这些对象占用的堆内存空间,如果对象一直被引用,jvm无法对其进行回收,创建新的对象时,无法从Heap中获取足够的内存分配给对象,这时候就会导致内存溢出。而出现内存泄露的地方,一般是不断的往容器中存放对象,而容器没有相应的大小限制或清除机制。容易导致内存溢出。
5.3常见的内存泄露
1、内存分配未成功,却使用了它.(多线程出现,正在分配就使用 了内存空间) 2、内存分配成功,但尚未初始化就引用它。 3、内存分配成功且初始化,但操作越过了内存的边界。(error错误:outofmarry)(递归溢出) 4、忘记释放内存,造成内存泄漏 5、释放了内存却继续使用它 6、该对象已经没有使用了,但是没有被gc回收
6.垃圾回收机制-GC
Java语言中一个显著的特点就是引入了垃圾回收机制,使c+ +程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回 收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。
6.1回收机制
6.1.1引用计数法
这种算法的大概思路如下:当对象被创建时,GC程序会存储有多少个引用指向该对象,当没有引用指向该对象时,表示该对象已经不可用,则可以对其进行回收。这种算法相对来说简单粗暴,但是它不能解决对象之间引用循环指向的问题,如下图 隐患:可能互相引用的两个都是垃圾
6.1.2可达性分析法
通过对象的引用链(两对象间想湖引用)来判断该对象是否需要被回收,通过一系列的GC Roots的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,就需要回收此对象。
什么是GC Roots呢?
GC Roots是一些由堆外指向堆内的引用 -般而言,GC Roots包括(但不限于)如下几种: 1.虚拟机栈(栈帧中的本地变量表)中引用的对象。(可以理解为:引用栈帧中的本地变量表的所有对象) 2.方法区中静态属性引|用的对象(可以理解为:引用方法区该静态属性的所有对象) 3.方法区中常量弓|用的对象(可以理解为:引用方法区中常量的所有对象) 4.本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)
6.3回收方法
6.3.1标记-清除算法
首先标记需要回收的对象,然后进行回收;优点:寻找速度快;缺点:回收速度慢,回收之后产后生大量不连续的内存碎片,后期运行过程中需要分配较大对象时无法找到足够的连续内存而造成内存空间浪费。
6.3.2复制算法
将内存空间等分为两份,每次只使用其中一份,当满了之后将还有效的对象复制到另一份内存中,然后把原来的空间进行清除,不会产生内存碎片,但是可用内存空间减半
6.3.3标记-整理算法
不仅对需要回收的对象进行清理,还对有效对象进行整理,不会产生内存碎片。
6.3.4分代收集算法
是一种比较智能的算法,也是现在JVM使用最多的-种算法,其实不是一个新的算法,而是在具体的场景自动选择以上三种算法进行垃圾对象回收
新生代:目的是回收那些生命周期短(new出来用了立马就销毁的对象)的对象,主要存放新产生的对象。 老年代:存放对象生命周期较长,且内存大概是新生代的两倍,老年代存活对象生命周期长。 永久代:指内存的永久保存区域,主要存放静态文件,如Java类,方法等。永久带对垃圾回收基本没有影响。
总结: 新生代中:每次垃圾收集时都有大批对象死去,只有少量存活,那就选用复制算法。只需要付出少量存活对象的复制成本就可以完成收集。 老年代中:因为对象存活率高、没有额外空间对他进行分配担保,就必须用标记清除或者标记-整理。 永久代中:经常会内存不够用或者发生内存泄露,JDK1.8开始废弃了永久代,取而代之的是元空间(直接存在内存中可自定义大小),主要存放类的元数据。