jvm

203 阅读4分钟

标记是从gcroot根节点开始标记: 根节点一般是:static引用的对象,常量引用的对象。虚拟机栈引用的对象,本地方法栈引用的对象

  • 标记-清除,标记后在清除(会产生大量碎片)

  • 标记-整理,标记后整理,像一端移动。

  • 复制,把存活的对象复制进另一个内存,当对象存活比较多时,会产生大量的复制并浪费内存(一般新生代用这个。新生代存活的对象比较少。) -XX:SurvivorRatio用于eden和Survivor的比例配置。

cms

  • 初始标记(stop the world,标记与gcroot直接关联的对象。)

  • 并发标记(和用户线程一起运行)

  • 重新标记(stop the world用于并发标记阶段,由于用户线程导致改变的那一部分对象)

  • 并发清除 缺点

  • 占用cpu资源

  • 由于并发清理阶段,可能会导致浮动垃圾(用户线程运行导致的垃圾),无法处理,也是由于并发清理,要给用户线程留一定的内存。当内存不够用时候(current mode failure),会使用serial old来清理,单线程stop the world。导致性能降低

  • 基于标记-清理的算法。会导致大量的空间碎片。可能导致提前触发 full gc (+UseCmsCompactAtFullCollection)开启合并整理

G1

优点

  • 不会产生大量碎片

  • 建立在可预测时间的回收模型 (g1把堆的划分多个大小相等的独立区域region,判断每个region的回收时间和回收大小。维护成一个优先列表,在规定的时间内,回收最大的region)

  • region之间的引用信息是通过remembered set来维护的。每个region都关联一个remembered set列表,对引用进行写操作时,判断是否是同一个region,如果不是。就在被引用对象的remembered set加入引用信息(如:a对b进行写操作。判断不是同一个region 就加入到b的remembered set里面。清理的时候就可以避免扫描全堆)

步骤

  • 初始标记(stop the world)

  • 并发标记(并发标记)

  • 重新标记(并行标记)

  • 回收(对region的回收价值和成本进行排序,在规定的时间内,制定回收计划,并行回收) serial单线程垃圾回收。新生代采用复制算法。老年代采用标记整理 parnew (serial的多线程版本)垃圾回收,不一定比单线程块。

类加载流程:加载->验证->准备(给静态变量默认值(static))——>链接->初始化(分配内存并初始化零值,不包含对象头)-使用。 类初始化的五个场景

  • 操作静态对象

  • new关键字

  • 反射

  • 加载main方法

加载:

java如何确定一个类是否相等

  • 类的类空间名称相同

  • 类加载机制相同

必须保证这两点。才能保证类相等

分析下列两种代码:

public class MyClassLoader {

    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new ClassLoader() {

            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream in = getClass().getResourceAsStream(fileName);
                    if (in == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[in.available()];
                    in.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };
        Object finalizeEscapeGc = classLoader.loadClass("com.hoolai.load.MyClassLoader").newInstance();
        System.out.println(finalizeEscapeGc.getClass());
        System.out.println(finalizeEscapeGc instanceof  com.hoolai.load.MyClassLoader);
    }
}
out::put 
class com.hoolai.load.MyClassLoader
false

__________________________________________________

public class MyClassLoader {

    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new ClassLoader() {

            @Override
            **public Class<?> findClass(String name) throws ClassNotFoundException {**
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream in = getClass().getResourceAsStream(fileName);
                    if (in == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[in.available()];
                    in.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };
        Object finalizeEscapeGc = classLoader.loadClass("com.hoolai.load.MyClassLoader").newInstance();
        System.out.println(finalizeEscapeGc.getClass());
        System.out.println(finalizeEscapeGc instanceof  com.hoolai.load.MyClassLoader);
    }
}

out::put 
class com.hoolai.load.MyClassLoader
true

这里不得不引入双亲委派模型 1、启动类加载器:加载<JAVA_HOME>lib/下面的类或者被-Xbootclasspath指定的路径。而且必须保证名称合法。 2、扩展类加载器:加载<JAVA_HOME>lib/ext目录中的下面的类 3、应用程序加载器:加载用户所在路径的类。默认是用应用程序加载器 先看类加载继承关系图

1、类加载机制代码如下:

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、检查是否被加载 2、没有被加载的话,且存在父类使用父类加载 3、否则调用findClass加载。 分析第一段代码: 重载了loadClass方法,并且当自定义加载器获取不到这个类时,才使用父类加载 即存在两个MyClassLoader对象。一个是使用自定义加载器加载,一个是使用应用程序加载器加载(main调用的时候) 第二段代码: 重载了findClass 判断类没有被加载。先调用夫类加载。即被应用程序加载器加载。所以两个类相等 这就是双亲委派机制。给予了java最基本的保障。即使不同的加载器加载object对象,都是由最顶层的类加载。