JVM整理

jvm

java运行时数据区域

  • 程序计数器

    当前线程运行的行号指示器,计数器的任务就是用来调度字节码的执行(分支,循环,跳转,异常处理等),以及线程切换后能记录上次执行的位置

  • java虚拟机栈

    每一个方法执行的同时都会创建一个栈帧(用于存储局部变量表、操作数栈、动态链接、方法出口等信息),每一个方法的执行就是进栈和出栈的过程,

  • 本地方法栈

    本地方法栈则为虚拟机使用到的Native方法服务

  • 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,

  • 方法区

    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

    • 运行时常量池

      运行时常量池是方法区的一部分。Class文件还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

GC垃圾回收机制

  • 垃圾回收算法

    • 标记清除算法

      标记-清除 分为两个阶段:标记阶段和清除阶段。
      在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。
      在清除阶段,清除所有未被标记的对象。

      缺点:
      1、效率问题,标记和清除两个过程的效率都不高;
      2、空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大的对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    • 标记整理算法

      标记整理算法类似与标记清除算法,不过它标记完对象后,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
      缺点:
      1、效率问题,(同标记清除算法)标记和整理两个过程的效率都不高;
      优点:
      1、相对标记清除算法,解决了内存碎片问题

    • 复制算法

      它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过的内存空间一次清理掉,这样使得每次都是对整个半区进行内存回收,
      优点
      效率高,没有内存碎片
      缺点:
      1、浪费一半的内存空间
      2、复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。

    • 标记整理算法

      标记整理算法类似与标记清除算法,不过它标记完对象后,不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
      缺点:
      1、效率问题,(同标记清除算法)标记和整理两个过程的效率都不高;
      优点:
      1、相对标记清除算法,解决了内存碎片问题。

  • 对象生存或者死亡

    • 引用计数算法

      引用计数算法的思想是:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减一;任何时刻计数器为0的对象就是不可能再被使用的
      引发的问题点:
      ObjA与ObjB循环引用这两个对象永远无法被垃圾回收机制回收。

    • 可达性分析算法

      通过一组称为“GC Roots”的活跃引用(注意不是对象,和书上写的不一样)作为起始点,从这些活跃的引用开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,可以被回收了。
      在Java语言中,可作为GC Roots的对象包括下面几种:
      1,方法中引用的对象
      2,静态成员对象
      3,常量对象
      4,本地方法中引用的对象

    • 真正判断生存或者死亡的方法

      至少要经过两个阶段:
      第一个阶段是对象没有和GC Roots相连的引用链。那该对象会被第一次标记并进行第一次筛选,筛选条件为该对象有没有必要执行finalize()方法。当没有覆盖finalize()方法,或者finalize()方法已经被调用过,则判定为没有必要执行。
      如果判定有必要执行finalize()方法,则虚拟机会将其放到一个F-Queue的队列中,并在稍后创建一个低优先级的Finalizer去执行它。finalize()方法是对象逃脱死亡的最后一次机会(只会执行一次 就是在finalize方法中重新引用一下)

  • 堆内存划分

    • 新生代(1/3)

      • Eden(8/10)

        Java新对象的出生地

      • From Survivor(1/10)

        上一次GC的幸存者,作为这一次GC的被扫描者。

      • To Survivor(1/10)

        保留了一次MinorGC过程中的幸存者。

      • MinorGC

        • 触发条件

          当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收

        • 过程分析

          首先,把Eden和from区域中存活的对象复制到to区域,同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区);然后,清空Eden和from中的对象;最后,to和from互换,原to成为下一次GC时的from区。

      • 对象什么时候进入老年代

        1、对象的年龄达到某个值时 ( 默认是 15 岁)这些对象就会成为老年代。
        2、对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代可通过参数进行设置。
        3、如果Survivor区中年龄相同的对象的所占空间大于Survivor的一半,年龄大于等于该年龄的对象直接进入老年代(因为是复制算法)
        4、空间分配担保机制:如果ServicorTo不够位置了就放到老年区

    • 老年代(2/3)

      • FullGC

        Full GC触发条件:
        (1)调用System.gc时
        (2)方法区空间不足
        (3) 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
        (4)当发生Minor GC之前只要老年代所剩空间大于新生代对象的总大小或者大于每次从新生代晋升到老年代的对象的平均值的大小就会触发一次MinorGC否则就会触发一次Full GC

  • 垃圾回收器

    • 新生代

      • Serial收集器(复制算法)

        新生代单线程收集器,标记和清理都是单线程。

      • ParNew收集器(复制算法)

        新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

      • Parallel Scavenge收集器(复制算法)

        并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。

    • 老年代

      • Serial Old收集器(标记-整理算法)

        老年代单线程收集器,Serial收集器的老年代版本。

      • Parallel Old收集器(复制算法)

        Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。

      • CMS收集器(标记-清除算法)

        高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

关于java对象

  • 对象的内存布局

    • 对象头

      • 运行时数据

        用于存储对象自身的运行时数据(Mark Word)如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

      • 类型指针

        类型指针(Class Pointer)用来指向对象对应的Class对象的内存地址

      • 数组长度

        数组长度(Length) 如果是数组对象,还有一个保存数组长度的空间

    • 实例数据

      实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类中继承下来的,还是在子类中定义的,都需要记录下来。

    • 对齐填充

      Java对象占用空间是8字节对齐的,即所有Java对象占用bytes数必须是8的倍数

  • 对象的创建

    • 首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否已被加载、解析和初始化过

      • 执行类加载逻辑
    • 虚拟机将为新生对象分配内存(对象所需内存的大小在类加载完成后便可完全确定)

    • 内存是否规整

      • 指针碰撞

        假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离

      • 空闲列表

        虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)

    • 当前Eden区是否有空间放的下当前对象的内存大小?

      • GC
    • 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值

      属于对象的变量 非静态变量

    • 分配到的内存空间都初始化为零值虚拟机要对对象进行必要的设置

      例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中

    • 执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始

      父类非静态变量
      父类匿名构造器
      父类构造器
      子类非静态变量
      子类匿名构造器
      子类构造器

  • 关于引用

    • 强引用(Strong Reference)

      强引用就是我们经常使用的引用

    • 软引用(Soft Reference)

      如果一个对象只具有软引用,在内存足够时,垃圾回收器不会回收它;如果内存不足,就会回收这个对象的内存。
      作用:适合作用于缓存

    • 弱引用(Weak Reference)

      当垃圾回收器扫描到只具有弱引用的对象,不管当前内存空间是否足够,都会回收内存。只会存活一次垃圾回收机制
      如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到

    • 虚引用(Phantom Reference)

      虚引用主要用来跟踪对象被垃圾回收器回收的活动。
      虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

类加载

  • 触发类加载的条件

    • 类的主动引用(一定会发生类的初始化)

      • 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类

      • 调用类的静态成员(被final修饰、已在编译期把结果放入常量池的静态字段除外)和静态方法

      • new一个类的对象

        • 使用java.lang.reflect包的方法对类进行反射调用
    • 类的被动引用

      • 当访问一个静态域时,只有真正声明这个域的类才会被初始化
      • 通过数组定义类引用,不会触发此类的初始化
      • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
  • 类加载机制

    • 加载

      将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个表这个类的java.lang.Class对象,作为方法区类数据的访问入口。 这个过程需要类加载器参。

    • 链接

      • 验证

        确保加载的类信息符合JVM规范,没有安全方面的问题。

      • 准备

        正式为类变量(static变量)分配内存并设置类变量初始值(默认值)的阶段,这些内存都将在方法区中进行分配

      • 解析

        虚拟机常量池内的符号引用替换为直接引用的过程

    • 初始化

      •初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集
      类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

      • 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
      • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

  • 类加载器

    • 引导类加载器引导类加载器(bootstrap class loader)

      它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar)

    • 扩展类加载器(extensions class loader)

      用来加载 Java 的扩展库(JAVA_HOME/jre/ext/*.jar) 。

    • 应用程序类加载器(application class loader)

      它根据 Java 应用的类路径(java.class.path 路径下的内容)来加载 Java 类。
      一般来说,Java 应用的类都是由它来完成加载的。

    • 自定义类加载器

      开发人员可以通过继承 java.lang.ClassLoader类的方式
      实现自己的类加载器,以满足一些特殊的需求。

  • 双亲委派机制

    • 流程

      1)那么app会先查找是否加载过A,若有,直接返回;
      2)若没有,去ext检查是否加载过A,若有,直接返回;
      3)若没有,去boot检查是否加载过A,若有,直接返回;
      4)若没有,那就boot加载,若在E:\Java\jdk1.6\jre\lib*.jar下找到了指定名称的类,则加载,结束;
      5)若没找到,boot加载失败;
      6)ext开始加载,若在E:\Java\jdk1.6\jre\lib\ext*.jar下找到了指定名称的类,则加载,结束;
      7)若没找到,ext加载失败;
      8)app加载,若在类路径下找到了指定名称的类,则加载,结束;
      9)若没有找到,抛出异常ClassNotFoundException

    • 优点

      总结:
      从底向上检查是否加载过指定名称的类;从顶向下加载该类。(在其中任何一个步骤成功之后,都会中止类加载过程)
      双亲委托的好处:假设自己编写了一个java.lang.Object类,编译后置于类路径下,此时在系统中就有两个Object类,一个是rt.jar的,一个是类路径下的,在类加载的过程中,当要按照全类名去加载Object类时,根据双亲委托,boot会加载rt.jar下的Object类,这是方法结束,即类路径下的Object类就没有加载了。这样保证了系统中类不混乱。

    • 如何破坏双亲委派机制

      1.重写loadClass方法并且里面不委派给父类去加载
      2.利用线程上下文加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的 setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承 一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序 类加载器。
      Thread thread = new Thread();
      thread.setContextClassLoader(new ClassLoader() {
      @Override
      protected Class<?> findClass(String name) throws ClassNotFoundException {
      return super.findClass(name);
      }
      });

    • java.class.ClassLoader类解析

      作用:
      – java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,
      找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个
      Java 类,即 java.lang.Class类的一个实例。
      相关方法
      getParent() 返回该类加载器的父类加载器。
      loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
      findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
      findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
      defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果java.lang.Class类的实例。这个方法被声明为 final的。
      resolveClass(Class<?> c) 链接指定的 Java 类。
      对于以上给出的方法,表示类名称的 name参数的值是类的名称。需要注意的是内部类的表示,如
      com.example.Sample 1 和 c o m . e x a m p l e . S a m p l e 1和com.example.Sample 1com.example.SampleInner等表示方式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值