<>1.JVM介绍

<>1.什么是jvm

*
Java Virtual Machine(java二进制字节码运行环境)

*
好处:

* 一次编译,好处运行
* 自动内存管理,垃圾回收机制
* 数组下标越界检查
* 多态
*
比较JVM\JRE\JDK

*
jvm=屏蔽java代码与底层操作系统的差异

*
JRE=JVM+基础类库

*
JDK=JVM+JRE+编译工具 Java -v -p class 反编译字节码

<>2.常见jvm

<>3.jvm运行流程

*
javaClass被编译成二进制字节码后通过类加载器加载到jvm里面运行。类放到方法区,类创建的实例对象放到堆中,堆里面的对象调用方法时会使用到虚拟机栈,程序计数器、本地方法栈。方法执行时每行代码通过执行引擎中的解释器逐行执行,方法里的热点代码会由即时编译器做一个优化后的执行,堆中没有被引用的对象会被垃圾回收,java不方便实现的功能必须调用底层的方法就需要调用本地方法提供的接口

<>2.内存结构

<>1.程序计数器

*
定义

程序计数器是java对物理硬件的屏蔽和抽象,物理上是通过寄存器来实现的。

首先将java源码编程成二进制字节码(这里统一简称jvm指令),然后解释器将每一条指令解释成机器码,最后交给cpu执行,执行过程中寄存器会记录下一条指令的地址。

*
作用

* 记录下一条jvm指令的执行地址
*
特点

* 是线程私有的,每一个线程都有属于自已的寄存器
* 不会存在内存溢出
<>2.栈

* 什么是栈?
* 栈是一种先进后出的结构,是线程运行需要的内存空间,它有很多栈帧组成,栈帧是方法运行所需要的内存空间,它里面包含方法的参数、局部变量、返回值
* 垃圾回收不涉及栈内存
* 栈内存并不是分配(-xss)的越大越好,分配越大(比如物理内存有500m -xss设置2m,那么线程数就为250),线程数越少,不过可以更多次的递归调用
*
cpu占用过高如何排查?

*
命令

* 使用top命令查询到哪个进程占用cpu最高
* ps -H(进程里所有的线程信息展示出来) -eo pid,tid,%cpu | grep 进程id 用ps命令进一步查看哪个线程引起的cpu过高
*
jstack 进程id

* 根据线程id(jstack查询来的线程是10进制显示) 找到有问题的线程,进一步定位到问题代码
<>3.本地方法栈

*
虚拟机调用本地方法时,需要给本地方法提供的内存空间,因为java代码有一些限制不会和操作系统底层打交道,所以需要c或者c++这样的语言编写的本地方法与操作系统api打交道,java代码可以间接调用本地方法来调用到底层方法
<>4.堆

*
定义

* 通过new关键字,创建对象都会使用堆内存
*
特点

* 线程共享,堆中的对象都需要考虑线程安全的问题
* 有垃圾回收机制
*
堆内存溢出

*
堆内存诊断

* jps
* 查看当前系统中有哪些java进程
* jmap工具
* 查看堆内存占用情况 jamp -heap 进程id
* jconsole工具
* 图形界面,多功能的监测工具,可以连续监测
* jvirsualvm工具
* 图形界面
<>5.方法区

*
定义

方法区是所有java虚拟机线程共享的区,存储了跟类的结构相关的信息(成员变量、方法数据、成员方法和构造器的代码),方法区在虚拟机启动时创建。方法区申请内存时发现不足,虚拟机会抛出OOM

*
常量池

* 就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
* 运行时常量池,常量是*.class文件中的,当该类被加载,它的常量池信息就会被放入运行时常量池,并将里面的符号地址变为真实地址
*
StringTable特性

* 常量池中的字符串仅是符号,第一次使用时才变为对象
* 利用串池的机制,来避免重复创建字符串对象
* 字符串拼接的原理是StringBuilder
* 字符串常量值拼接的原理是编译器优化
* 可以使用intern方法主动将串池中还没有的字符串放入串池
* stringTable会发生垃圾回收

<>6.直接内存

*
定义

* 常见于NIO操作时,用于数据缓冲区
* 分配回收成本较高,读写性能高
* 不受JVM内存回收管理
*
直接内存的基本使用(零拷贝)

* bytebuffer

*
传统io

*
比对效率

*
原理图

*
直接内存的释放原理

* 当调用System.gc的时候,直接内存也被释放了,底层是怎么做的呢?

*
底层通过Unsafe对直接内存的分配,通过freeMemory来释放直接内容

<>3.垃圾回收

<>1.四种引用

*
强引用

* 只有所有GCroots对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
*
软引用

* 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象
* 可以配合引用队列来释放软引用自身 // -Xmx20m public class Demo1 { private static final int
_4MB= 4 * 1024 * 1024; public static void main(String[] args) { List<
SoftReference<byte[]>> list = new ArrayList<>(); ReferenceQueue<byte[]> queue =
new ReferenceQueue(); for (int i = 0; i < 5; i++) { // 加入引用队列,垃圾回收时
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue); System.
out.println(ref.get()); list.add(ref); System.out.println(list.size()); } System
.out.println("循环结束:"+list.size()); // 清除软引用 Reference<? extends byte[]> poll =
queue.poll(); while (poll != null){ list.remove(poll); poll = queue.poll(); }
for (SoftReference<byte[]> ref : list) { System.out.println(ref.get()); } } }
*
弱引用

* 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
* 可以看到执行到14次的时候,弱引用全部为null,是因为发生了一次fullGC

*
虚引用

*
必须配合引用队列使用,主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由RefrenceHandler线程调用虚引用相关方法释放直接内存
*
终结者引用

*
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由Finalizer线程通过终结器引用找到引用对象并调用它的finalizer方法,第二次gc时才能回收被引用对象
<>2.垃圾回收算法

<>1.标记清除算法

*
扫描堆内存中没有被根对象所直接或者间接引用的对象进行标记,然后将标记的内存空间给释放(对象起始结束的地址放到一个空闲地址列表里)下次有新对象时到这个列表里找有没有足够的空间容纳新对象
* 优点:速度快。缺点:内存碎片化

<>2.标记整理

* 优点:内存碎片化。缺点:速度慢

<>3.复制

优点:内存碎片化。缺点:双倍内存空间

<>3.分代回收

*
jvm不会只使用一种垃圾回收算法,而是会根据情况来选择使用,具体实现在jvm里面称之为分代的垃圾回收机制

* 首先新的对象被使用伊甸园空间
* 当新生代空间不足时,会触发一次MinorGC将存活的对象使用复制算法复制到幸存区To中,并标记回收次数+1,然后交换幸存区to和from的位置
* minorGC会引发stop the word,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
* 当对象寿命超过阈值,会晋升老年代,最大寿命是15(4bit)
* 当老年代空间不足时,会先尝试minorGC,如果之后空间仍不足时,会触发FullGc,STW时间会更长

<>1.jvm参数

<>4.垃圾回收器

<>1.串行

* 单线程
* 堆内存比较小,适合个人电脑
* 新生代采用复制算法,老年代采用标记+整理

<>2.吞吐量

* 多线程
* 堆内存较大,多核cpu
* 让单位时间内,stw时间最短
* 并行(多线程)新生代采用复制算法,老年代采用标记+整理

<>3.响应时间 CMS垃圾回收器

* 多线程
* 堆内存较大,多核cpu
* 单词stw时间最短

<>4.G1垃圾回收

*
适用场景

* 同时注重吞吐量、低延时,默认的暂停目标是200ms
* 超大堆内存,会将堆内存划分为多个相同大小的region
* 整体上是标记+整理算法,两个区域之间是复制算法
*
相关参数

-XX:+UseG1GC

-XX:G1HeapRegionSize=size

-XX:MaxGCPauseMillis=time

*
运行流程

*
YoungCollection

* 新生代收集,对象分配到伊甸园区,当伊甸园区满了后会触发新生代垃圾回收会STW并以拷贝的算法放到幸存区,幸存区满了会部分对象晋升老年代
*
YoungCollection+ConcurrentMark

* 在YoungGC时会进行GcRoot的初始标记
* 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由jvm参数决定
-XX:InitiatingHeapOccupancyPercent=percent(默认45%)
*
MixedCollection

<>1.字符串去重

*
优点:节省内存空间

*
缺点:略微多占用了cpu空间,新生态回收时间略微增加

<>5.垃圾回收调优

<>1.新生代调优

*
新生代的特点

*
所有的new操作的内存分配非常廉价

*
死亡对象的回收代价是0

*
大部分对象用过即死

*
MinorGC时间远低于FullGC

<>2.老年代调优

*
cms为例

*
cms的老年代内存越大越好

*
先不做调优,如果没有fullgc说明老年代空间充裕,否则先尝试调优新生代

*
观察发生fullgc时老年代的内存占用,将老年代内存预设调大1/4~1/3

-XX:CMSInitiatingOccupancyFraction=percent

<>6.类文件结构

<>1.类文件结构

<>7.类加载

<>1.加载

* 将类的字节码载入方法区,内部采用c++的instanceKlass描述java类,它的重要field有:
* _java_mirror即java的类镜像,例如对string来说,就是String.class,作用是把klass暴露给java使用
* _super即父类
* _fileds即成员变量
* _methods即方法
* _constants即常量池
* _class_loader即类加载器
* _vtable虚方法表
* _itable接口方法表
* 如果当前类的父类还没有加载,先加载父类
* 加载和链接可能是交替运行的

<>2.链接

*
验证类是否符合JVM规范,java安全检查

*
可以将魔术部分修改进行测试

*
准备

* static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
* 如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
* 如果static变量是final的引用类型,那么赋值也会在初始化阶段完成
*
解析

* 将常量池中的引用解析为直接引用
<>3.初始化

* 会初始化的情况
* main方法所在的类,总会被首先初始化
* 首次访问这个类的静态变量或静态方法时
* 子类初始化,如果父类还没初始化
* 子类访问父类的静态变量,只会触发父类的初始化
* Class.forName
* new 会导致初始化
* 不会初始化的情况
* 访问类的static final的静态常量(基本类型和字符串)
* 类对象.Class
* 创建该类的数组
* 类加载器的loadClass方法
* Class.forName的参数2为false时
<>8.类加载器

1.类加载器介绍

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信