总结:java运行环境运行起来之后,jvm也被系统创建,main线程会根据jvm运行数据区,创建线程私有的虚拟机栈,PC本地方法栈,然后jvm会将main方法所在的类所产生的字节码,使用类加载器加载到内存当中来并且将类的信息,静态变量,常量编辑后的代码放入方法区,并在堆中产生了一个Class对象来映射该类的内存结构。随后main方法进入虚拟机栈,PC计数器开始运行,遇见类先尝试去父加载器中查询是否被加载,否则的话自己去加载(加载字节码相当于读写io比较慢,有缓存,不需要一直读写io),然后产生的对象会被放入的堆的新生代区域,随着代码的执行,这些新创建的对象会根据GC的垃圾回收器的回收算法进行回收,或者放入老年代,保证内存不oom。
1.jvm运行数据区
jvm运行数据区将内存分为五个部分
1.堆,java保存对象和数组的地方
A.虚拟机启动的时候,他就被创建了,是jvm管理的最大一块内存区域
B.jvm创建的对象大都保存在这个地方。
C.垃圾收集器管理的内存区域,好多时候成为GC堆
2.PC程序计数器,指向当前执行字节码的指令。不会oom,并且线程私有
3.jvm虚拟机栈,是jvm执行方法的内存模型。每一个线程都会有一个虚拟机栈和PC,栈中可以存放栈帧,每一个将要运行的方法都是一个栈帧,方法运行,栈帧入栈,方法返回或抛出异常,栈帧出栈。栈帧中有局部变量表(方法执行过程中的所有局部变量),操作数栈(操作数的内存模型,),返回值,动态链接。线程私有
4本地方法栈,用来调用native方法的一个栈,线程私有
5.方法区,用来存储被jvm加载到内存当中类的信息,常量静态变量,及时编译之后的等数据。
A.线程共享
B.运行时常量池
1.是方法区的一部分
2.存放编译时期的符号引用和各种字面量
2.类的加载->对象的产生->对象的访问->对象的回收->类的释放
类加载机制
一.概念:类的加载(将类的字节码加载到内存,并且将类的信息,静态变量,常量,和编译之后的代码等信息放入到方法区,同时在堆中产生一个Class对象来映射方法区中的数据结构)
A.加载。查找并加载类的.class文件
B.连接
1.验证 确保被加载类的正确性
2.准备 为类的静态变量(static修饰)分配内存,并给与初始值
A。static分配空间和赋值是2个步骤分配空间在准备阶段,赋值在初始化阶段
B。static finnal 基本类型 分配空间和赋值均在准备阶段
C。static finnal 引用类型 赋值在初始化阶段
3.解析 将类中的符号引用变成直接引用(比如变量符号或者方法符号直接对应到内存区域的地址)
C.初始化 对类的静态变量赋值
二。双亲委派模型
1.它是什么
当类加载器收到加载类的请求之后,首先当前类加载器并不会直接去加载,而是会向其父加载器发出请求,并依次向上,所以所有的类加载请求都会首先转发给父加载器,只有当父类加载器无法加载到当前类时,子类才会去尝试加载当前类,如果都无法加载这个类时候,则会抛出ClassNotfindException
2.意义 提高系统的安全性,自定义的加载器类不能顶替本该有系统加载器类才能加载的类
三.特点
A.全盘负责 当加载一个类时,他依赖着其他类,这时其他类也会被加载进来
B.缓存机制 所有的Class对象都会被缓存,当需要一个Class时,类加载器会首先从Class对象中寻找,如果没有找到才会找到字节码加载到内存当中,并产生一个Class对象
四。类加载器种类
1.jvm自带的类加载器(3种)
A根加载器BootstrapClassLoader,加载虚拟机的核心库
B.扩展类加载器ExtensionClassLoader指定目录的类库,父加载器是BootstrapClassLoader
C.应用加载器ApplicationClassLoader 环境变量或者classpath中加载类,父加载器是B
他们都是ClassLoader的子类
2.用户自定义的类加载器(父加载器是ApplicationClassLoader)
A。使用方法:重写findClass方法,从特定位置找到字节码文件,得到字节码数组,然后使用defineClass方法将字节数组转化为对应的Class对象(不重写loadClass方法不会走双亲委派机制)
B。意义:指定加载路径,加密
五,java 对类的初始化有几种方式
java必须在每个类的首次主动使用的时候,才会初始化这个类
1.主动使用(6种)
A.创建类的实例
B.读写某个类或者接口的静态变量
C。调用类的静态方法静态变量
D。通过反射获取类
E。初始化一个类的子类
F。jvm启动的时候,被标记启动类的类(main方法所在类)
2.被动使用,除了上述6中均为被动,都不会导致类的初始化(包括自定义classLoader去loaderclass,创建类数组)
类加载机制与接口
1.当jvm初始化一个类的时候,不会初始化他实现的接口
2.初始化一个接口时,不会初始化该接口的父接口
3.只有当程序使用到该接口的静态变量时候,才会初始化接口
类的卸载
1.jvm系统自带的三种类加载器的类不会被卸载,因为jvm始终要引用这些类加载器,
2.用户自定义的类加载器的类是可以被卸载的
3.HotSpot虚拟机详解
一。对象的创建过程
1.虚拟机接收到new的指令的时候,会首先去常量池当中寻找一个类的符号引用,并检查这个符号引用是否被加载链接初始化,如果没有就进行类的加载过程
2.为该对象分配内存(绝大多数都在堆上,当方法中的对象不满足逃逸分析时会将对象分配到栈上,分配到栈上的对象的生命周期跟随线程不需要垃圾回收了)
A。jvm堆是规整的话,用过的分为一块,空闲的分为另一块,中间的界限用指针来表示,分配内存的时候,指针会向后移动对应的对象的空间大小,这叫做指针碰撞
B。jvm不规整的话,用过的空间跟空闲空间交错存在,那就没办法指针碰撞。jvm维护了一张列表,记录了那些内存块是空闲的,然后jvm会找到能够放入对象的一块空间给对象,这种分配空间的方式叫做空闲列表
C。哪种分配方式由jvm的堆是否规整决定,jvm的堆是否规整由使用的垃圾回收器是否带有整理功能决定的
D。分配对象保证线程安全的做法,JVM采用CAS不断重试的方法保证更新操作的原子性
3.虚拟机为分配的空间初始化初始值
4.虚拟机对对象头进行初始化设置(比如classpointer,分代年龄,hashcode)
5.执行init方法,按照程序猿的方式进行初始化
对象的内存布局
如果对象是一个java数组,那么在对象头中还有一块用于记录数组长度的数据
二。对象的定位访问方式
1.句柄 在堆中画出一块空间存放句柄池,引用中存放的是对象的句柄地址。句柄包含了对象实例数据和对象类型数据的地址
2,直接引用
1的好处是堆中对象地址发生改变时候,只需要更改句柄内容
2的好处是节省了时间的开销
三,判断对象的存活(jvm回收垃圾的标记)
1.引用计数法。被其他对象引用一次就+1,失去一次引用就-1直到为0时(对于循环引用的对象来说无可适从 )
2.可达性分析
通过一系列被称为GCRoot的对象为起点,从这些节点向下搜索,搜索的路线叫做引用链,当一个对象到GCRoot没有引用链时候,证明是不可用的
GCRoot对象包含几种:
1.虚拟机栈中(局部变量表中引用的对象)
2.方法区中类静态属性和常量引用的对象
3.本地方法栈中引用的对象
4.JVM的内部引用(Class对象,异常对象,系统类加载器)
5.被同步锁Sync锁住的对象等等
类回收的条件:比较苛刻
1.所有的对象实例被回收
2.Class对象已经被回收
3.ClassLoader被回收
Finalize方法:
即使对象已经不可达,未必会被回收,他会被2次标记,第一次没有找到GCRoot的引用链时候会被标记,随后进行依次筛选(重写了finalize方法),然后GC会在下一次垃圾回收的时候将对象回收,Gc的线程优先级比较低
Java四大引用类型
强引用 无论如何不会被回收
软 内存不足时会被回收,充足时即使主动gc也不会回收 (图片加载)
弱 只要主动gc就会被回收(ThreadLocal)
虚引用 随时都会被回收需要使用引用队列来释放内存
四。垃圾回收算法
1.标记清除法(不可达的对象会被jvm标记,并将对象地址记录到列表,下次再分配对象时,直接覆盖数据)缺点碎片化严重
2.标记整理(不可达对象会被标记,然后将不连续的内存空间重新整理成连续的内存空间)
缺点:花费时间整理空间,优点:没有碎片
3.复制算法(开辟一个同样内存大小的空间,不可达的对象被标记之后,将没被标记有用的对象复制到新区域,然后旧内存跟新内存交换区域)
4.分代垃圾回收(目前大部分jvm采取的算法)
上面三种算法各有优劣,不可能单独使用某一个,交叉使用才能发挥出它的优势:
java堆内存结构分为新生代,老年代,永生代1.8之后被移除在直接内存中元空间
1.新生代
A。使用的复制清除算法的原因是年轻代每次GC回收大量对象。里面分为eden区和from区to区,每次只是用eden区和一块s区,然后垃圾回收之后将数据放入到未使用另外一个区,清空刚才标记的区
B。区域比例 eden:from:to 8:1:1
C.内存不足时会触发mintor GC
2.老年代 采用标记整理的算法,原因是老年代每次GC只会回收少量的对象 eden区满的时候触发majorGC
3.元空间 用来存储类的元数据,也就是方法区(原生代使用的是堆,1.8之后元空间使用的是直接内存)
FullGc 老年代内存不足时,清理整个堆空间包含新生代,老年代
5.常用的垃圾回收器
1.CMS
(1)一种以获取最短停顿时间为目标的收集器
(2)基于标记清除算法对老年代清理的,有以下4个步骤
A。初始标记,用于标记GCRoot直接引用的对象
B。并发标记,利用多线程按照引用链向下寻找关联对象
C。重新标记,为了修正在并发标记过程中,用户线程同时执行时所产生的标记发生改动的那些对象的记录
D。并发清理,多线程对标记对象进行清除
核心点在于费时的并发标记跟并发清理采用多线程跟用户线程同时进行的,
缺点:
A对cpu资源利用有点多
B。CMS无法处理浮动垃圾。在并发清理垃圾的同时,用户线程同时进行,这个时间段产生的垃圾叫做浮动垃圾CMS不能直接清理,只能等待下一个轮回的标记
C。需要预留一部分内存,在垃圾回收时,供用户线程使用
D。基于标记清除算法,产生大量碎片,容易引起FullGc
虚拟机会按从上到下的顺序,收集所有的static静态代码块和静态成员赋值的代码,合并为一个特殊的方法<cinit>V)(方法会在类加载的初始化阶段被调用)
编译器会从上到下的顺序收集所有的{}代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后
异常捕获 在字节码层面上,finnally里面的代码会被复制到try和catch块里面,无论走try还是catch都会执行finnally代码,同时还会抛出Exception同级Throw或者父类的错误
在finnally中使用return 会吞掉异常asthrow指令不要在finnally中使用return
Synchronized 块,就算报错了也会解锁,并且抛出异常
泛型参数只用于编译阶段的类型检查,不影响运行阶段,都会给它擦除成Object
擦除的仅仅是类中的泛型符号,真正的类型信息会存放在字节码的LocalVariableTypeTable中,LocalVariableTypeTable中包含了方法的参数的泛型信息