-
JVM虚拟机的启动
-
JVM虚拟机的执行
虚拟机指令
//编辑Java文件 虚拟机指令 javap -v 类名.class //查看程序执行 进程 jps
简略版jvm虚拟机
3.JVM 虚拟机的 栈 特点
跨平台性、指令集小、指令多;职能g'xing
4.加载器
4.1系统加载器 java的核心类库,使用系统加载器 4.2引导类加载器 获取 加载器时为null 4.3自定义加载器 什么样的文件加载什么样的文件
5.双亲委派机制
jvm对calss文件 ,是一个按需加载机制。什么时候需要什么时候加载
自定义加载器 → 系统类加载器 → 扩展类加载器 → 引导类加载器
6.反向委派机制
加载第三方类的时候 核心类提供接口 → 执行引导类加载 → 线程上下文加载器(默认执行:系统类加载器) → 执行第三方jar包 优势: 1.避免类的重复加载 2.保护程序安全,防止核心api被篡改
7.运行时数据区
7.1程序计数器 (pc寄存器) (可以理解为 游标或者 迭代器) 作用:来储存下一条指令 地址的 (占内存空间很小) 特点:属于线程私有,生命周期与线程保持一致。 程序的 分支、循环、跳转、异常处理、线程回复,都需要他来执行。 jvm唯一个没有内存溢出 的 区域。 7.2 为什么使用pc寄存器 储存字节码指令?? 答:因为cpu需要不停切换各个线程,当他切换到要执行线程时,就时候需要jvm的字节码解释器 通过 pc寄存器的值来明确下一条指令。 7.3pc寄存器为什么设定为线程私有? 为了能准确地记录各个线程正在执行的字节码指令地址,最好的方法自然是每个线程分配一个pc寄存器。独立计算,不会出现干扰。 7.4 栈管运行堆管存储 7.5 栈中常见异常 1.StackOverFlowError (如果 jvm虚拟机设置了 固定的栈大小,当运行程序超出,设置的大小就会报StackOverFlowError (栈空间溢出)) 2.OutOfMemoryError (如果jvm虚拟机栈可以动态扩展,但是扩展时无法申请到足够的内存,或者创建创建虚拟机栈,没有足够的内存,就会报OutOfMemoryError)
8.虚拟机栈(栈管运行,队管存储)
8.1 java虚拟机栈,其内部保存一个个栈帧(Stack Frame)(栈帧是栈的最小储存单元) 一个线程对应一个java虚拟机栈 (栈不存在垃圾回收问题, 但是存在内存溢出) 栈: 先进后出 队列:先进先出
8.2 栈中储存 一个线程对应一个java虚拟机栈 每个方法队形一个栈帧
8.3栈的内部结构 局部变量表 局部变量表中的变量只在当前方法调用中有效。 当方法调用结束后,随着方法栈的销毁,局部变量表也会随之销毁。 局部变量表 会存放方法的变量, //在静态方法当中,不允许引用this。原因:this变量不存在当前变量表中 //实例方法中可以引用this, 实例对象提供this变量的Slot访问索引 操作数栈 入栈/出栈 方法返回地址 动态链接 一些附加信息 帧数据区(方法返回地址、动态链接、一些附加信息)
8.4 栈顶缓存技术(hotspot虚拟机) 8.5方法区 方法区是一个非常重要的区域,也是被线程共享的区域,方法区存储了每个类的信息(类的名称、方法信息、字段信息),静态变量、常量以及编译后的代码等。 方法区还包括一个常量池,用来存储编译期间生成的字面量和符号引用。这部分内容在类被加载后,都会存储到方法区中的RCP。值得注意的是,运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。 常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用.
8.5.1常量池作用 常量池的作用:就是为了提供一些符号和常量,便于指令的识别。(高可用,使得编译文件较小) 本地方法接口(本地方法库) //java虚拟机 需要调用C库,所存在一些方法 本地方法接口:包括对操作系统对接 本地方法栈 java虚拟机用于管理java方法的调用 本地方法栈,是私有的。
回顾 方法 -- > 栈帧 --> 局部变量 --> 方法返回地址 --> 操作数栈 --> 动态链接 --> 一些附加信息
面试问题
1.栈溢出的情况(stackOverflowError) 答:设置栈大小,运行时超出设置值 //不设置栈大小,运行时超出物理内存值 (内存溢出) 3.栈余越大越好么??? 答:他会挤占运行时区 其他区域的空间
2.垃圾回收会涉及虚拟机栈么? 答案:如以下表格
|
GC
|
Error
|
程序计数器
|
×
|
×
|
虚拟机栈
|
×
|
√
|
本地方法区
|
×
|
√
|
堆
|
√
|
√
|
方法区
|
√
|
√
|
堆
什么是TLAB // 1.一个进程对应一个方法区和堆。 2.将堆内存分成小块,每个线程占用一小块内存堆。就形成了TLAB。 3.方法结束后,堆中对象不会马上转移,
关系图
堆空间的内部结构 java7 以及之前堆内存逻辑分为三部分:新生区+养老区+永久区
java8和之后版本 内存逻辑分为:新生区+养老区+原空间 相较于以前版本,永久区 变为 原空间
设置堆空间的大小
-Xms 表示堆起始内存
-Xmx 表示堆区的最大内存
//堆的OOM
年轻代和老年代
优先在伊甸园区分配,
堆内存 --> 年轻代 --> 伊甸园、S0、S1的对象分配
YGC过程,伊甸园区空间充满时,会触发YGC
常见调优工具
Minor GC、Major GC、Full GC
(jvm调优 可简单的理解为 执行GC次数少一些) Minor GC 完全等价与 YGC(年轻代) 老年代区域 执行 Major GC Full GC :整个java堆,方法区的垃圾收集 Major 和 Full GC 执行时间 相较于 Minor GC时间较长 JVM进行GC,并非每次都是对三个内存区域一起回收,大部分时候回收是 指新生代。(三部分指:新生代、老年代、方法区) 年轻代触发机制(Minor GC) 1.当年轻代空间不足,就会触发Minor GC(年轻代中 Edem区满了,才会触发GC。 S1,S0不会触发GC) 2.Minor GC 触发频率高 3.Minor GC 会触发STW(停止其他用户线程),等垃圾回收结束,才会继续执行。 老年代GC(Major GC/Full GC) 1.当对象从老年代中消失,Major GC 或 Full GC 执行发生了 2.执行Major GC 经常会执行Minor GC(非绝对),(当老年代空间不足则先触发Minor GC,如果还是不足则触发Major GC) 3.Major GC的速度会很慢,STW的时间会很长。 4.Major GC之后空间还是不足,就会抛出异常(OOM)内存溢出 触发Full GC的五种情况 1.调用System.gc(),系统建议执行Full GC。(非必然执行) 2.老年代空间不足 3.方法区空间不足 4.通过Miner GC后进入老年代的平均大小大于老年代的空用内存(标记 没理解) 5.当创建对象Eden区空间不足,转移到S0,S1同样出现空间不足,直接晋升到老年代,老年代空间不足就触发Full GC //打印GC在虚拟机中的指令 -Xms9m -Xmx9m -XX:+PrintGCDetails 字符串常量池存在堆空间 针对不同年龄的对象分配原则: 1.优先分配Eden区 2.大对象直接分配到老年代(尽量避免出现大对象) 3.长期存活的对象分配到老年代 堆空间参数设置 -XX:+PrintFlagsInitial : 查看所有默认参数的初始值 -XX:+PrintFlagFinal : 查看所有参数的最终值 -Xms: 初始堆空间内存(默认是物理内存的1/64) -Xmx: 最大堆空间内存(默认是物理内存的1/4) -Xmn: 设置新生代的大小。(初始值) -XX:NewRatio: 配置新生代与老年代在堆结构的占比 -XX:SurvivorRatio: 设置新生代中Eden和S0/S1空间的比例 -XX:MaxTenuringThreshould: 设置新生代垃圾的最大年龄 -XX:PrintGCDetails: 输出详细的GC处理日志 -XX:HandlePromotionFailure:是否设置控件分配担保
代码优化
1.栈上分配:将堆分配。在方法new出对象,并返回结果。 -Xms256m -Xmx256m -XX:DoEscapeAnalysis -XX:+PrintGCDetails 2.同步省略 3.分离对象或标量替换