JVM初探
jvm体系结构
-
方法区:存储 static,final,Class,常量池。
-
本地方法栈 :用来登记native 方法,最终的执行时,通过JNI加载本地方法库的方法(java本地接口)
-
java虚拟机栈 : 8大基本类型+对象引用+实例方法
- 栈内存:主管程序的运行,声明周期和线程同步,线程结束栈内存释放,不存在垃圾回收问题
双亲委派机制
类加载器:
BootstrapClassLoader
(启动类加载器):负责加载jre/lib/rt.jar(c++编写,开发者无法直接获取启动类加载器的引用)ExtClassLoader
(扩展类加载器) : 负责加载jre/lib/ext/*.jar(java编写)AppClassLoader
(应用程序类加载器): 负责加载用户类路径(classpath)上指定的类库。(java编写)CustomClassLoader
(用户自定义类加载器) : 加载指定路径的class文件(java编写)
工作流程:
- 当类加载器收到类加载的请求时,不断委派父加载器,直到启动类加载器
- 若启动类加载器能加载,则加载,不能的话,就抛出异常并通知子加载器加载
- 重复步骤二;
native
在95年创造java的时候,c和c++已经很流行了,java要占据一地,使Java也可以调用c,c++的程序,这就是native
native
标记的 代表是非java编写的,会回去调用底层的c语言库,进入本地方法栈。
jvm
在内存管理中开辟了一块标记区域:本地方法栈 用来登记native 方法,最终的执行时,通过JNI加载本地方法库的方法
new一个对象的过程
new一个对象分为,加载并初始化和创建一个对象两个步骤,如果第一个步骤已经走过了,那么就会直接执行第二步;
如果没有则会进行类的加载和初始化:
-
类的加载: 分为加载和连接两部分其中连接包括(验证,准备,解析)
- 加载: jvm找到对应的class文件,以二进制字节流的形式读取到jvm方法区中。
- 验证:总的来说就是检查是否符合java规范
- 是否符合Class文件规范
- 是否符合java语法(final标记的类是否有子类,final方法是否被重写等)
- 准备:为其静态变量分配空间并设置一个初始值,如果是静态常量则直接被赋值
- 解析:将符号引用转换为直接引用,和静态绑定(private,final,static 的方法与类和对象关联起来)
- 符号引用 : java类并不知道所引用类的实际地址用符号引用代替
- 直接引用 : 直接指向目标的指针
- 静态绑定: 所有不会被重写的方法和域都会被静态绑定
-
初始化的过程(先父后子)
- 静态变量赋值 和 执行静态代码块
-
创建对象
- 在堆区为其分配空间(分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量)
-
对所有实例变量赋默认值 ( 将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值)
-
执行实例初始化代码
- 初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法
-
-
在栈区定义引用变量,然后将堆区对象的地址赋值给它
堆
jdk1.8后,常量池,被分离出来,和元空间一起,逻辑上属于堆,物理上却不属于
VM调优参数 :-Xmx2048m -Xms1024m -XX:+PrintGCDetails(压制警告)
:-Xmx8m -Xms4m -Xlog:gc*(代替上面的gc参数)
-Xmx表示运行时最大可占用的内存(默认1/4)
-Xms程序启动时占用的内存(默认1/64)
默认经历15次GC没有被回收,进入养老区。
GC
GC主要是对堆和方法区进行的,程序计数器,虚拟机栈和本地方法栈都是线程私有的,随线程消失而消失不需要回收。
由上图知:堆中分为新生区和老年区
新生区: 伊甸园 :幸存区(to) : 幸存区(from) = 8 : 1: 1
- 伊甸园 : 绝大部分刚创建的对象就存储在伊甸园区
- 幸存一区 : 在伊甸园空间执行第一次GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)。每一次gc后存活的对象就会被移到同一个幸存区(to)
- 幸存二区 : 当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者(from)空间。然后会清空已经饱和的哪个幸存者空间。 清空的区就变成from区,from就变成了to区;
- 在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代
垃圾回收算法
-
标记清除法
- 标记阶段 : 程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
- 清除阶段 : 对对象进行回收并取消标志位,并判断与前一个空闲分块是否连续,连续则合并。 (回收就是,将次分块连接到空闲链表中)
- 分配 : 搜索空闲链表,找到大于或等于新对象size的块。
不足:
- 标记和清除过程效率都不高;
- 会产生大量不连续的内存碎片,导致无法给大对象分配内存。
-
标记整理法
- 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点 : 避免产生内存碎片
不足 : 需要大量移动对象,效率低
-
复制法
- 将内存分为两块,每次使用其中的一半,使用后若该对象存活则复制到另一半空间中,并释放刚刚使用的空间中的对象。
不足: 内存只有一半了。
-
分代收集
- 新生代使用:复制算法
- 老年代使用:标记 - 清除 或者 标记 - 整理 算法
JMM(java内存模型)
什么是Volatile
Volatile是Java虚拟机 提供的 轻量级同步机制,是关键字。
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
JMM: Java内存模型,不存在,概念,约定;
关于JMM的一些同步约定
- 线程解锁前,必须把共享变量立刻写回。
- 线程加锁前,必须读取主存中最新值到工作内存。
- 加锁解锁是同一把锁。
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
-
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
-
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
-
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
-
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
-
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
-
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
-
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存