jvm知识梳理

1.java虚拟机的组成

java虚拟机主要由四部分组成

(1)ClassLoader:按特定格式加载class文件到内存中

(2)runtime data area:jvm内存空间模型

(3)execution engine:命令解析器

(4)native interface:融合不同开发语言的原生库供java使用

2.类加载过程

(1)编译器把.java文件编译成.class文件

(2)ClassLoader将.class文件加载到内存中,生成class<T>对象

(3)jvm根据class<T>生成对应的实体类对象

3.谈谈ClassLoader

ClassLoader负责将系统外部的class二进制数据流加载到jvm中,再由jvm进行连接/初始化。ClassLoader是一个抽象类,通过loadClass()方法加载类,ClassLoader的一般实现类有四个,即

(1)BootStrapClassLoader:由C++编写的基础类加载器,用于加载java.*下的核心类,加载路径是jre\lib\rt.jar或者系统变量Xbootclasspath下的类。源码不可见

(2)ExtClassLoader:由java编写的用于加载java扩展类javax.*下的类,加载路径是jre\ext\*.jar或系统变量Djava.ext.dirs下的类。 可以查看源码

(3)AppClassLoader:由java编写,用于加载工程目录下的类,加载路径是CLASSPATH或系统变量Djava.class.path下的类。 可以查看源码

(4)自定义ClassLoader:用户自己编写的(需继承ClassLoader),定制加载

4.双亲委派机制

在加载一个类时,步骤如下:

先判断此类是否已被加载,已加载则直接返回,未加载则从下至上继续判断

(1)判断是否已被自定义ClassLoader加载

(2)若未被自定义ClassLoader加载,则判断是否被AppClassLoader加载

(3)若未被AppClassLoader加载,则判断是否被ExtClassLoader加载

(4)若未被ExtClassLoader加载,则判断是否被BootStrapClassLoader加载

若全部加载器判断完,均未加载此类则由BootStrapClassLoader开始从上至下尝试加载

(1)尝试通过BootStrapClassLoader加载

(2)若未能被BootStrapClassLoader加载,则尝试通过ExtClassLoader加载

(3)若未能被ExtClassLoader加载,则尝试通过AppClassLoader加载

(4)若未能被AppClassLoader加载,则尝试通过ClassLoader加载

(5)若所有加载器都未能成功加载,抛出classNotFound异常

5.loadClass和forName区别

先说下类装载和加载的基础知识,类的加载分为隐式加载(new)和显式加载(loadClass和forName),java类的装载过程分为三个阶段。

(1)加载:通过ClasLoader加载class字节码文件,生成class对象

(2)连接:连接分为三个子步骤1校验,校验class文件的正确性和安全性。2准备,为类变量分配存储空间并设置初始值(注意:这里说的类变量是static类型变量,初始值是默认值,比如变量static int a = 111,在此步骤只给赋值0,因为int默认为0,111是在下一步赋值的)。3解析,将jvm常量池中的符号引用转为直接引用。

(3)初始化:进行类变量的复赋值和执行静态代码块

loadClass方法只是实现了上诉第一步,即加载

forName方法实现了全部三个步骤

6.JVM内存模型(jdk8)

由线程私有的:本地方法栈,虚拟机栈,程序计数器

线程公有的:元空间,堆

5部分组成,如图

(1)程序计数器:标识字节码文件执行行号,告诉机器执行哪行代码

(2)虚拟机栈:是java方法执行的内存模型,程序运行时每执行一个方法内存就会分配一定空间作为栈帧(Stack Frame是用于支持虚拟机进行方法调用和方法执行的数据结构),这些栈帧存放在虚拟机栈中,方法执行结束会自动按后进先出的策略释放这些栈帧,因此无需回收。栈帧由局部变量表和操作数栈组成,局部表量表存放方法中的各种局部变量,操作数占是描述方法执行中变量的计算/赋值过程

(3)本地方法栈:与虚拟机栈类似,只不过是针对native方法的栈

(4)元空间:存储class相关信息(class的方法,属性信息等)jdk7及以前,这部分属于永久代,元空间和永久代的本质区别是元空间使用本地内存,永久代使用jvm内存,使用元空间替代永久代有以下如下好处:消灭了outOfMerroryError:permGen space这个异常,之前永久代放在堆中,增加了GC的复杂度,而且类和方法信息大小难以确定,不方便指定永久代大小,永久代设置过大容易造成老年代内存溢出,永久代设置过小容易造成永久代内存溢出。再一点就是orcale有意将hotspot虚拟机和其他JVM集成,但永久代是hotspot特有的,为了方便后续集成去掉永久代

(5)堆:对象实例空间和GC管理的空间

7.为什么递归容易导致stackoverflow

因为调用方法会创建栈针并压如虚拟机栈,递归会不断创建栈针增加栈的深度,而线程的虚拟机栈深度固定,超出就会导致栈溢出

8.JVM核心参数

-Xms:堆的初始值

-Xmx:堆的最大值,在堆内存不足时,会自动扩容至-Xmx的大小,在实际中通常把此值设跟-Xms一样大,因为java堆扩容会发生内存抖动,非常影响机器的稳定性

-Xss:线程虚拟机栈大小,通常256K就够了,此参数影响并发线程数的大

-XX:NewRatio:老年代和年轻代内存大小比例,通常为2,即老年代和年轻代大小比例2:1

9.程序运行时的内存分配策略

静态存储:编译时即能确定运行时的存储空间需求(对应方法区,存class对象,类的static变量)

栈式存储:编译时不能确定,但运行时通过程序入口可确定的存储需求(对应栈,存和方法中的基本类型变量和对象类型的引用)

堆式存储:编译和运行时程序入口都不能确定空间的存储需求(对应堆,存new出来的对象类型)

10.栈和堆的联系和区别

联系:访问对象/数组时,会在栈中创建一个引用变量指向堆中对象/数组,退出方法时引用变量销毁但堆中对象仍然存在,堆中对象若一定时间内没有被引用变量指向则会被回收

区别:1.栈比堆小 2.栈自动释放堆需要GC 3.栈产生的碎片小于堆 4.栈支持静态分配和动态分配,堆只支持动态分配 5.栈的效率比堆高

11.谈谈String的intern方法在jdk6和jdk6以后的区别

方法作用:返回字符串在常量池中地址并赋给调用该方法的对象。

jdk6实现:从字符串常量池中查找,若不存在则在常量池中创建并返回

jdk6以后实现:从字符串常量池中查找,若不存在则进入堆中查找,若堆中存在则在常量池中创建该对象的引用并返回,若堆中也不存则在常量池中穿件并返回

引申练习题:请问下面两段代码输出结果是啥?

String s1=new String("a")+new String("b");
s1.intern();
String s2 = "ab";
System.out.println(s1==s2);//jdk6:false jdk7/jdk8:true
------------------------------------------
String s3=new String("a")+new String("b");
String s4 = "ab";
s1.intern();
System.out.println(s3==s4);//false
------------------------------------------
String s5=new String("a");
s1.intern();
String s6 = "a";
System.out.println(s5==s6);//false

12.GC的标记算法

(1)引用计数法:原理是记录对象被引用的次数,被一个对象引用则计数+1,引用被回收则-1,归0后被回收。优点是计算简单效率高,缺点是无法计算互相引用等场景

(2)可达性分析算法:判断程序中对象的引用链,当对象顺着引用链可被查到时,则认为“可达”,否则将被回收

13.GC的回收算法

(1)标记-清除算法:标记可达的对象,在回收时回收未标记的对象,优点效率高,缺点容易产生内存碎片

(2)标记-整理算法:标记可达的对象,在回收时将可达对象移动到集中的内存区域再进行回收,有点是相比标记清除算法解决了内存碎片的问题,但效率不如标记清除算法

(3)复制算法:将内存分为相等的两块区域,将可达对象标记后复制到另一块区域,然后全量清除原区域空间,优点是避免内存碎片问题,缺点是需要冗余一份内存空间,可达对象越少,采用复制算法越高效,因为复制对象会消耗cpu

(4)分代收集算法:是上诉算法的组合拳,把JVM分为年轻代和老年代,年轻代采取复制算法,老年代采取标记清除/整理算法

年轻代:执行minor GC,分为Eden区和Survivor区(Eden区是大部分对象创建时所在区域,除非内存很大的对象会被直接创建到老年代,Survivor区又分为from区和to区,这三个区的默认大小比例是8:1:1可在jvm参数-XX:SurvivorRatio设置Eden区和Survivor的比例),采用复制算法,具体执行过程为

第一步:标记Eden区和from区中可达对象

第二步:将上诉两区可达对象复制到to区,并将标记“年龄”加1,年龄超过15则存入老年代(15是默认,可在jvm参数-XX:+PretenuerSizeThreshold中配置)。当to区装不下复制结果时,也会将一部分对象直接放到老年代

第三步:将Eden区和from区清空,将原from区标记为下一次执行GC的to区,原to区反之

老年代:存放生命周期较长的对象,执行Full GC和Major GC,采用标记清除/标记整理算法

14.java中强引用,软引用,弱引用,虚引用的区别和作用

软引用:主要用途是做内存缓存,内存不足时回收,缓解内存不足导致OOM的问题

弱引用:比软引用更弱些,用于标记些偶尔使用的对象,会跟随GC被回收

虚引用:最弱的引用,用于跟随对象被垃圾回收器回收,哨兵作用

15.jvm两种运行模式

(1)Servier:启动较慢,但稳定运行后速度快

(2)Client:启动较快,但运行稳定后速度慢

16.垃圾收集器

(1)年轻代垃圾收集器

Serial收集器:采用复制算法,单线程收集,通过-XX:+UseSerialGC设置,此收集器简单高效,注重缩短stop-the-world的时间,提升用户体验,是Client模式下默认的年轻代收集器

ParNew收集器:Serial收集器的多线程版本(通过-XX:+UseParNewGC设置),特点与Serial收集器相同,在单核环境下效率不如Serial收集器,与Serial收集器是唯二的可以跟cms收集器共同工作的年轻代收集器,默认线程数是系统核数,可通过jvm参数修改

Parallel Scavenge收集器:通过-XX:+UseParallelGC设置,同样是多线程的采用复制算法的收集器,该收集器更注重提高系统吞吐量,适合后台跑定时任务之类的应用,是Server模式下默认的年轻代收集器

(2)老年代垃圾收集器

Serial Old收集器:采用标记-整理算法,单线程收集,通过-XX:UseSerialOldGC设置,Client模式下默认的老年代收集器,可搭配任何年轻代收集器使用

Patralled Old收集器:采用标记-整理算法,多线程收集,吞吐量优先,搭配年轻代的Parallel Scavenge使用,通过-XX:+UseParalledOldGC设置

CMS收集器:采用标记-清除算法,通过-XX:+UseConcMarkSweepGC设置,是常用的老年代收集器。

G1(Garbage First)收集器:同时用于年轻代和老年代,特点是会将整个java堆内存划分成多个大小相等的Regin,年轻代和老年代不再物理隔离

G1有如下优势:

1.并发和并行:G1可以用多个CPU来缩短stop-the-world

2.分代收集:虽然年轻代和老年代不再物理隔离,但仍然独立处理新对象和存活已久的老对象

3.空间整合:因为基于标记-整理算法,解决了很多其他老年代收集器内存不连续问题

4.可预测的停顿:因为支持配置垃圾收集所占用时间在N毫秒内

17.对象从年轻代晋升到老年代的场景

(1)对象过大,直接创建到老年代

(2)可达性算法计数年龄达到阈值(默认15)

(3)动态对象年龄判定:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄

18.触发full GC的条件

(1)老年代空间不足

(2)永久代(如有)空间不足

(3)gc 担保失败,逻辑如下图

(4)程序中的Sysyem.gc()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值