JAVA虚拟机

JVM

  • 类的加载过程:加载、验证、准备、解析、初始化、使用、卸载
  • 类的初始化时机(有且只有六种):
    1. new、读取/设置类的静态字段(final 修饰、已在编译期把结果放入常量池的静态字段除外)、调用类的静态方法
    2. 反射机制调用类
    3. 初始化子类时,父类未初始化,会先初始化父类
    4. 虚拟机会先初始化主类(包含main方法)
    5. jdk7新加入动态语言支持
    6. jdk新加入方法
  • 双亲委派(向上级类提交加载请求):1 避免重复加载类 2 避免用户篡改核心库的类
  • 加载器分类:加载核心库的类、加载扩展jar包类、加载自定义的类、继承自定义类加载器在这里插入图片描述
public class Test01 {
    public static void main (String[] args){
        System.out.println(MyChild1.str2);
    }
}
class MyParent1{
    public static String str = "hello world";
    static {
        System.out.println("MyParent1 static");
    }
}
class MyChild1 extends MyParent1{
    public static String str2 = "welcome";
    static{
        System.out.println("MyChild1 static");
    }
}
//MyParent1 static
//MyChild1 static
//welcome
public class Test02{
    public static void main(String[] args){
        System.out.println(MyParent2.str);
    }
}

class MyParent2{
    public static final String str = "hello world";
    static {
        System.out.println("Myparent2 static block");// 这一行能输出吗?不会
        //这里的str在编译期间确定了 只有主动使用类才会导致类初始化
    }
}
//hello world
public class Test03{
    public static void main(String[] args){
        System.out.println(MyParent3.str);
    }
}

class MyParent3{
    public static final String str = UUID.randomUUID().toString();
    static {
        System.out.println("Myparent3 static block");
    }
}
//Myparent3 static block
//d0358b6d-7e33-4cae-9a61-65bdb9037944
  • 程序计数器:线程私有的。线程所执行的字节码的行号指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
  • 栈(Stack):存储一些基本类型的值、对象的引用、方法等。存取速度比堆要快,仅次于寄存器,栈数据可以共享。主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放。对于栈来说不存在垃圾回收问题,只要线程一旦结束,该栈就Over,生命周期和线程一致,是线程私有的。方法自己调自己就会导致栈溢出(递归死循环测试)
  • 方法区:与堆(heap)一样是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码
    1. JDK7 之前(永久代)用于存储已被虚拟机加载的类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。每当一个类初次被加载的时候,它的元数据都会被放到永久代中。永久代大小有限制,如果加载的类太多,很可能导致永久代内存溢出
    2. JDK8用元空间替代了永久代,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
  • 堆(Heap) Java7之前Heap 堆,一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的,类加载器读取了类文件后,需要把类,方法,常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行,堆内存分为三部分:新生区 Young Generation Space Young/New养老区 Tenure generation space Old/Tenure永久区 Permanent Space Perm堆内存逻辑上分为三部分:新生,养老,永久(元空间 : JDK8 以后名称)
    1. 新生区(伊甸区+幸存区)+ 养老区
       新生区是类诞生,成长,消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。
       新生区又分为两部分(默认8:1:1):伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是在伊甸区被new出来的,幸存区有两个:0区 和 1区,当伊甸园的空间用完时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC)。将伊甸园中的剩余对象移动到幸存0区,若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区,那如果1区也满了呢?(这里幸存0区和1区是一个互相交替的过程)再移动到养老区,若养老区也满了,那么这个时候将产生MajorGC(Full GC),进行养老区的内存清理,若养老区执行了Full GC后发现依然无法进行对象的保存,就会产生OOM异常
      新建对象不一定直接放在新生代,如果对象过大,会直接存入老年区
  • OutOfMemoryError (内存溢出):
     指的是JVM而可用内存不足,内存超过最大可用值。常见:
    1. 栈溢出:与方法的执行(创建虚拟机栈 栈帧入栈出栈)有关,可能发生了死递归。
    2. 堆溢出:首先垃圾回收,回收后依然内存不足
    3. 方法区
    4. 本机内存溢出
  • 内存泄漏(严重的内存泄漏会导致内存溢出)
     无用的对象继续占用内存,一直没有得到释放,无用的内存没有得到释放叫内存泄漏。
     典型场景:每一次请求或操作都分配内存,却有一部分没有释放,请求越来越多,内存泄漏也愈加严重。

1、Java虚拟机的堆内存设置不够,可以通过参数 -Xms(初始值大小),-Xmx(最大大小)来调整。
2、代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)或者死循环
2. 永久区(Perm)
 永久存储区是一个常驻内存区域,用于存放JDK自身所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。

  • 判断是否是需要回收的对象
    1. 引用计数法
      给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。 这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
    2. 可达性分析算法(gc Roots)
      这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
      在这里插入图片描述
      1、虚拟机栈(栈帧中的局部变量表)中引用的对象;
      2、方法区中的类静态属性引用的对象。
      3、方法区中常量引用的对象。
      4、本地方法栈中 JNI (Native方法)引用的对象。
  • 引用
    1. 强引用
       当内存不足,JVM开始垃圾回收,对于强引用的对象,就是出现了OOM也不会对该对象进行回收,死都不收。强引用是我们最常见的普通引用,只要还有强引用指向一个对象,就能表明对象还活着,垃圾收集器不会碰这种对象。在 Java中最常见的就是强引用,把一个对象赋值给一个引用变量,这个引用变量就是一个强引用,当一个对象被强引用时,它处在可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到,JVM也不会回收。
       因此强引用是造成 Java 内存泄漏的主要原因之一。

    2. 软引用
      对于只有软引用的对象来说:当系统内存充足时它不会被回收,当系统内存不足时它会被回收。软引用通常在对应内存敏感的程序中
       假如有一个应用需要读取大量的本地图片-适用软引用:
      1、如果每次读取图片都要从硬盘读取则会严重影响性能;
      2、如果一次性全部加载到内存中又可能造成内存溢出。

    3. 弱引用
       弱引用需要用 java.lang.ref.WeakReference 类来实现,它比软引用的生存周期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内容空间是否足够,都会回收该对象占用的内存;

  • GC内存回收算法
    1. 标记清除
       该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。标记清除后会产生大量不连续的碎片,不利用继续存放对象。
    2. 标记复制
       为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
    3. 标记整理
       根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
    • 分带收集算法
       一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
  • gc分类/Minor GC 与 Full GC
    1. 部分收集 (Partial GC):
      – 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
      – 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。
      – 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
    2. 整堆收集 (Full GC):收集整个堆和方法区。
  • GC垃圾回收算法和垃圾收集器
     GC算法(引用计数、复制、标记清除、标记整理)是内存回收的方法论,垃圾收集器就是算法的具体实现;
     因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集;
    • 并发垃圾回收器(CMS)- Concurrent Mark Sweep
       用户线程和垃圾收集线程同时执行,不需要停顿用户线程,因为是标记清除算法所以产生大量垃圾碎片,因此默认在FullGC时进行内存碎片的合并整理。
      1.初始标记:从GC Roots对象开始扫描能够直接关联到的对象,并做标记,需要STW,但是一般会很快完成。
      2.并发标记:从初始标记的基础上,继续向下搜索并做标记,这个阶段应用线程和GC线程并发执行,不会造成停顿。
      3.重新标记:处理上几次以来可能引用关系发生变化的部分,并重新进行标记,这个阶段会停止应用线程
      4.并发清除:将以上几个步骤标记的无法访问的对象进行并发清理,并将清理的空间回收到空闲列表

      缺点:并发清除时应用线程也在运行,可能产生新的垃圾。
    • G1垃圾回收器(垃圾优先Garbage-First)
      将物理分区变成逻辑分区,将内存分为一个个Region。一个region在逻辑上分为四种:Eden,survivor,old,humongous。G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先回收垃圾最多的分区。G1与CMS在并发收集时的算法没太大区别,用的是 三色标记 算法。
      &emsp三色标记算法: 黑色:对象和属性已标记 灰色:对象被扫描过了,但是对象中还存在未扫描的引用 白色:不可达对象 。因为是并发标记,应用线程会使得引用发生改变,可能会存在漏标问题(CMS重新标记、G1快照记录因引用的变化)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值