八股文——JVM

java从编译到执行过程

知乎
编译 -> 运行(加载->链接->初始化->解释->执行)

  1. 编译: 将源码文件编程成jvm可识别的class文件

    编译过程会对源代码做 [语法分析]、[语义分析]、[注解 处理]等操作

  2. 运行: 将编译后的class文件加载到jvm中

    运行又分成 加载 - > 连接 -> 初始化
    装载: 查找并加载类的二进制数据,在jvm的堆中创建class对象,并将类相关的信息存储到jvm方法区中。
    [装载时机]:为了节省内存的开销,并不会一次性将所有的类都装载到jvm中,而是等到有需要的时候才装载。(比如 new 和反射等)
    [装载发生]: 双亲委派机制
    连接: 对class的信息进行验证,为类变量分配内存空间并对其赋默认值
    初始化 给变量赋初始值

  3. 解释:把字节码转换为机器可识别的二进制码

  4. 执行:操作系统将解释器得到的二进制码,调用系统的硬件执行最终的程序指令。

如何打破双亲委派机制?

jvm体系结构

在这里插入图片描述

jvm的内存模型

链接
在这里插入图片描述

方法区(线程共享):被所有方法线程共享的一块内存区域。用于存储已经被虚拟机加载的类信息,常量,静态变量等。
堆(线程共享):被所有线程共享的一块内存区域,在虚拟机启动的时候创建,用于存放对象实例(自己创建的对象等,基本变量存在栈区),jvm进行垃圾回收的区域。对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)。当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
栈(线程私有):每个方法在执行的时候也会创建一个栈帧,存储了局部基本变量(如int、char、long等),操作数,动态链接,方法返回地址。 每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。 JVM栈中存放的为当前线程中局部基本类型的变量,部分返回结果以及函数的参数值,非基本类型的对象的JVM栈上仅存放一个指向堆上的地址,获取非基本类型对象需要去堆中寻找。 局部变量所需内存在编译期间完成分配, 如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
程序计数器(线程私有):计数器记录的是虚拟机字节码指令的地址(当前指令的地址),每条线程都有一个独立的程序计数器。
本地方法栈(线程私有):主要为虚拟机使用到的Native方法服务

String类型存在哪里

new String 创建出来的存在堆区

 String a = new String("123");
 String b = new String("123");
 System.out.println(a == b);
false        

String = “abc”
存在方法区,且指向相同的地址

String a = "123";
String b = "123";
System.out.println(a == b);
true

堆里面存放什么

所有的对象实例以及数组都在堆上进行分配。jdk1.7以后,字符串常量从永久代中剥离出来,存放在堆中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

新生区(伊甸园区、幸存区 (幸存1区、幸存2区))
养老区 old
永久区 perm

新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成。

Minor GC(轻gc) : 清理年轻代
Full GC: 清理老年代。GC期间会停止所有线程等待GC完成。出现了了Full GC, 经常会伴随至少一次的Minor GC。

列举几种你知道的垃圾回收机制

链接
标记清除算法
在这里插入图片描述
找到GC roots根来遍历将非垃圾对象进行标记。
优点:清除速度快,效率高。
缺点:会产生大量的内存碎片(就是很多不连续的内存空间)
注意:jvm并不是真正的把垃圾对象进行了遍历,把内部的数据都删除了,不是这样的,而是把垃圾对象的首地址和尾地址进行了保存,等到再次分配内存时,直接去地址列表中分配,所以清除的效率高。

标记整理算法
在这里插入图片描述
步骤:
1、先找到GC roots根来遍历将非垃圾对象进行标记。
2、它会将垃圾进行清除,并且会将非垃圾对象进行向前移动,使得内存紧凑。

优点:不会出现内存碎片,提高了内存的利用率
缺点:清除速度慢,因为在整理期间会有对象的拷贝和移动,并且引用内存的对象的地址要进行改变。

复制算法
在这里插入图片描述
步骤:
1、先找到GC roots根来遍历将非垃圾对象进行标记。
2、然后将from区的非垃圾对象进行复制,到to区,并且进行整理,整理完成之后会将from区的数据进行清空,然后交换from和to区,使得from区一直是存储数据,to区一直空白的。
优点:也不会出现内存碎片
缺点:有效内存只有一半,只有一半存储数据,太浪费空间

垃圾回收的过程

放入对象时,对象先会来到伊甸园区,如果伊甸园的剩余内存大小可以放下,就直接放到伊甸园区,如果伊甸园内存不够,就会进行minor GC 将伊甸园区的对象进行回收,并且将存活的对象放置from区,将分代年龄进行加1。新生代用的是复制算法。minor GC 回收时,会将伊甸园区的垃圾和幸存区的垃圾都会进行回收。
如果对象来到伊甸园区,对象放不下,又到幸存区也放不下,就会进入老年代,然后放入老年代,一直这样,直到老年代放不下的时候**,先会进行一次minor gc ,如果进行完之后,还是内存不够,就会进行full GC** ,full GC 会将新生代和老年代都会进行回收,并且会使其他的进程全部停止,称为stop the word(STW),就是平常的卡顿,如果进行full GC 之后,内存还是不够的时候,就会抛出异常OOM。老年代的算法是标记清除或者标记整理算法。

什么情况下回进入老年代?

1、对象的分代年龄到了15岁(默认情况下,可以自己设置)。
2、大对象直接进入老年代,就是大对象在新生代放不下,进行minor GC之后还是不够的情况下,会直接放入老年代。

有什么方法来避免Full GC

1、避免频繁创建销毁大对象(运用单例模式)
2、把新生代空间调大

出现oom怎么办?

尝试扩大堆内存去查看内存结果

  1. -Xms1024m -Xmx1024m -XX:+PrintGCDetails
  2. 若不行,分析内存,看一下是哪个地方出现了问题(专业工具,能够看到代码第几行出错:内存快照分析工具,MAT(eclipse),Jprofiler
  3. Dubug,一行行分析代码!(不现实)

java的运行过程

链接
先编译成class文件,生成二进制码,然后加载代码,加载到方法区,运行入栈,建堆

谈一下Java类加载机制,为什么需要双亲委派机制?

链接
jvm加载class文件过程:
在这里插入图片描述
1、虚拟机自带的加载器
2、启动类(根)加载器【BOOT】。它是ExtClassLoad的父类加载器,默认加载%JAVA_HOME%/lib下的jar包和class包
3、扩展类加载器【EXT】。是APPClassLoader的父类加载器,加载/lib/ext下的文件。
4、应用程序加载器【APP】。下载classpath下的类文件

从4到1进行加载(注:是4委托3、2、1进行加载,因此4其实是最后加载运行的)

双亲委派机制(安全):APP—>EXT—>BOOT【最终执行

APP应用程序加载器委托扩展类加载器加载,扩展类加载器委托根加载器加载,层层委托,因此交双亲委派机制。

好处:

  1. 防止类重复加载;
  2. 使得类的加载出现优先级,防止了核心API被篡改,提升了安全,所以越基础的类就会越上层进行加载,反而一般自己的写的类,就会在应用程序加载器(Application)直接加载。

栈、堆、方法去的关系

在这里插入图片描述
每个方法是在栈区执行的,方法栈中存放的为当前线程中局部基本类型的变量,部分返回结果以及函数的参数值,非基本类型的对象的JVM栈上仅存放一个指向堆上的,真正保存的对象值是在堆区,栈要使用该对象时要去堆上获取实际对象。堆区会引用方法区的常量池是常量。

栈区图

在这里插入图片描述

串行GC和并行GC

链接
垃圾回收器组合

在这里插入图片描述
垃圾回收器从线程运行情况分类有三种:

  1. 串行回收,Serial回收器,单线程回收,全程stw;
  2. 并行回收,名称以Parallel开头的回收器,多线程回收,全程stw;
  3. 并发回收,cms与G1,多线程分阶段回收,只有某阶段会stw;

Serial收集器:是一个单线程年轻代收集器,当它运行进行垃圾回收时,其它工作线程必须被停止,直到它收集完成。serial依然是JVM client模式下的默认收集器,对于client模式是一个很好的选择。

parNew收集器:它也是一个年轻代收集器,parNew收集器是serial收集器的多线程模式,除了使用多个线程进行垃圾回收外,其余与serial基本完全相同,preNew是jvm server模式下的默认收集器。

cms收集器:它是一个并行老年代收集器,缩短stw的时间,使用标记清除算法,而且比较消耗cpu性能,适合服务器性能较好,需要高吞吐量的情况,是java 1.8默认老年代收集器

G1收集器: 它是一个并行老年代收集器,使用标记整理算法,不会产生内存碎片,效率也比cms收集器高,建立可预测的停顿时间模型(实现方法:调整新生代和堆空间的大小控制stw的时间),能让使用者明确指定在一个长度为M毫秒的时间片段内。

cms回收器:

链接
链接
CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。
在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。

CMS过程

  1. 初始标记(CMS-initial-mark) ,会导致swt;
  2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;
  3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
  4. 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行;
  5. 重新标记(CMS-remark) ,会导致swt;
  6. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
    在这里插入图片描述
    是时候使用cms
    如果你的应用程序对停顿比较敏感,并且在应用程序运行的时候可以提供更大的内存和更多的CPU(也就是硬件牛逼),那么使用CMS来收集会给你带来好处。还有,如果在JVM中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS。
  • 在jvm中cms只会回收老年代和永久带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻带;
  • cms是一种预处理垃圾回收器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以cms垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%;
  • CMS并发GC不是“full GC”。HotSpot VM里对concurrent collection和full collection有明确的区分。所有带有“FullCollection”字样的VM参数都是跟真正的full GC相关,而跟CMS并发GC无关的,cms收集算法只是清理老年代。

cms的缺点:

  1. CMS回收器采用的基础算法是Mark-Sweep。所有CMS不会整理、压缩堆空间。这样就会有一个问题:经过CMS收集的堆会产生空间碎片。
    CMS不对堆空间整理压缩节约了垃圾回收的停顿时间,但也带来的堆空间的浪费。为了解决堆空间浪费问题,CMS回收器不再采用简单的指针指向一块可用堆空
    间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当JVM分配对象空间的时候,会搜索这个列表找到足够大的空间来hold住这个对象。
  2. 需要更多的CPU资源。从上面的图可以看到,为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU,单纯靠线程切
    换是不靠谱的。并且,重新标记阶段,为空保证STW快速完成,也要用到更多的甚至所有的CPU资源。当然,多核多CPU也是未来的趋势!
  3. CMS的另一个缺点是它需要更大的堆空间。因为CMS标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在CMS回
    收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已
    避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用68%的时候,CMS就开始行动了。 –
    XX:CMSInitiatingOccupancyFraction =n 来设置这个阀值。

总得来说,CMS回收器减少了回收的停顿时间,但是降低了堆空间的利用率。

jmm模型

JMM:Java Memory Model的缩写
作用:缓存一致性协议,用于定义数据读写的规则。
JMM定义了线程工作内存和主内存之间的抽象关系,线程之间的共享内存存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
在这里插入图片描述
解决共享对象可见性这个问题:voliate

什么是字节码?字节码的好处是什么?

java源代码—>编译器—>jvm可执行的字节码(.class文件)—>jvm—>jvm中的解释器—>机器可执行的二进制机器码—>运行程序

好处:通过字节码的方式,在一定程度 上解决了传统解释型语言的执行效率低的问题。(其他语言每次执行都需要重新编译,而java是在打包的时候就编译好了,运行的时候不许要编译),而且java的字节码(.class)文件跨平台,移植到其他平台也不需重新编译。

gc如何判断对象可以回收?

可达性分析法:从GC roots开始向下搜索,搜索走过的路径作为引用链。若gc roots到达不了的对象都是不可用的。那么虚拟机判断该对象可回收。

GC roots的对象有:

  • 虚拟机栈中引用的对象
  • 方法去中的静态对象和常量
  • 本地方法栈中的JNI(native方法)引用的对象

可达性分析算法的不可达对象并不是立刻死亡的,对象拥有一次自我拯救的机会。对象宣告死亡至少会经历两次标记过程,第一次是gc roots是可达性分析,第二次是由虚拟机自动简历的Finalizer队列中判断时候需要执行finalize()方法(复活方法,若对象覆盖该方法,则有一次复活机会,gc roots会再次进行可达性分析,若没有覆盖则直接回收。不推荐重写该方法,因为该方法内存消耗很大)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值