JVM
什么是寄存器看不懂,再说吧
《深入理解JVM虚拟机》作者:周志明
jvm
类加载过程
双亲委派
垃圾回收
垃圾回收算法
JVM主要有三个组件
ClassLoader Subsystem: 用来读取、加载class文件*,并把字节码存在jvm方法区 [ *class文件:一个二进制流文件(8位字节的)包含了常量池、文件版本号、访问标志、类索引、接口索引、字段、方法属性表]
Runtime Data Area运行时内存区域
Execution Engine执行引擎
类加载过程
加载、验证、准备、解析、初始化如下图
类加载步骤
Loading:加载
1.通过一个类全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
这一步在jvm外部实现,一般系统提供三种类加载器
1.启动类加载器(Bootstrap Classloader) C++语言实现,是虚拟机的一部分,加载<JAVA_HOME>/lib路径下被虚拟机可识别的库,他无法通过class.getClassLoader()方法获取。
2.扩展类加载器(Extension classloader)加载<JAVA_HOME>/lib/ext路径下的类库,Java程序可以直接使用。
3.应用程序类加载器(Application classloader)又叫做系统类加载器,加载应用程序classpath下的jar,Java程序可以直接使用,程序中的默认类加载器。
Verification: 验证
确保class文件的字节流包含的信息符合当前虚拟机的要求。
1.变量在使用前要初始化
2.方法调用和对象引用类型之间要匹配
3.没有违反访问私有变量和方法的规则
4.对本地变量的访问都在运行时堆栈内
5.运行时堆栈没有溢出
Preparation: 准备
在方法区中为类变量(被static修饰的变量[域field])分配内存并设置初始值
Resultion: 解析
虚拟机将常量池的符号应用替换为直接引用
Initialization:初始化
执行类加载器的最后阶段,为所有静态变量都分配了原始值,静态块从父类执行到子类
类加载器的双亲委派模型
双亲委派模型是类加载器的一个层次关系,要求除了顶层的启动类加载器外,其他的都要有自己的父亲加载器。
过程 一个类加载器收到类的加载请求,首先会把和请求委派给父类加载器去完成,每一层都是如此,所以所有加载请求都会先传到启动类加载器,只有弗雷无法完成加载(在可加载的路径中没有找到所需的类)子加载器才会加载。
好处因为是层级关系,加载基础类比如java.lang.*中的类,会被最高级启动类加载器加载,这样可以避免类的重复加载,也避免了核心api被篡改,使基础类得到统一。
自定义类加载器 继承Classloader的子类重写findclass方法
作用:可以自己加载制定位置的class文件,这个class文件可以动态获取(网络下载、文件读取等)并且可以对自己吗进行自定义加密、修改,然后再自定义类加载器中处理成正常的class文件,然后使用。
java内存区域
JMM(java memory model)java内存模型
Java内存模型其实是一个规范,并不是真的实体内存分区,并没有一个真的jmm分区。主要就是说明每个线程都有一个自己的工作内存,线程的通信和数据同步(这里还要再看看) 都是先通过jmm,将数据同步到主内存,然后再和其他线程通信。所以jmm规定了如何同步数据,什么时候同步数据
java内存模型图JMM
- 线程都有自己的一个工作内存
- 有一个主内存空间
- 线程不直接操作主内存
- 线程的功能做内存保存了主内存中需要的变量拷贝
- JMM是线程工作内存和主内存的中介,用阿里规定如何数据同步及何时同步
- 线程A和线程B要数据同步/数据传递,首先要和主内存同步。
java内存区域
分为两大模块线程数据共享区域(方法区、堆)和线程私有数据区域(虚拟机栈、本地方法栈、程序计数器)
- 方法区(Method Area)
类信息、常量、静态变量、即时编译器编译后的代码还包含一个运行时常量池用来存放编译器生成的各种字面量和符号引用[a = 1,a ="String",1和String是字面量 ]。超出内存区域会OOM - 堆(java Heap):
是jvm管理的最大一块内存,存放实例对象,Java垃圾回收管理的主要区域,超出会OOM异常。 - 程序计数器(Program Counter Register):
线程私有数据区域,运行最快的内存区域
是程序控制流的指示器,程序分支,循环,跳转,异常处理,程序恢复都依赖计数器,它代表当前程序正在执行的字节码行号指示器。字节码解释器需要通过计数器的值来选取下一条要执行的字节码指令。 - 虚拟机栈(jvm Stack)
线程私有数据区域,用来描述Java方法执行的内存模型。
这个栈用来管理栈帧的入栈和出栈(方法调用和执行的数据结构),执行一个方法A时,A对应的栈帧入栈,执行完成后A方法对应的栈帧出栈。
4.1 栈帧(stack frame)用来存储局部变量表、操作数栈、动态链接、方法出口等信息。
4.2 局部变量表(local variables table)一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
4.3 操作数栈(operand stack)执行过程中,根据程序,会入栈和出栈设计到比如算术运算或调用方法是通过栈来进行方法参数的传递。
4.4 动态链接(dynamic linking)编译后,类的方法、字段都以符号的形式存在class文件的运行时常量池中,当运行时先从常量池获取符号,再转化为直接引用。
4.5 方法的返回地址()方法结束后,要返回到调用方法的地方继续执行,方法正常调用完成时,一般程序计数器的值可以当做返回地址(帧中保存的有该值)异常调用完成(抛异常了)返回地址有异常处理器来确定(帧中没有保存)。
4.6 本地方法栈(native method stack)线程私有数据区域,与虚拟机用到的native方法相关。
java垃圾回收
- 根据jvm内存模型,主要针对两个地方,方法区和堆区[共享区]进行回收。虚拟机栈、本地方法栈、程序计数器[线程私有,随线程生成消亡]
- 两大类回收算法:1.引用计数:对象引用次数计数器,为零说明没有引用指向,该对象回收。2.[主流]引用到达:有个GCRoot对象,到达不了的对象就要回收。
可以做GCRoot对象的类型
1.在虚拟机栈的战阵的本地变量表里引用的对象:比如线程中方法栈中使用的参数、局部变量、临时变量等。
2.方法去中类的静态属性引用的对象:比如Java类中的引用类型静态变量。
3.方法去中常量引用的对象:比如String字符串(常量池中的字符串是符号,只有第一次用到才变成对象)
4.在本地方法栈中native方法引用的对象
5.JVM内部的引用,比如①基本数据烈性对应的class对象,②常驻的异常对象NullPointerException OOM等,③系统类加载器
6.被所有synchroized关键字持有的对象
7.其他
方法区回收
- 废弃的常量
- 不再使用的类型
2.1 该类所有实例都被回收(堆中没有这个类的实例)
2.2 该类所有类加载器都已经被回收
2.3 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射方为该类的方法
堆的回收
1.分类
Partial GC 指目标不是完整收集整个Java堆的垃圾
- 新生代收集(Young GC/Minor GC) 只对新生代
- 老年代收集(Old GC/Major GC) 只对老年代,只有CMS收集器有
- 混合收集(Mixed GC) 整个新生代、部分老年代,只有GI收集器有
Full GC 收集整个堆和方法区的垃圾
- 算法
标记-清除算法(Mark-sweep) 先标记出要回收的对象,然后统一回收
缺点:①执行效率不稳定(会停止程序进行递归与全堆对象遍历),有大量对象时,标记和清除两项个你工作量大,效率低。②空间碎片化,清除后有大量不连续内存碎片,如果有大对象,但无大的整体空间存放,那么就会提前触发GC
标记-复制算法(Mark-copy)[用在新生代]是为了解决标记清除算法的效率低问题,把内存一分为二,每次用其中之一,内存用完了,就把存活的复制到另一边,然后将这边清除
缺点:可用空间缩小一半,如果对象过多复制会产生大量内存开销
标记-整理算法(Mark-Compact)[针对老年代]为了解决标记-复制在有很多对象存活的时候需要大量复制,并且有一半的空间浪费问题。把存活对象都向内存空间一端移动,然后清理掉边界以外的内存