jvm的基础知识点梳理

什么是双亲委派机制:

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器

加载顺序为:

1、Bootstrap Class Loader (加载rt.jar)

2、Bootstrap Class Loader (加载rt.jar)

3、App Class Loader(加载ClassPath)

4、Customer ClassLoader(通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat,jboss都会根据j2ee规范自行实现ClassLoader)

进行加载的时候会先委派根ClassLoader,然后Extension ClassLoader进行加载,如果没找到才使用系统加载器加载,这样做可以防止jdk的代码被使用者篡改

 

由双亲委派机制可以得到一个结论:Object以及java.lang包下的类能直接用的原因:

jvm启动时根ClassLoader已经加载rt.jar下的类,java.lang包在rt.jar中,所以可以直接使用

 

jvm整体架构图:

方法区:

方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法(如构造函数、接口代码)在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区域

静态变量+常量+类信息(构造方法/接口定义)+运行时常量池存在此方法区中

栈:

栈也叫栈内存,主管java程序的运行,是在创建线程的时候创建的,它的生命周期是跟随线程的生命周期,线程结束时栈内存也就释放,对于栈来说不存垃圾回收的问题,只要线程一结束就释放,是线程私有的,8中基本类型的变量、对象的引用变量、实例方法都是在函数的栈内存中分配

栈中每个元素成为栈帧,栈帧代表方法的执行

栈帧代表的是方法的执行,里面存放局部变量表、操作数栈、动态链接、方法返回地址

heap(堆):

一个jvm实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需要把类,方法,常变量放到堆内存中,保存所有引用类型的真实信息,堆存储对象和数组,堆内存分为三部分:

  • 新生去
  • 老年区
  • 永久区

本地方法栈:

本地方法(C/C++)的栈

如果在java虚拟机栈中用到了native方法(本地方法),会在本地方法栈进行压栈,同时在java虚拟机栈中调用本地方法栈的方法会动态链接到本地方法栈中的相应本地方法,此时这个虚拟机栈和本地方法栈都在线程中,即该线程拥有本地方法栈和java虚拟机栈两个栈

程序计数器:

由于线程之间会竞争cpu资源,程序计数器是用来记录程序正在执行的地址

动态链接:

把类的符号引用转变为直引用 (如在方法中的Student s = new Student(),只有在运行时才会调用)

方法区:

jdk1.7之前成为PermSpace(永久代),jdk1.8叫做MetaSpace(元空间)

方法区在逻辑上是堆的一部分,但是通常上成为非堆

 

垃圾收集机制:

  • 整体分为老年代和新生代,新生代又分为eden区,两个S区
  • 一个对象如果经过15次GC还没有被回收,就会去老年代(15次可以更改,为默认值)
  • 一个对象如果创建,优先分配到新生代
  • 如果一个对象的大小超过了新生代的一半,那么会会直接分配到老年代
  • 如果新生代不够用会发生一次GC
  • 新生代空间不连续,会行程空间碎片,导致对象放不下,也会触发一次GC,因此新生代会划分为Eden,S0,S1,对象现在Eden区进行分配,如果Eden区不够用,会将存活的对象分配到一个S区,两个S区,一定是一个为空,另一个正在被使用,复制完后,直接清空Eden区所有存活对象,Eden区的清空实质上是整个新生代的GC,两个S区也会触发GC,其中一个S区会将存活对象复制到另一个S区,从而保证总是有一个S区为空
  • 新生代(young区)的Gc称为 Young GC/Minor GC,Old区的GC称为Old GC/Major GC
  • Eden区存活的对象,如果在S区放不下,并且年龄没有到去老年区的大小,会通过担保机制,将对象直接存到Old区
  • Eden区,S0区,S1区的空间大小比默认为8:1:1,也就是说新生代默认有10%的空间浪费
  • 老年代的GC:由于老年代空间较大,扫描会耗费更大的线程资源和CPU资源
  • Major GC通常会伴随着Minor GC

 

不只是堆,运行时数据区每个区都会出现内存不足的问题

如方法区内存不足够也会抛出oom:java.lang.OutOdMemoryError:Metaspace

设置方法区空间大小:

-XX:MetaspaceSize=30M -XX:MaxMetaspaceSize=30M

如果虚拟机栈深度不够,也会发生内存溢出:java.lang.StackOverflowError

调整虚拟机栈大小:-Xss128k

栈的深度太小:意味着方法链比较短,很快会溢出

栈的深度太大:栈的深度越大,会更多的消耗cpu资源,影响线程创建的数量,另外线程也会占用内存空间,造成oom溢出

java垃圾回收机制: 可达性分析

GC Root:根据gc root可以直接或间接到达的实例就不是垃圾,反之就是垃圾

GC Root可以是局部变量表,static成员、常量方法区、本地方法栈中的变量、类加载器、Thread

 

回收算法:

标记-清除:缺点:耗时,会产生空间碎片

复制算法(标记-复制-清除):young区算法,解决空间碎片问题,但是会造成空间浪费,存活对象比较少(朝生夕死)适合该算法

标记-整理:会把存活的对象整理成连续的一段,解决空间碎片,老年代适用于标记清除或者标记整理算法

jvm 不同的垃圾收集器实现了不同的垃圾回收算法:


目前还没有任何收集器能在不停止用户程序的情况下实现垃圾回收,最好的java11中的zgc停顿时间在10ms左右

jdk1.3之前为Serial,适用于新生代,是对复制算法的实现,单线程收集

SerialOld:适用于老年代,对于标记整理算法的实现

ParNew:和Serial类似,但是是多线程

Parallel Scanvenge:复制算法的实现,吞吐量更好

Parallel Old:适用于老年代的版本,实现标记整理算法

CMS:Concurrent Mark Sweep,并发类的收集器,实现了标记-清除算法

优缺点:并发收集,低停顿(更加关注停顿时间的收集器),但是会产生空间碎片,内存不连续,只能适用于老年代

G1:筛选回收:可以根据开发者的设置,作出一个停顿时间的期望值,这样做的话会有一些空间没有办法完全回收,这里的机制如果该回收机制的名称(Garbage First),优先回收垃圾对象多的区域,这样可以把更多的空间释放出来

 

针对G1GC调优指南:

1、避免设置young区和young区old区比例大小

2、停顿时间不要设置的太严格,否则会增加gc次数

3、调整G1并发收集百分比:InitiatingHeapOccupancyPercent,默认值45

 

一些简单问答:

名词解释(内存泄漏和内存溢出)

内存泄漏:不再使用的对象无法得到及时回收,持续占用内存空间,造成空间的浪费

内存溢出:内训泄露容易导致内存溢出,内存溢出不一定是内存泄漏导致的,也有可能是大对象等等

 

被GC Root判定为不可达,是都一定会被回收?

不一定,有可能因为finalize()重新复活变为有用的对象

finalize():当一个对象被虚拟机宣告死亡时会先调用它finalize()方法,让此对象处理它生前的最后事情,这个对象可以趁这个时机挣脱死亡的命运

如何自我救赎:

1.对象覆写了finalize()方法(这样在被判死后才会调用此方法,才有机会做最后的救赎);

2.在finalize()方法中重新引用到"GC Roots"链上(如把当前对象的引用this赋值给某对象的类变量/成员变量,重新建立可达的引用)

注意:

finalize()只会在对象内存回收前被调用一次

finalize()的调用具有不确定行,只保证方法会调用,但不保证方法里的任务会被执行完(比如一个对象手脚不够利索,磨磨叽叽,还在自救的过程中,被杀死回收了)

 

方法区中的类是否会被回收?

会:

  • 该类中的所有实例都已经被回收
  • 该类的ClassLoader已经被回收
  • java.lang.Class对象没有任何地方使用

 

cms和g1的区别:

cms只能适用于老年代,g1将堆内存分割为一个个region,停顿时间可控

cms使用标记清除算法,会产生空间碎片,g1较少产生碎片的可能

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章