微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JVM探究丶

JVM架构

img

双亲委派机制

类装载器采用的机制是双亲委派模式:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

BootstrapClassLoader启动类加载器:

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

ExtClassLoader标准扩展类加载器

java编写,加载扩展库,如classpath中的jrejavax.*或者java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器。

AppClassLoader系统类加载器

java编写,加载程序所在的目录,如user.dir所在的位置的class

CustomClassLoader用户自定义类加载器

java编写,用户自定义的类加载器,可加载指定路径的class文件

类加载器测试

public class Car {
    public int age;

    public static void main(String[] args) {
        Car car = new Car();
        System.out.println(car);

        Class<? extends Car> aClass = car.getClass();
        System.out.println(aClass);

        ClassLoader classLoader = aClass.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader
        System.out.println(classLoader.getParent());//sun.misc.Launcher$ExtClassLoader jre8\lib\ext
        System.out.println(classLoader.getParent().getParent());//null rt.jar即root.jar
    }
}

双亲委派机制源码

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox),沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问:CPU、内存、文件系统、网络

img

Native(JNI)

进入本地方法栈Native Method Area/Stack

public class Thread implements Runnable {
    public synchronized void start() {
        try {
            start0();
        }
    }
    private native void start0();
}

方法区

静态变量static、常量final、类信息Class(构造方法、接口定义)、运行时的常量池,都保存在方法区中,实例变量保存在堆内存中

一种数据结构,先进后出。(main方法先执行,后结束)(区别于队列的先进先出FIFO)

存放的内容:基本数据类型、对象引用、实例方法

运行原理:栈帧,程序正在执行的方法一定在栈的顶部,栈溢出会报错StackOverFlowError

三种JVM版本

  • Sun公司的HotSpot
  • Oracle的JRockit
  • IBM的j9 VM

  • Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
  • 存放对象的实例
  • 堆里边还分三个区域:新生区(伊甸园区+幸存0区+幸存1区)、养老区、永久区(jdk8以后叫元空间)
  • 轻GC、重GC(full GC)
  • 垃圾回收主要在 伊甸园区 和 养老区
  • OOM:java.lang.OutOfMemoryError: Java heap space

永久区

这个区是常驻内存的,用来存放Jdk自身携带的Class对象、Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟机释放。

  • jdk1.6:永久代,常量池是在方法区
  • jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中(方法区)
  • jdk1.8:无永久代,常量池在元空间(方法区),逻辑上存在

JVM调优

public class Test {
    public static void main(String[] args) {
        long max = Runtime.getRuntime().maxMemory();//虚拟机试图使用的最大内存 默认电脑内存的1/4
        long total = Runtime.getRuntime().totalMemory();//jvm初始化总内存 默认电脑内存的1/64
        System.out.println("max:"+max+"byte,"+max/(double)1024/1024+"MB");
        System.out.println("total:"+total+"byte,"+total/(double)1024/1024+"MB");
        //OOM调试jvm调优:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
    }
}

项目中定位OOM的工具:Jprofiler(idea+windows)、MAT(eclipse)

  • 能够看到代码第几行出错,内存快照分析
  • 分析Dump内存文件,快速定位内存泄漏
  • 获得堆中的数据
  • 获得大的对象
import java.util.ArrayList;

//-Xms设置初始化内存分配大小 1/64
//-Xmx设置最大内存分配 1/4
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {
    byte[] bytes = new byte[1024 * 1024];

    public static void main(String[] args) {
        ArrayList<Demo03> list = new ArrayList<>();
        int count = 0;

        try {
            while (true) {
                list.add(new Demo03());
                count++;
            }
        } catch (Exception e) {
            System.out.println("count:"+count);
            e.printStackTrace();
        }
    }
}

image-20201114125728027

垃圾回收

GC的作用区域:方法区和堆

主要是回收新生代

  • 新生代
  • 幸存区(from,to)
  • 老年区

GC有两种:轻GC,重GC(全局GC)

GC算法

引用计数法

计数为0的淘汰

缺点计数器成本

复制算法

GC执行时,将非垃圾对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间(from复制到to,谁空谁是新的to。)

好处无内存碎片,坏处也是显而易见的,直接损失了一半的可用内存。最佳实践:存货对象少,垃圾对象多的情况下,非常高效

标记清除法

扫描标记不可达对象,清除不可达对象

缺点两次扫面浪费时间+内存碎片,优点是不需要额外的空间

标记压缩

标记清除的方式之上,整理碎片

总结

内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)

内存整齐度:复制算法=标记清除算法>标记压缩算法

内存利用效率:标记压缩算法=标记清除算法>复制算法

没有最好的算法,只有最合适的算法:分代收集

年轻代,存货率低:复制算法

老年代,存货率高:标记清除(碎片不是太多)+标记压缩

JMM

JUC并发编程丶

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐