JVM——内存模型、类加载机制、垃圾回收机制

1、jvm由哪几部分组成,存放哪些数据

方法区、堆、虚拟机栈、本地方法栈、程序计数器

方法区:存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据

堆:对象实例、数组

虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

本地方法栈:与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

程序计数器(PC寄存器):指示Java虚拟机下一条需要执行的字节码指令

2、栈之间怎么实现数据共享

帖子:起初,我一直不清楚到底是堆共享还是栈共享,后来查阅了很多资料,有说是堆共享,又有说栈共享,直到最后我才了解到共享分为两个:一个为数据共享,一个为线程共享。

先来分析数据共享:

int a = 3; 

int b = 3; 

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况,即共享了3这个栈数据。

 

对于String类型来说,编译期已经创建好的(String a="123" ,双引号定义的内容)存在常量池里面,如果是运行期则存在堆中(String b=new String("123"),new 出来的对象),而等号的左边为引用对象。

对于栈和常量池中的数据可以共享,即可以有多个引用对象;而对于堆来说,数据不可以共享,只能有一个引用对象。

再来分析线程共享:

所有线程共享堆,但每个线程都有自己的寄存器和自己的栈。

所有线程占有的都是不共享的:栈、寄存器、PC。

线程共享的有:堆、全局变量、静态变量、方法区。

3、栈常见的有哪些异常,为什么

StackOverflowError代表的是,当栈深度超过虚拟机分配给线程的栈大小时就会出现此error

OutofMemoryError代表的是,当再申请新的内存时,虚拟机分配给线程的内存大小中无法再分配新的内存,就会出现此error

 

 

4、类加载机制有哪几种   

类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束。过程共有七个阶段,其中到初始化之前的都是属于类加载的部分

加载----验证----准备----解析-----初始化----使用-----卸载

系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类,当运行某个java程序时,会启动一个java虚拟机进程,两次运行的java程序处于两个不同的JVM进程中,两个jvm之间并不会共享数据。

JVM三种预定义类加载器

    JVM预定义有三种类加载器,当一个 JVM启动的时候,Java缺省开始使用如下三种类加载器:

1)引导类加载器(bootstrap class loader:它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。它负责将<Java_Runtime_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

2)扩展类加载器(extensions class loader:该类加载器在此目录里面查找并加载 Java 类。扩展类加载器是由Sun                            ExtClassLoadersun.misc.Launcher$ExtClassLoader)实现的。它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dirs指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器

3)系统类加载器(system class loader:系统类加载器是由 Sun AppClassLoadersun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它

 

 

JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

http://hi.csdn.net/attachment/201009/25/0_1285421756PHyZ.gif

1Bootstrap ClassLoader

  负责加载$JAVA_HOMEjre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

2Extension ClassLoader

  负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOMEjre/lib/ext/*.jar-Djava.ext.dirs指定目录下的jar

3App ClassLoader

  负责加载classpath中指定的jar包及目录中class

4Custom ClassLoader

  属于应用程序根据自身需要自定义的ClassLoader,如tomcatjboss都会根据j2ee规范自行实现ClassLoader,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoaderBootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类在所有ClassLoader只加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

 

5、什么是类的双亲委派加载机制

 4.1 “双亲委派机制介绍

  在这里,需要着重说明的是,JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。关于虚拟机默认的双亲委派机制,我们可以从系统类加载器和标准扩展类加载器为例作简单分析。

系统类加载器的父加载器是标准扩展类加载器,但是我们试图获取标准扩展类加载器的父类加载器时确得到了null,就是说标准扩展类加载器本身强制设定父类加载器为null。如果父加载器为null,则会调用本地方法进行启动类加载尝试。

虚拟机出于安全等因素考虑,不会加载< Java_Runtime_Home >/lib存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。但可以放在/lib/ext下当成系统类进行加载。

双亲委派机制的作用:系统安全性,同时保证加载类的唯一性,提高效率

 

6、系统怎么判断一个对象是垃圾

引用计数算法:

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值就减1;计数器为0,说明对象没有被使用

但是主流的java虚拟机里面没有选用引用计数法来管理内存,其中最重要的原因是它很难解决对象之间互相循环引用的问题(objA和objB都有字段instance,赋值令objA.instance=ObjB和objB.instance=objA,除此之外,两者再无任何引用,但是因为它们互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们)

 

可达性分析算法:

通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的

 

GC Root

常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

 

什么对象可以作为GC Roots?

Thread - 活着的线程

Stack Local - Java方法的local变量或参数

JNI Local - JNI方法的local变量或参数

JNI Global - 全局JNI引用

Monitor Used - 用于同步的监控对象

另一种说法:

Java语言里,可作为GC Roots对象的包括如下几种: 
a.虚拟机栈(栈桢中的本地变量表)中的引用的对象 
b.方法区中的类静态属性引用的对象 
c.方法区中的常量引用的对象 
d.本地方法栈中JNI的引用的对象

 

7、复制算法的使用及其缺点

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。
Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:EdenFrom SurvivorTo Survivor
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
堆的内存模型大致为:

 

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GCFull GC ( 或称为 Major GC )
Minor GC 发生在新生代中的垃圾收集动作,所采用的是复制算法

8、老生代使用什么算法

Full GC 发生在老年代的垃圾收集动作,所采用的是标记-清除算法

 

9、有没有遇到线上jvm问题,怎么处理的?

 CPU 飚高,内存溢出,频繁 GC

CPU 飚高问题排查思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程。然后找到那个进程中的问题线程,最后根据线程堆栈信息找到问题代码。最后对代码进行排查。

内存问题有2种情况,一种是内存溢出了,一种是内存没有溢出,但 GC 不健康。

内存溢出的情况可以通过加上 -XX:+HeapDumpOnOutOfMemoryError 参数,该参数作用是:在程序内存溢出时输出 dump 文件。

YGC 5秒一次左右,每次不超过50毫秒,FGC 最好没有,CMS GC 一天一次左右。GC 的优化有2个维度,一是频率,二是时长. JDK 提供了很多的工具,比如 jmap ,jcmd 等,oracle 官方推荐使用 jcmd 代替 jmap,因为 jcmd 确实能代替 jmap 很多功能。jmap 可以打印对象的分布信息,可以 dump 文件,注意,jmap 和 jcmd dump 文件的时候会触发 FGC ,使用的时候注意场景。

还有一个比较常用的工具是 jstat,该工具可以查看GC 的详细信息,比如eden ,from,to,old 等区域的内存使用情况。

还有一个工具是 jinfo,该工具可以查看当前 jvm 使用了哪些参数,并且也可以在不停机的情况下修改参数。

 

 

参考:https://blog.csdn.net/qq_39159227/article/details/88775238

猜你喜欢

转载自blog.csdn.net/qq_39159227/article/details/89035965