JVM 相关知识点复习

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

JVM内存结构说一下

image.png 主要分为线程隔离区域和线程共享的区域 线程隔离区域有包括:

  • 虚拟机栈

    描述的是方法执行时的内存模型,是线程私有的,生命周期与线程相同, 每个方法被执行的同时会创建栈桢(下文会看到),主要保存执行方法时的 局部变量表、操作数栈、动态连接和方法返回地址等信息,方法执行时入栈,方 法执行完出栈,出栈就相当于清空了数据,入栈出栈的时机很明确,所以这块区 域不需要进行 GC

  • 本地方法栈

    与虚拟机栈功能非常类似,主要区别在于虚拟机栈为虚拟机执行Java 方法时服务,而本地方法栈为虚拟机执行本地方法时服务的。这块区域也不需要进行 GC

  • 程序计数器

    线程独有的, 可以把它看作是当前线程执行的字节码的行号指示器

线程共享区域:

  • 方法区

主要存储类的信息,常量,静态变量,即时编译器编译后代码等,这部分由于是在堆中实现的,受 GC 的管理,不过由于永久代有 -XX:MaxPermSize 的上限,所以如果动态生成类(将类信息放入永久代)或大量地执行 String.intern (将字段串放入永久代中的常量区),很容易造成 OOM

  • 堆区

    对象实例和数组都是在堆上分配的,

  • 本地内存(java8以后才有)

    线程共享区域,Java 8 中,本地内存,也是我们通常说的堆外内存,包含元空间和直接内存。

    在 Java 8 中就把方法区的实现移到了本地内存中的元空间中,这样方法区就不受 JVM 的控制了,也就不会进行 GC,也因此提升了性能。

  • 直接内存

    不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;

    • 如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;
    • 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

什么情况下内存栈溢出

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常,方法递归调用产生这种结果。
  • 如果JVM可以动态扩展,并且扩展动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建线程的时候没有足够的内存去创建对应的JVM Stack,那么JVM将抛出一个OutOfMemory异常(这是启动过多)

描述new 一个对象的流程

image.png

  1. 当虚拟机遇到一条new指令时候,首先去检查这个指令的参数是否能 在常量池中能否定位到一个类的符号引用(即类的带路径全名),并且检查这个符号引用代表的类是否已被加载、解析和初始化过,即验证是否是第一次使用该类。如果没有(不是第一次使用),那必须先执行相应的类加载过程(class.forname())。

  2. 在类加载检查通过后,接下来虚拟机将 为新生的对象分配内存。对象所需的内存的大小在类加载完成后便可以完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来,目前常用的有两种方式,根据使用的垃圾收集器的不同使用不同的分配机制。

  3. 内存分配完后,虚拟机需要将分配到的内存空间中的数据类型都 初始化为零值(不包括对象头)

  4. 虚拟机要对对象头进行必要的设置

    对象的内存布局:

image.png

  1. 调用对象的init()方法,根据传入的属性值给对象属性赋值

  2. 在线程栈中新建对象引用,并指向堆中刚刚新建的对象实例

java对象会不会分配在栈中

会。JVM提供了一种叫做栈上分配的概念,针对那些作用域不会逃逸出方法的对象,在分配内存时不在将对象分配在堆内存中,而是将对象属性打散后分配在栈(线程私有的,属于栈内存)上,这样,随着方法的调用结束,栈空间的回收就会随着将栈上分配的打散后的对象回收掉,不再给gc增加额外的无用负担,从而提升应用程序整体的性能

如果判断一个对象是否被回收,有哪些算法,实际虚拟机使用最多的是什么?

  • 引用计数算法

    就是对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为 0),则此对象可回收。缺点:相互引用的可能无法进行垃圾回收

  • 可达性算法(虚拟机用的是这种)

    是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下 个节点为起点,引出此节点指向的下一个结点。。。(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」,会被 GC 回收。

    以下对象可以作为GC_Root

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

GC收集算法有哪些?他们的特点是什么?

  • 标记清除算法

    先根据可达性算法标记出相应的可回收对象,对可回收的对象进行回收。 操作起来确实很简单,也不用做移动数据的操作,会存在内存碎片。

  • 复制算法

    把堆等分成两块区域, A 和 B,区域 A 负责分配对象,区域 B 不分配, 对区域 A 使用以上所说的标记法把存活的对象标记出来(下图有误无需清除),然后 把区域 A 中存活的对象都复制到区域 B(存活对象都依次紧邻排列)最后把 A 区对象全部清理掉释放出空间,这样就解决了内存碎片的问题了

  • 标记整理算法

    和标记清除法一样,不同的是它在标记清除法的基础上添加了一个整理的过程 ,即将所有的存活对象都往一端移动,紧邻排列(如图示),再清理掉另一端的所有区域,这样的话就解决了内存碎片的问题。

JVM中一次完整的GC流程是怎样的?对象如何晋级到老年代?

image.png

Eden -》 Survivor区 -》 老年代

如果第一次会判断是否是大对象,如果是大对象直接进入老年代。如果不是则进入Eden区域,如果下次出发gc,Eden需要引用的对象会到from区域同时对象的年龄+1,下次出发就将from移动到to,同时age也+1,直到最大(16)后将对象移入到老年代。

Java中的几种引用关系?他们的区别是什么?

  • 强引用

    内存空间不足时,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常 终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 如果强引 用对象不使用时,需要弱化从而使 GC 能够回收

  • 软引用

    当内存不足时垃圾回收器才会回收这些软引用可到达的对象(可以做缓存)

  • 弱引用

    弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。

  • 虚引用

    虚引用指向的对象十分脆弱,我们不可以通过get 方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁

final ,finally ,finalize的区别

final:java中的关键字,修饰符。
A).如果一个类被声明为final,就意味着它不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract抽象类的和final的类。
B).如果将变量或者方法声明为final,可以保证它们在使用中不被改变.
1)被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。
2)被声明final的方法只能使用,不能重载。
finally:java的一种异常处理机制。
finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。
finalize:Java中的一个方法名。
Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没被引用时对这个对象调用的。它是在Object类中定义的,因此所的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。

String s = new String("xxxx") 创建了几个对象

2个对象 字符串常量池,堆区new一个String对象

猜你喜欢

转载自juejin.im/post/7033284202820796452