# 技术栈知识点巩固——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依赖了spring4bwebapp依赖了spring5,而spring在包路径和命名都有很大的相似,但是功能实现差别却又很大,所以必须进行隔离各自webapp进行单独加载。

JVM内存为什么要分成新生代,老年代,持久代
  • 对象的存活所时间是不一样的。
  • 为了更好的进行垃圾回收。不同的区进行不同的垃圾回收机制,提高效率。

新生代中为什么要分为Eden和Survivor
  • 如果没有Survivor区,那么Eden每次满了清理垃圾,存活的对象被迁移到老年区,老年区满了,就会触发Full GCFull 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收集器一起使用。
  • 使用标记清除算法,容易产生内存碎片。

打出线程栈信息
  • 找出服务进程idps -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:老年代GCjvm调优主要是尽可能增加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:保证新值能立即同步到主内存,每次使用前立即从主内存中刷新。
  • synchronizedsynchronized关键字在释放锁之前,必须先把此变量同步回主内存中。
  • finalfinal修饰的变量,一旦完成初始化,就不能改变。
有序性
  • volatile 规定禁止指令重排,从而保证数据的一致性。
  • synchronized:某一时刻只有一个线程访问资源。

谈谈先行发生原则
  • 先行发生原则是判断数据是否存在竞争、线程是否安全的主要依据。
  • 先行发生是Java内存,模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,那么操作A产生的影响能够被操作B观察到。
JVM 为什么使用元空间替换了永久代?
  • 在Java7及以前的版本,是存在永久代的。在Java7版本时,永久代已经发生了悄悄的变化。等到Java8时,彻底废弃了永久代,由元空间替换。

  • 永久代是 HotSpotVM 对方法区的实现,JDK 8 将其移除。

  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

  • HotSpotJRockit 进行整合,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不存活的对象被回收。
  • EdenS0区空间满了,S0的所有的数据都被复制到S1,对象的年龄+1。
  • 经过几次MinorGC之后,当存活对象的年龄达到一个阈值之后,就会被从年轻代Promotion到老年代。
  • MinorGC一次又一次的进行,不断会有新的对象被promote到老年代。

解释 Java 堆空间及 GC
  • Java程序在启动的时候,会分配内存,程序中的对象创建的时候,从堆空间中分配内存。
  • GcJvm内部的一个进程,回收无效的对象用于将来的分配。
能保证 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 0Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为Stop-The-World

如何避免OOM
  • 使用轻量的数据结构,例如使用StringBuilder拼接字符串
  • 不用的对象因该立马释放,关闭不用的流
  • 使用软引用
JVM调优思路?如何确定它们的大小呢?
调优目标
  • 延迟:GC低停顿和GC低频率
  • 低内存占用
  • 高吞吐量
调优步骤
  • 分析GC日志快照文件,判断是否需要优化
  • 确定JVM调优参数
  • 对比观察调优前后的差异
  • 不断的分析和调整,直到找到合适的JVM参数配置

以上是关于# 技术栈知识点巩固——Jvm的主要内容,如果未能解决你的问题,请参考以下文章

# 全栈开发学习文档

# 技术栈知识点巩固——Nginx

# 技术栈知识点巩固——Nginx

# 技术栈知识点巩固——Mybatis

# 技术栈知识点巩固——Mybatis

# 技术栈知识点巩固——Css