JVM
Java Virtual Machine Java 虚拟 计算机
JVM( Java Virtual Machine)是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的.
JVM 是 Java Virtual Machine 的缩写,它是一个虚构出来的计算机,一种规范。通过在实际的计算机上仿真模拟各类计算机功能实现
1:Java怎么实现跨平台
java虚拟机是一个可执行.class文件的虚拟进程
Java将文件用过javac转化为.class文件
Jvm执行这个.class文件
2:Java 内存结构
JVM 类加载器 内存区域 执行引擎 本地库接口
内存区域(运行时数据区)
1.Stacks 栈区
2:Heap Memory 堆区
3:Method Area 方法区
4:Native Method Stacks 本地方法栈
5:程序计数器
创建对象---> 类加载器判断是否加载
已加载
未加载--->分配内存---> 设置对象头-->执行init
堆和方法区 线程共享
2.1 JVM Stacks ava虚拟机栈是线程私有的,他与线程的声明周期同步。
虚拟机栈描述的是java方法执行的内存模型,每个方法执行都会创建一个栈帧,栈帧包含局部变量表、操作数栈、动态连接、方法出口等
Stack Frame 栈帧 一个栈帧随着一个方法的调用而创建 这个方法调用完而结束.栈帧内 存放着方法中的局部变量
一个线程对应一个 JVM Stack。JVM Stack 中包含一组 Stack Frame。线程每调用一个方法就对应着 JVM Stack 中 Stack Frame 的入栈,方法执行完毕或者异常终止对应着出栈(销毁)。
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
栈是私有的每一个线程创建一个栈 其他栈不能访问
堆内存是线程共享的 JVM只有一个堆 堆内存满足不了 new 对象申请的空间 会抛出OutOfMemory
注意:
栈内存超过了设定值异常处理
栈大小固定的时候: StackOverFlowError 当程序执行需要的内存超过了JVM设置的固定栈内存时候
栈内存大小动态增长:当JVM尝试申请的内存超过了可用内存报这个
2.2 Heap Memory 堆区
堆区,用来存储引用数据类型的对象和数组.由多个线程共享 随JVM创建而创建.运行时动态分配内存
2.3 Method Area 方法区 :存储 静态 常量池.
方法区是线程贡献区域 存储了类信息(类信息,方法名字,字段信息) 静态变量和常量池
2.4 Native Method Stacks 非java语言实现的方法 的调用栈
2.5 Program Counter Register 程序计数器
用来记录当前正在执行的指令,线程私有 每个线程创建一个程序计数器(是唯一一个不会抛出OutOfMemory的区域)
堆区是所有内存共享的一块内存区域,虚拟机创建的时候,所有对象实例都在这里创建,该区域会经常发生垃圾回收
Heap 可以动态调整大小,当需要扩容或者缩容的时候必然进行一次GC
Java --> javac-->.class
.class文件 --->Jvm-->linux-->cpu/内存/cpu
cpu/内存/cpu -->linux--> Jvm --->.class
GC 垃圾回收,JVM对已经已经没有引用的对象进行回收,判断对象是否存活两种方法 引用计数和可达分析
引用计数:对象每增加一个引用 计数+1 释放一个引用-1(无法处理循环引用问题)
可达分析: 从GC Roots 开始向下搜索 搜索走的过程叫引用链 ,当GC Roots 没有任何引用链为不可用 为不可达对象 被回收
JVM
1:JVM 是什么 java virtual machine 是一种Java语言用于计算设备的规范。
Java虚拟机器(Java语言运行的虚拟机器) Java语言编译的程序,只需要生成JVM运行的字节码文件 就能运行到不同的平台上.
问题:Java跨平台原因
Java语言的跨平台是由不同平台上的JVM所绝对的.java在不同平台上创建不同的虚拟机 java生成的不同程序运行在虚拟机上就实现了跨平台.
Java 的跨平台就是 不同平台上的Jvm 支持Java跨平台.
2:Jvm 运行流程
类文件--->类加载器--->运行时内存
---> 方法区:类信息 常 量池 静态变量
---> 堆区对象实例 如果该区域内存不足也会抛出OutOfMemoryError异常
---> java栈区(虚拟机栈区):java 方法操作的数据模型 生命周期和线程相同
栈帧:方法调用和方法执行的数据结构
局部变量表,操作数栈 动态链接 方法出口 其他
如果请求的站深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
虚拟机栈在动态扩展时如果无法申请到足够的内存,就会抛出OutOfMemoryError异常。
流程: 每个线程创建一个栈 线程内执行一个方法创建一个栈帧
---> 本地方法栈:
与虚拟机栈类似,不过虚拟机栈是为虚拟机执行Java方法(字节码)服务,
而本地方法栈则是为虚拟机使用到的Native方法服务。
--->执行引擎
https://ask.qcloudimg.com/http-save/yehe-7544680/gp6ght82w4.png
xx.java ---> java编译器 --->xx.class文件 --->类加载器(加载class文件)
|
|
|
运行时数据区 |
|
|
线程共享 线程不同享 |
|
| | |
| | |
方法区 虚拟机栈区 本地方法栈 |
|
程序计数器 |
堆区 |
|
| | | |
| | | |
| | |
执行引擎 | | |
| | 本地接口 | 本地方法库 |
| | | |
| | |
即时编译器 垃圾回收器 |----------------------------------------------
|
加载.class文件
A: 类加载器
程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化
核心:类加载器负责将.class文件加载到内存中
类加载过程:
连接(Linking)
加载(loading)--> 验证(Verification)---> 准备(Preparation) ---> 解析(Resolution)---> 初始化(Initialization)
类的生命周期包括:加载、链接、初始化、使用和卸载,其中加载、链接、初始化,属于类加载的过程
1.1 加载(loading)
1.类加载器通过全限定名(包名 + 类名)获取此类的二进制字节流
2.将二进制字节流所代表的静态存储结构,转化为方法区运行时的数据结构
3.内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
在内存存储一个class对象(存储在方法区中)
总结:
类加载器加载阶段干了以下事儿
类转化为二进制数据加载到内存--->转化为方法区运行时的数据结构--->在内存中生成一个代表这个类的对象(方法区中存储)
1.2 连接(Linking)
是指将上面创建好的类,创建到虚拟机中使其能够运行
包含:验证(Verification),准备(Preparation),解析(Resolution)
1.2.1 验证(Verification)
确保class文件中的字节流包含的信息,符合当前虚拟机的要求,验证类的正确性
1.2.2 准备(Preparation)
为类中的静态字段分配内存,并设置默认的初始值,
比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了
1.2.3 解析(Resolution)
解析阶段的目的,是将常量池内的符号引用转换为直接引用的过程;
解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。
1.3: 初始化(Initialization)
初始化就是执行类的构造器方法init()的过程。
这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。
2: 类加载器分类
启动类/引导类:Bootstrap ClassLoader
扩展类加载器:Extension ClassLoader
应用程序类加载器:Application Classloader
第四个:自定义加载器
2.1 Bootstrap ClassLoader 启动类加载器
c/c++实现 嵌套在Jvm内部 java不能调用
用途:加载Java核心类库
重点:
它加载扩展类加载器和应用程序类加载器,并成为他们的父类加载器
2.2 Extension ClassLoader 扩展类加载器
java语言编写,由sun.misc.Launcher$ExtClassLoader实现,我们可以用Java程序操作这个加载器
派生继承自java.lang.ClassLoader,父类加载器为启动类加载器
从系统属性:java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。
2.3 Application Classloader 应用程序类加载器
java语言编写,由sun.misc.Launcher$AppClassLoader实现。
派生继承自java.lang.ClassLoader,父类加载器为启动类加载器
它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库
它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。
我们可以通过ClassLoader#getSystemClassLoader()获取并操作这个加载器
2.4 自定义加载器
一般情况下,以上3种加载器能满足我们日常的开发工作,不满足时,我们还可以自定义加载器
比如用网络加载Java类,为了保证传输中的安全性,采用了加密操作,那么以上3种加载器就无法加载这个类,这时候就需要自定义加载器
// 方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
// 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
// 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
// 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()
重点:类加载机制—双亲委派机制
jvm对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。
在加载类的时候,是采用的双亲委派机制,即把请求交给父类处理的一种任务委派模式。
双亲委派机制
自己有爹就交给爹,爹一看也有爹也交给了爹 爷能干就干了 爷干不了还给爹,爹能干爹干,爹干不了还给儿子儿子干
儿子干不了抛出异常: ClassNotFoundException
双亲委派机制目的:
为了防止类重复加载
关系图
B: Heap Memory(堆内存)
堆内存存储Java对象实例 如果该区域内存不足也会抛出OutOfMemoryError异常
当堆内存的对象不被引用的时候,满足一定条件就会被移除.
GC即垃圾回收,是指jvm用于释放那些不再使用的对象所占用的内存,系统自动调用 无需开发者调用
堆内存分区
新生代区(Young/New) 老年代区(Old) 永久代区(Perm)(元空间)
1.1 新生代区(Young/New)
是用来存放新生的对象(new 对象会存储到新生代)一般占据堆的 1/3 空间, 由于频繁的创建对象,所以新生代会频发的触发GC
新生代又分为 Eden 区(伊甸园区8/10),和SurvivorFrom (幸存0区1/10)SurvivorTo(幸存1区1/10)
Eden 区(伊甸园区8/10):Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代
SurvivorFrom (幸存0区):**上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
SurvivorTo(幸存1区):**保留了一次 MinorGC 过程中的幸存者
轻GC(普通GC)只针对于新生区,偶尔作用幸存区(在新生区满的情况下)
重GC(全局GC)全局释放内存
1.2 老年代区(Old)
主要存放应用程序中生命周期长的内存对象。不会频繁执行
创建对象和GC流程
1.绝大多数刚刚被创建的对象会存放在Eden区
2.当Eden区第一次满的时候,会触发MinorGC(轻GC)。首先将Eden区的垃圾对象回收清除,并将存活的对象复制到S0,此时S1是空的。
3.下一次Eden区满时,再执行一次垃圾回收,此次会将Eden和S0区中所有垃圾对象清除,并将存活对象复制到S1,此时S0变为空。
4.如此反复在S0和S1之间切换几次(默认15次)之后,还存活的对象将他们转移到老年代中。
5.当老年代满了时会触发FullGC(全GC)
1.3 GC算法
- 引用计数法 给每个对象设置一个计数器,根据对象使用次数决定回收(对象0 就回收)
- 复制算法(新生代) 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中,同时回收未使用的对象
- 标记清除(老年代) 标记清除的算法最简单,主要是标记出来需要回收的对象,然后然后把这些对象在内存的信息清除,会产生大量内存碎片。
- 标记压缩(老年代) 个算法是在标记-清除的算法之上进行剪切操作,将存活对象压缩在一起,减少内存碎片。由于压缩空间需要一定的时间,会影响垃圾收集的时间。
JVM-GC
GC:Garbage Collection 垃圾收集
GC:内存回收机制
ˈɡɑːrbɪdʒ/
GC : Garbage Collection 内存回收内存收集
1:GC是什么
Garbage Collection 内存收集 内存回收
GC 是JVM提供的一种内存回收机制,当创建的内存无用的时候就会对内存进行回收释放.
GC对程序员不可见JVM自动调用 不能手动调用.
2:怎么判断对象存活
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
class MyObject{
public Object object = null;
}
1:引用计数法
给每一个对象设置个计数器 有引用就加1,引用失效就减1 当计数器为0 的时候就无效
缺点:无法解决循环用问题 A引用B B引用A AB引用都为1 会造成永远存活 无法GC
Java 虚拟机未采用此方法,Java中用的是可达性分析法
2:可达性分析法
Java 采用可达性分析法
以GC Roots对象未起点进行搜索,如果GC Roots和一个对象之间没有可达路径成为不可达,不可达对象不会立即回收会进行标记 多次标记之后才会被回收
分析:
object1.object = object2; object1 引用了object2 所以object2 引用是1
object2.object = object1; object2 引用了object1 所以object1 引用是1
object1 = null; object2 = null;
引用计数法:
object1和object1 虽然为Null 但是引用还是1 所以 按照引用计数法去判断对象不可用的时候 这两个对象是可用的
可达性分析法:
GC Roots---> object1---> object---> object2
GC Roots---> object2---> object---> object1
object1 = null object2 = null
GC Roots---> object1---> object ==null 后边中断了 ---> object2
GC Roots---> object2---> object ==null 后边中断了 ---> object1
object1和object2 不可达 会标记为不可达 至少两次GC后还是不可达就会标记GC
3:匿名内部类造成内存泄漏原因
匿名内部类隐式持有了创建类对象 创建类销毁的时候 因为有引用所以会造成创建类不销毁
4:setOnClickListener()是内名内部类实现为什么不会内存泄漏
因为 Activity onDestory销毁的时候 会移除View的引用 通过可达性分析法 会引用不到View 即中断了引用链 会被回收.
总结
JVM java运行在计算机的规范 分模块
类加载器 运行时内存 执行引擎 本机方法接口 本地方法库
运行时内存 内存划分
方法区 存储常量池 类信息 静态变量
堆 存储对象数据
JVM栈 方法调用的数据模型 存储栈帧
栈帧:方法调用的数据结构 局部变量表 操作数栈 方法返回地址
本地方法栈 jni方法栈
程序计数器
堆内存分区
新区(New)
伊甸园区8/10
幸存者0,1区
老区(Old)
永久代(元空间1)
标记复制
标记清除
标记压缩
GC Garbage Collection 垃圾收集回收
无用内存回收释放机制.
GC操作的思路是
通过GC Roots 搜索遍历关系可达就表示存活 其余对象不可达 即标记不可达 多次GC后还不可达 就释放内存
引用计数法(对象引用做记录,引用和移除引用计数做加减,引用为0 无用数据)
缺点:
无法处理循环引用 A引用B B引用A问题
Java 未使用
可达性分析法:
以GC Roots 对象未根路径搜索,通过引用链能引用到对象 成为可达对象 反之,引用链未到达称之为不可达.不可达对象,会被标记,经历至少两次GC以后对象会被销毁.
网友评论