复习之 JVM【类加载机制,内存模型,GC 】

裁员在家,没有面试机会,整理整理面试知识点吧!

JVM 全名 Java Virtual Machine(Java虚拟机

Java中的所有类,必须被装载到JVM中才能运行,这个装载工作是由jvm中的类装载器完成的,.class文件可以在虚拟机运行,但不是直接和操作系统交互,需要jvm解释给操作系统,解释的时候需要java类库,这样就能和操作系统交互。

类加载器:启动类加载器,扩展类加载器,应用程序类加载器。自定义类加载器

类加载机制 :

加载:加载编译后的class 文件。

验证:验证class 文件是否符合java 虚拟机规范 比如(是否以CAFEBABE 开头,是否存在父类继承是否正确,final 不能被继承 等等吧。因为class 文件可能从网上拿过来,可能被修改)

准备:静态变量申请内存空间,赋初默认值。

解析:符号引用转换成直接引用。类里 User user = new User(); 此时user 就是符号引用。她只是一个符号user 没有任何意义,当解析的时候 会将user 符号替换成 new User() 在堆里指针,指向堆里的user 实例对象。。

初始化:  静态变量,初始化代码块执行初始化操作。

双亲委派机制:   

一个类过来首先要通过自定义类加载器加载,判断如果加载过则跳出,如果没有加载则通过父类加载,向上委派,递归加载直到找到启动类加载器。如果启动类加载加载不了,在向下委派,由子类加载器加载。

打破双亲委派机制:tomcat

一个tomcat 可以部署多个项目,加入项目1 用的时spring4 ,项目2 用的时spring5 。先加载项目1 ,再加载项目2的时候开始加载spring5 发现相同包下相同的类都已经被加载了,就不会再去加载了,但是实际上spring5 没有被加载,加载的时spring4 就会出现问题,所以tomcat 通过自定义类加载器的方式去打破双亲委派,每个项目自己加载自己的

Tomcat破坏双亲委派原则,提供隔离机制,为每个web容器单独提供一个WebAppClassLoader加载器。优先加载 Web 应用自己定义的类,没有遵照双亲委派的约定,
每一个应用有自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。
  

JVM 内存模型

JVM 虚拟机主要分成5部分:

java 堆(线程共享):主要存储java对象的,堆分年轻代(1/3),老年代(2/3),年轻代又分Eden(8/10),S0(1/10),S1(1/10)。垃圾回收也主要是回收堆区的。如果频繁FGC 会导致系统卡顿,这时候需要进行排查了。整个垃圾回收的时候再说。(jdk1.8 字符串常量池也放到堆中)

方法区(线程共享):常量池,静态变量,方法信息(修饰符、方法名、返回值、参数等),类元信息

虚拟机栈(线程独有):主要存储局部变量,生命周期和线程同步,线程结束,栈内存就释放了。不存在垃圾回收问题。虚拟机栈储存的是:8大基本类型 + 对象引用 + 实例方法。线程栈又存在多个栈帧,一个请求过来可能调用多个方法,每个方法就是一个栈帧,每个栈帧存储自己的局部变量。

本地方法栈:本地接口库里调用的方法,就是java里面native关键字修饰的方法。

程序计数器:多线程的情况下,会交替获取CPU 时间片执行,当t1 线程执行一半,t2线程获取CPU时间片执行,当t1 再次执行的时候要知道上一次执行到哪里了。每个线程启动是都会创建一个程序计数器,保存的是正在执行的jvm指令,程序计数器总是指向下一条将被执行指令的地址。生命周期与线程的生命周期保持一致。

面试点:

1 java 内存模型那些区域会报错OutOfMemoryError(OOM)

堆,栈,方法区都会报OOM ,程序计数器保存jvm 指令是再栈中,没有单独空间所以不会报OOM。

此处主要考 栈为什么也会报OOM 

OOM 主要是jvm 虚拟机内存满了,没有可申请的空间了,所以会报OOM 。

JVM 内存主要就是,堆内存,方法区内存,栈内存(虽然栈内存很小,且执行完马上释放,但是多线程同时请求,申请不到栈所需要内存大小了,就会报OOM 。)

StackOverFlowError 只线程栈嵌套太深,或者死循环,栈里局部变量占用内存大小超过申请内存空间大小–Xss配置。会报StackOverFlowError。

2 内存泄漏,内存溢出区别

内存泄漏:是指程序在运行过程中分配的内存空间无法被正常释放,‌导致内存的使用量不断增加,‌最终可能耗尽可用的内存

内存溢出:则是指程序在申请内存时无法获得所需的内存空间,‌导致程序运行停止并抛出“内存溢出”的错误

内存泄漏会导致内存溢出。

3 常用jvm 参数

-Xmx:JVM最大内存

-Xms:启动初始内存

-Xmn:新生代大小

-Xss : 每个线程虚拟机栈及堆栈的大小 

-XX:+HeapDumpOnOutOfMemoryError 表示打开开关:VM 出现 OOM 时 Dump 当前内存堆转储快照 。可通过工具(VisualVM  ,MAT)进行分析找出那些对象多,进行优化

==========================华丽丽的分割线================================

JVM GC :主要分三部分

可回收的对象:

可达性分析:通过GCRoot 的对象作为起点,从这些节点向下搜索,找到的对象为非垃圾对象,其余的为垃圾对象。

GCRoot: 线程栈的本地变量、静态变量、本地方法栈的变量

java 引用类型:强引用,软引用,弱引用,虚引用

强引用:强引用的对象回收基于“可达性分析”算法,当对象不可达时才可能会被回收。就算jvm内存满了,会抛出OutOfMemoryErro,也不会回收强引用

软引用:软引用是说一些有用但是非必需的对象。在jvm内存即将满的时候,会将软引用关联的对象,进行回收,如果回收之后,内存还是不够,才会抛出OutOfMemoryError。

弱引用:无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。

虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,随时可能会被回收

垃圾回收器:

serial 单线程 :年轻代 老年代,可做CMS 备选方案,垃圾回收时,应用线程停止

parNew 多线程 :年轻代 跟CMS 搭配使用

Parallel :内存大于2G cpu 大于2核 默认jvm 垃圾回收器 提高吞吐量 高效利用CPU 年轻代 复制算法,老年代 标记整理清除 可以设置 停顿时间,但是可能不能完全回收

CMS 减少卡顿时间 只能老年代(标记清除是对未标记的进行清除 老年代回收是默认到达百分之92 就回收,可以设置90 )

G1 大容量内存 用G1 满足吞吐量,及卡顿时间 年轻代老年代都可以G1

还有一些新出的垃圾回收器,暂时没有研究,后续再补充....

垃圾回收算法:

复制算法:主要用于年轻代。

标记清除算法:老年代,老年代都是存活比较长的,只要进入老年大大多数都是不会垃圾回收的,通过可达性分析,标记除不可回收的对象,其他的都是可以回收的对象

标记整理算法:标记清除算法,由于标记的对象中间可能存在很多内存需要回收,但是又不是连续的(内存碎片),导致导入如果出现大对象,老年代放不下。标记整理清除,就是标记后进行内存空间整理,再删除。

标记顺序:

初始标记:标记根节点。STW  用户线程不可用,但是标记时间短

并发标记:根据初始标记的根节点标记其引用的节点,此时非STW ,用户线程可用,标记时间长

重新标记:标记并发标记时产生的对象。STW 。时间短

并发清理:删除无用对象

YGC :当服务器接收请求,会产生对象,放到年轻代Eden 区,当Eden 区满了,会已经一次YGC 。把还存活的对象复制到s0区。第一次YGC 完成。当再有请求进来Eden满了,会去s0 申请空间,当s0也满了会进行第二次YGC,把Eden,S0 存活对象放到 S1 区里。

因为大多数java 对象程序执行完都会变成不可达对象,只有正在执行的线程引用的对象才是不可回收的对象,所以只有少部分存活对象。垃圾回收只需要把存活的对象复制到s 区即可,然后可以整体清除对象。

FGC: 多次YGC 没有回收掉的对象会转移到老年代。当老年代满了会触发FGC。

到达老年代的条件: 

当对象年龄超过15 没有被年轻代回收的。可以通过参数 -XX:MaxTenuringThreshold 来设置

大对象可直接进入老年代。jvm 可以配置多大对象  -XX:PretenureSizeThreshold=1m:大对象直接分配在老年代,这个参数只对 Serial 和ParNew两个收集器有效

动态年龄判断:当年轻代垃圾回收时,Survivor 一批对象大于该区域总大小的百分之50,年龄1 年龄2 年龄3 。。。年龄6 大于survivor 百分之50 则把 年龄6 包括年龄6 以后的对象会受到老年代,一般是在minor gc 之后。

老年代分配担保机制:在执行YGC之前,先回去看老年代剩余空间大小是否小于年轻代对象大小,如果是就先进行老年代回收,但是如果配置“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了。如果配置了 jvm 会再次检查一下 老年代剩余大小是否 小于以前年轻代回收的平均大小,如果小于,则进行老年代回收。

调优命令

jps  : 查看存活的进程 jps -l 查看全名

jinfo: 查看jvm 配置信息

jstat: 对java应用程序的资源和性能进行实时的命令行监控 。 jstat -gc 3776  查看进程的gc 信息

jstack 查看jvm 线程 它用于打印出给定的java进程ID、core file、远程调试服务的Java堆栈信息。

jmap 是 JDK 自带的一个命令行工具,可以用于生成 Java Heap Dump 文件,以及查看 Java 进程中的内存使用情况。jmap -histo 3776     jmap -heap 3776

jvm 调优主要就是减少进程的FGC ,频繁FGC 系统会容易卡顿。

CPU 标高,通过top 命令找到进程,找对应的线程 top -p pid -H,通过jstack 命令 查看堆栈日志根据线程id 找到对应的代码。

-XX:+HeapDumpOnOutOfMemoryError :内存溢出通过该参数打印错误日志。通过通过工具分析。

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值