jvm模型脑图
2020-05-29 19:38:27 0 举报
AI智能生成
个人学习JVM
作者其他创作
大纲/内容
jvm
GC
1.垃圾判断算法
引用计数法
在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。
优点:简单
缺点:无法解决循环引用
- 实列:先创建一个字符串,`String m = new String("jack");`,这时候 "jack" 有一个引用,就是m。然后将m设置为null,这时候 "jack" 的引用次数就等于 0 了,在引用计数算法中,意味着这块内容就需要被回收了。
public class MyObject {
public Object ref = null;
public static void main(String[] args) {
MyObject myObject1 = new MyObject();1
MyObject myObject2 = new MyObject();1
myObject1.ref = myObject2;//这一步 myObject2对应的地址会加1
myObject2.ref = myObject1;//这一步 myObject1对应的地址 也会加1,所以两个都是2
myObject1 = null;//然后只减了一个
myObject2 = null;//只减了一个
}
}
public Object ref = null;
public static void main(String[] args) {
MyObject myObject1 = new MyObject();1
MyObject myObject2 = new MyObject();1
myObject1.ref = myObject2;//这一步 myObject2对应的地址会加1
myObject2.ref = myObject1;//这一步 myObject1对应的地址 也会加1,所以两个都是2
myObject1 = null;//然后只减了一个
myObject2 = null;//只减了一个
}
}
可达性分析算法(现在都是这种,因为计数法有循环引用)
通过`GC ROOT`的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断对象是否可被回收。如果所有的引用链都没连到这个对象那么该对象会被回收
可达性分析图
上图D、F、E都不在引用链中 所以就要被回收
java中可作为GC Root的对象有
1.虚拟机栈中引用的对象(本地变量表)
2.静态属性引用的对象
3.常量引用的对象
4.本地方法栈中引用的对象(Native对象
优点:更加精确和严谨,可以分析出循环数据结构相互引用的情况
缺点:实现比较复杂、需要分析大量数据,消耗大量时间、分析过程需要GC停顿(引用关系不能发生变化),即停顿所有Java执行线程(称为"Stop The World",是垃圾回收重点关注的问题)。
分代:新生代、老年代、永久代
2.JVM GC时候核心参数
3.垃圾回收算法
4.垃圾回收器(记住名字,记住作用地方,及主要特点即可)
5.触发GC的条件(避免fullgc)
6.GC Easy 可视化工具
7.强引用、弱引用、虚引用
类加载机制
一、类加载机制概述
二. 类加载的生命周期(过程)
谈谈Java对象的创建过程
一、Java对象创建时机(什么时候触发对象创建
二. Java 对象的创建过程
Java类加载器
1、一共有四种类加载器
2.双亲委派机制
3.能否自定义java.lang.String类
4.其它(提一下就可以了)
常见异常
常用参数
jvm调优
jvm虚拟机数据区
程序计数器
作用:记录程序执行到什么地方了
1.如果线程正在执行java方法,则这个计数器记录的是正在执行的虚拟机字节码指令地址
2.如果正在执行的是本地方法(native),大部分为C语言实现,并没有编译成字节码指令,则这个计数器就为空(undefined)
3.此区域是jvm虚拟机唯一没有规定OutOfMemoryError的区域
原因:因为代码是在线程中执行的,一会儿CPU执行线程A 一会儿执行线程BB 如果不做记录 那么线程切换回来时就不知道执行哪段代码
java虚拟机栈
线程私有的。每个java方法被调用的时候都会创建一个栈帧,并入栈,一旦完成调用,则出栈。所有的栈帧都出栈后,线程就结束
线程是jvm创建的时候创建,一个线程对应一个栈,且虚拟机栈的生命周期是和线程一致的。除了native方法,java方法都是通过虚拟机栈来实现代码的调用和执行过程(当然需要程序计数器、堆、元空间数据)。java虚拟机栈是虚拟机执行引擎的核心之一。
栈帧
1.java虚拟机中出入栈的元素就称为栈帧
2.栈帧是用于支持虚拟机进行的方法调用和方法执行的数据结构.
3.栈帧存储了方法的局部变量表、操作数栈、动态链接栈和返回地址等信息。每一个方法从调用至执行完成。都对应着一个栈帧在虚拟机里从入栈到出栈的过程。
局部变量表
存储方法参数和局部变量
存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot变量槽),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot变量槽),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
操作数栈
在变量进行存储时,需要进行入栈和出栈
- 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈 / 入栈操作(例如:在做算术运算的时候是通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行参数传递的)
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。
动态链接
引用类型的指针
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中方法的符号引用为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态方法,私有方法等),这种转化称为静态解析,另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
方法出口(就是地址返回)
方法的返回
包含异常返回与正常返回
有效栈帧
在活动线程中, 只有位于栈顶的帧才是有效的,, 称为**当前栈帧**
当前栈帧
是指在栈顶的栈帧
在执行引擎执行时,所有操作都只会对当前帧进行操作,而 `StackOverflowError` 表示请求的**栈溢出**, 导致内存耗尽, 通常出现在递归方法中
栈溢出
栈溢出是指,方法执行的深度超过了允许深度
线程多了也会出现栈溢出问题,可以用XSS进行栈空间大小指定
jvm虚拟机栈
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,所以具体的虚拟机可以自由实现它。甚至有的虚拟机(比如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,所以具体的虚拟机可以自由实现它。甚至有的虚拟机(比如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
堆
类文件中常量池
所处区域:堆
诞生时间:编译时
内容概要:符号引用和字面量
class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种 字面量 (Literal)和 符号引用 (Symbolic References),每个class文件都有一个class常量池
**保存有静态变量与常量
诞生时间:编译时
内容概要:符号引用和字面量
class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种 字面量 (Literal)和 符号引用 (Symbolic References),每个class文件都有一个class常量池
**保存有静态变量与常量
字符串常量池
。。。。。。
堆中被分为新生代与老年代
**默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )**,即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小
然后新生代中有
eden:伊甸园区 占新生代8
s1、s2:Survivor 分别占1
默认的,Edem : from(s1) : to(s2) = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
**当一个对象创建时,优先是分配在新生代的eden(伊甸园)区**
然后新生代中有
eden:伊甸园区 占新生代8
s1、s2:Survivor 分别占1
默认的,Edem : from(s1) : to(s2) = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
**当一个对象创建时,优先是分配在新生代的eden(伊甸园)区**
老年代
1.**大对象会直接分配到老年代**
**-XX:PretenureSizeThreshold**即对象的大小大于此值, 就会绕过新生代, 直接在老年代分配, 此参数只对 Serial及ParNew两款收集器有效。
**-XX:PretenureSizeThreshold**即对象的大小大于此值, 就会绕过新生代, 直接在老年代分配, 此参数只对 Serial及ParNew两款收集器有效。
2.空间分配担保**
当MinorGC时,如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象
当MinorGC时,如果存活对象过多,无法完全放入Survivor区,就会向老年代借用内存存放对象
3.长期存活的对象将进入老年代
java虚拟机对每个对象都有设置有一个年龄值,当年龄值达到15(默认,可以通过java命令设置)岁,就会进入老年区。
具体就是java虚拟机每次检测回收对象时,如果对象正在被引用,那么java虚拟机就将此对象从S1区复制到S2区(或者S2到S1)并且将年龄加1
**4.动态对象年龄判定**
虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同 年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就 可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor区中相同 年龄(设年龄为age)的对象的所有大小之和超过Survivor空间的一半,年龄大于或等于该年龄(age)的对象就 可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
假如Survivor的大小为20M,有10个5岁的1M的内存,那么 下次java虚拟机就会把第一个5岁的对象放入survivor区
堆结构
本地内存
元数据区
1.7以前加方法区(或者永久代),1.8以后叫元空间,1.8以后把字符串常量池、静态变量、常量移去了堆里面。原因是以前方法区主要存储的类相关描述信息,不好计算大小,也不好进行垃圾回收,导致内存泄漏,容易出现java.lang.OutOfMemoryError: PermGen
元空间里面保存的就是类的元数据,如方法、字段、类、包的描述信息,这些信息可以用于创建文档、跟踪代码中的依赖性、执行编译时检查
类加载器存储的位置就是元空间,每一个类加载器的存储区域都称作一个元空间,所有的元空间合在一起就是我们一直说的元空间。当一个类加载器被垃圾回收器标记为不再存活,其对应的元空间会被回收。
存在的问题:由于元空间的内存分配值组块方式的,所以存在内存碎片问题
元空间如何提高性能(面试题)
1. 永久代里面的常量池都移到堆里面,只保存元数据,从而让 Full GC 不再关心方法区
2. 元空间使用直接内存,理论上系统内存有多大,元空间就可以有多大,理论上不存在 OOM(因为有大小,所以还是存在oom),其实也可以通过:`-XX:MetaspaceSize` 来控制它的初始大小
3. 元空间有单独的元空间虚拟机执行内存分配与垃圾回收
1. 永久代里面的常量池都移到堆里面,只保存元数据,从而让 Full GC 不再关心方法区
2. 元空间使用直接内存,理论上系统内存有多大,元空间就可以有多大,理论上不存在 OOM(因为有大小,所以还是存在oom),其实也可以通过:`-XX:MetaspaceSize` 来控制它的初始大小
3. 元空间有单独的元空间虚拟机执行内存分配与垃圾回收
以后遇到java.lang.OutOfMemoryError: Metaspace 就是元空间的OOM ,一般不会出现,可以通过工具VisualVM监控,因为这里存储的类、方法等描述信息,那么可能是1.Metaspace 设置小了,2.或者一次性加载了太多类,3.java反射的滥用,出现bytecode
面试题:java反射速度慢的原因
一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版
运行时常量
内容概要:class文件元信息描述,编译后的代码数据,引用类型数据,类文件常量池。
所谓的运行时常量池其实就是将编译后的类信息放入运行时的一个区域中,用来动态获取类信息。
运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致
所谓的运行时常量池其实就是将编译后的类信息放入运行时的一个区域中,用来动态获取类信息。
运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致
直接内存
NIO的Buffer提供了一个可以不经过JVM内存直接访问系统物理内存的类——DirectBuffer。 DirectBuffer类继承自ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer仍在JVM堆上分配内存,其最大内存受到最大堆内存的限制;而DirectBuffer直接分配在物理内存中,并不占用堆空间。
就是物理内存,理论上是不会出现内存溢出。但是物理机也有内存限制所以还是会出现内存溢出。
绿色为线程隔离,线程安全
灰色为线程共享,线程不安全
0 条评论
下一页