# 技术栈知识点巩固——Jvm
Posted MarlonBrando1998
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了# 技术栈知识点巩固——Jvm相关的知识,希望对你有一定的参考价值。
什么是字节码?采用字节码的最大好处是什么?什么Java是虚拟机?
java
源代码通过虚拟机编译器编译后产生的文件.class
文件,他不面向任何特定的处理器,直面向虚拟机。- 一次编译到处运行。
Java
虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java
程序只需生成在Java
虚拟机上运行的目标代码字节码,就可以在多种平台上不加修改地运行。
字节码的编译过程
JVM怎么判断对象是可回收对象?有哪些方法。
引用计数法
- 给对象添加一个引用计数器,当有一个地方引用它,计数器值加 1;当引用失效时,计数器值减 1。任何时刻计数器值为 0 表示这个对象可以被回收了。
- 效率高,实现简单,无法解决循环引用。
可达性分析
- 从根节点到对象没有可达的路径。
JVM的内存结构,新生代与老年代的比例,Eden和Survivor比例。
新生代和老年代
图片来自:https://www.cnblogs.com/liuqing576598117/p/10277053.html
jvm 内存结构
类加载器,可以打破双亲委派么,怎么打破
- 类加载器见博客:https://blog.csdn.net/qq_37248504/article/details/116764112
- 在
Tomcat
中,就打破了双亲委派机制,否则多个webapp
类依赖容易产生冲突,如果awebapp
依赖了spring4
,bwebapp
依赖了spring5
,而spring
在包路径和命名都有很大的相似,但是功能实现差别却又很大,所以必须进行隔离各自webapp
进行单独加载。
JVM内存为什么要分成新生代,老年代,持久代
- 对象的存活所时间是不一样的。
- 为了更好的进行垃圾回收。不同的区进行不同的垃圾回收机制,提高效率。
新生代中为什么要分为Eden和Survivor
- 如果没有
Survivor
区,那么Eden
每次满了清理垃圾,存活的对象被迁移到老年区,老年区满了,就会触发Full GC
,Full GC
是非常耗时的。 Jvm
优化主要就是增加Minor Gc
减少FullGc
。
JVM 出现 FullGC 很频繁,怎么去线上排查问题?
原因
- 程序执行了
System.gc()
有可能触发FullGC
。 - 老年代空间不足。
- 方法区空间不足。
解决方法
- 通过
Jvm
参数获取堆快照
# 生成内存快照文件
-XX:HeapDumpBeforeFullGC
-XX:HeapDumpPath=保存dump文件的文件绝对路径
- 通过
Jdk
自带的工具
# 导出内存dump文件
jmap -F -dump:live,file=jmap.hprof [PID]
# 把dump文件从线上主机下载到本地
scp local_file remote_ip:remote_file
- 生成内存快照,分析内存快照。
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
说说你知道的几种主要的JVM参数。
Xms
:最小堆内存Xmx
:堆的最大内存Xmn
:新生代内存
Java对象的创建过程
- 类的生命周期可见:https://blog.csdn.net/qq_37248504/article/details/106302662
JVM
首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化
。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。
类初始化顺序
- 父类静态变量、静态初始化块
- 子类静静态变量、静态初始化块
- 父类初始化块、构造方法
- 子类初始化块、构造方法
Jvm参数配置
常用参数
-Xms:初始堆大小(最小堆)。
-Xmx:最大堆大小。
-Xmn:年轻代大小(Sun官方推荐配置为整个堆的3/8)。
-Xss:每个线程的堆大小(在相同物理内存下,减小这个值能生成更多的线程)。
-Xms 和 -Xmx 设置成一致的值可以避免堆自动扩展。
JVM内存大小 = 年轻代大小 + 老年代大小 + 持久代大小(perm)。
G1和cms区别
G1
G1
收集器收集范围是老年代和新生代。不需要结合其他收集器使用。- 可预测垃圾回收停顿时间。
- 使用标记整理算法,降低内存空间碎片。
Cms
CMS
收集器是老年代的收集器,可以配合新生代的Serial和ParNew
收集器一起使用。- 使用标记清除算法,容易产生内存碎片。
打出线程栈信息
- 找出服务进程
id
:ps -ef|grep java
sudo -u admin jstack pid > jstack.txt
类加载的执行过程
对象的访问定位有哪两种方式?
User user = new User();
user
表示一个本地引用,存储在栈的本地变量表中,表示一个引用类型的数据。new User()
作为实例对象放在堆中,Java
堆中存放了对象的类型、方法等信息。
使用句柄
直接指针
- 图片来自:https://www.jianshu.com/p/8580ab50e261
jvm 调优的工具
JVM垃圾回收机制,何时触发MinorGC等操作呢?
MinorGC
:新生代GC
,对象优先在eden
创建并区分配内存,当eden
区内存无法为一个新对象分配内存时,就会触发MinorGC
FullGc
:老年代GC
,jvm
调优主要是尽可能增加MinorGC
减少FullGc
对象什么时候会进入老年代?
- 对象年龄达到一定的大小 ,就会离开年轻代, 进入老年代。
- 若对象体积太大, 新生代无法容纳这个对象。
内存泄漏和内存溢出区别?
内存泄漏
- 程序在申请内存后,无法释放已申请的内存空间就造成了内存泄漏,内存泄漏堆积后的后果就是内存溢出。
- 对象被引用,无法回收。例如没有关闭流、集合没有清空
内存溢出
- 指程序申请内存时,没有足够的内存供申请者使用。
- 例如 从数据库中查出大量数据、创建大量的对象没有回收。
什么情况下会发生栈内存溢出。什么时候发生堆溢出?你是怎么排错的?
栈溢出
- 方法不能存栈帧中弹出,例如递归调用死循环。
堆溢出
- 创建大量的对象后,没有进行垃圾回收。
排错方法
- 生成内存快照文件,分析快照文件。
- 查看程序输出的日志文件。
tomcat类加载机制
Tomcat
的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object
,String
等),各个web
应用自己的类加载器(WebAppClassLoader
)会优先加载,加载不到时再交给commonClassLoader
走双亲委托。- 图片来源:https://www.cnblogs.com/aspirant/p/8991830.html
JIT
JIT
即时编译:在运行时候将字节码翻译为机器码。C1(Client)
:编译速度块,优化方式比较保守。- C2(server):编译速度慢,优化方式比较激进。
C1+C2
(分层编译):在开始阶段采用C1
编译,当代码运行到一定热度之后采用G2
重新编译。
逃逸分析技术
- 在编程语言的编译优化原理中,分析指针动态范围的方法称之为逃逸分析。
- 方法逃逸:对象作为参数进行传递。
- 线程逃逸:类变量、实例变量被不同的线程访问。
调用System.gc()会发生什么?
System.gc()
:告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的。System.runFinalization()
: 强制调用已经失去引用的对象的finalize
方法。
MinorGC条件FullGC条件
- 老年代最大的可用连续空间是否大于新生代的所有对象总空间,如果大于则执行
MinorGC
- 老年代空间不足触发
FullGC
System.gc()和Runtime.gc()会做什么事情?
System.gc()
public static void gc() {
Runtime.getRuntime().gc();
}
-
Runtime.gc()
-
调用了
System.gc()
或者Runtime.gc()
只是告诉虚拟机可以gc了
,但是什么时候gc
依然由虚拟机自身决定,调用之后是否回收资源是由虚拟机自身控制的,这个方法仅仅只是告诉他可以gc
了,gc
有一系列复杂的机制,至于它什么时候gc
由这些机制决定。
主内存与工作内存
- 主内存是所有的线程所共享的,工作内存是每个线程自己有一个。
内存间交互操作
Java
内存模型将内存分为工内存和主内存,变量从主内存拷贝到工作内存的过程中,java
内存模型定义了8中操作完成。
- 锁定
lock
:锁定主内存中的变量,将变量表示为一个线程独占的状态。 - 解锁
unlock
:释放主内存中锁定的变量,释放的变量被其他的线程使用 - 读取
read
:将主内存中的变量传输到线程工作内存中。 - 载入
load
:将read
的变量放到工作内存的共享变量副本中。 - 使用
use
:把工作内存中一个变量的值传递给执行引擎。 - 赋值
assign
:把一个从执行引擎接收的值赋给工作内存的变量。 - 存储
store
:把工作内存中一个变量的值传送到主内存中,以便随 后的write
操作使用。 - 写入
write
:把store
操作从工作内存中得到的变量的值放入主内存的 变量中。
volatile 禁止内存重排序
指令重排序
- 所谓指令重排序,是指计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令进行重排。指令重排必须保证最终执行结果和代码顺序执行结果一致。
- 在单线程中不会有问题,但是在多线程环境下会出问题。
public void test(){
// a b c 通过指令重排 执行的顺序不一定
int a = 1;
int b = 2;
int c = a+4;
int d = a*a;
}
volatile
规定禁止指令重排,从而保证数据的一致性。
内存模型三大特性
原子性
- 通过
synchronized
关键字定义同步代码块或者同步方法保障原子性。 - 通过
Lock
保证原子性。 - 通过
Atomic
类型进行原子操作。
可见性
volatile
:保证新值能立即同步到主内存,每次使用前立即从主内存中刷新。synchronized
:synchronized
关键字在释放锁之前,必须先把此变量同步回主内存中。final
:final
修饰的变量,一旦完成初始化,就不能改变。
有序性
volatile
规定禁止指令重排,从而保证数据的一致性。synchronized
:某一时刻只有一个线程访问资源。
谈谈先行发生原则
- 先行发生原则是判断数据是否存在竞争、线程是否安全的主要依据。
- 先行发生是
Java
内存,模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,那么操作A产生的影响能够被操作B观察到。
JVM 为什么使用元空间替换了永久代?
-
在Java7及以前的版本,是存在永久代的。在Java7版本时,永久代已经发生了悄悄的变化。等到Java8时,彻底废弃了永久代,由元空间替换。
-
永久代是
HotSpotVM
对方法区的实现,JDK 8 将其移除。 -
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
-
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
-
将
HotSpot
与JRockit
进行整合,JRockit
是没有永久代的。 -
图片来自:https://blog.csdn.net/qq_33591903/article/details/105634782
是堆中的永久代(Perm Gen space)
JVM
内存划分为堆内存和非堆内存,堆内存分为年轻代、老年代,非堆内存就一个永久代。- 堆内存:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
- 非堆内存:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
- 一般设置为128M就足够。
JVM的永久代中会发生垃圾回收么?
- 会,当永久代满了会触发
Full GC
MinorGC 的过程
- 新创建的对象分配到
Eden
区,S0、S1
为空。 - 当
Eden
区满了的时候,minor garbage
被触发。 - 经过扫描标记,存货的对象复制到
S0
不存活的对象被回收。 - 当
Eden
和S0
区空间满了,S0
的所有的数据都被复制到S1
,对象的年龄+1。 - 经过几次
MinorGC
之后,当存活对象的年龄达到一个阈值之后,就会被从年轻代Promotion
到老年代。 MinorGC
一次又一次的进行,不断会有新的对象被promote
到老年代。
解释 Java 堆空间及 GC
Java
程序在启动的时候,会分配内存,程序中的对象创建的时候,从堆空间中分配内存。Gc
是Jvm
内部的一个进程,回收无效的对象用于将来的分配。
能保证 GC 执行吗?
-
不能,虽然你可以调用
System.gc()
或者Runtime.gc()
,但是没有办法保证GC
的执行,
Java
内存回收的工作由Jvm
自动操作。
WeakHashMap 是怎么工作的?
WeakHashMap
使用的是弱引用,因此它的对象可能被随时回收。更直观的说,当使用WeakHashMap
时,即使没有删除任何元素,它的尺寸、get方法也可能不一样。
@Test
public void testWeakHashMap() {
WeakHashMap<String, String> map = new WeakHashMap<>();
map.put("key_one", "test");
map.put("key_two", "test");
map.put("key_three", "test");
}
垃圾收集器,各自的优缺点
Stop The World 了解过吗?
- 在垃圾回收过程中经常涉及到对对象的挪动(比如对象在
Survivor 0
和Survivor 1
之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为Stop-The-World
如何避免OOM
- 使用轻量的数据结构,例如使用
StringBuilder
拼接字符串 - 不用的对象因该立马释放,关闭不用的流
- 使用软引用
JVM调优思路?如何确定它们的大小呢?
调优目标
- 延迟:GC低停顿和GC低频率
- 低内存占用
- 高吞吐量
调优步骤
- 分析GC日志快照文件,判断是否需要优化
- 确定JVM调优参数
- 对比观察调优前后的差异
- 不断的分析和调整,直到找到合适的JVM参数配置
以上是关于# 技术栈知识点巩固——Jvm的主要内容,如果未能解决你的问题,请参考以下文章