zoukankan      html  css  js  c++  java
  • 内存结构篇:方法区

    一、定义

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

    和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常

    该区域的内存回收目标主要是针对常量池的回收和对类型的卸载,但是一般比较难实现(尤其是类型的卸载,条件相当苛刻)。

    方法区的演变过程:

    JDK8以前,HotSpot 虚拟机使用永久代来实现方法区,使得垃圾收集器能够像Java堆一样管理这部分内存。因为永久代有 -XX:MaxPermSize 的上限,这种设计导致 Java 应用更容易遇到内存溢出的问题(会抛出 OutOfMemoryError 异常)。

    为了更容易管理方法区,在 JDK6 时,有了逐步改成采用本地内存来实现方法区的计划。到了 JDK7 ,已经把原本放在永久代的字符串常量池、静态变量等移出;而到了 JDK8 ,完成废弃了永久代的概念,将原来永久代的数据分到了堆和元空间(使用系统内存,只要没到物理最大内存就没有上限,可以手动设置元空间最大内存如: -XX MaxMetaspaceSize=8m)中,元空间存储类的元信息,静态变量和常量池等放入堆中。

    二、组成

    三、方法区内存溢出

    • JDK1.8 以前会导致永久代(PermGen space)内存溢出
    • JDK1.8 之后会导致元空间(Metaspace)内存溢出

    可能出现溢出的场景:

    • spring
    • mybatis

    四、运行时常量池

    • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
    • 运行时常量池,常量池是*.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
    • 运行时常量池时方法区的一部分
    • 除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()

    五、StringTable

    它的底层数据结构是HashTable

    主要存放字符串常量

    面试题:

    // StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
    public class Demo1_22 {
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 b 符号变为 "b" 字符串对象
        // ldc #4 会把 ab 符号变为 "ab" 字符串对象
    
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s3 = "ab";   //先从常量池中查找,如果没有就创建
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab"),在堆中创建了对象(s1、s2是变量)
            String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab(a、b是变量,可以直接从常量池中取出,常量字符串拼接)
            String s6 = s4.intern();
    
            // 问
            System.out.println(s3 == s4);   // false
            System.out.println(s3 == s5);   // true
            System.out.println(s3 == s6);   // true
    
            String x2 = new String("c") + new String("d");
            String x1 = "cd";
            x2.intern();
    
            // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
            System.out.println(x1 == x2);   // false;如果调换后,1.8的结果为true,1.6的结果为false,因为1.6中是复制出一个对象放入串池
    
        }
    }
    

    六、StringTable特性

    • 常量池中的字符串仅是符号,第一次用到时才变为对象
    • 利用串池的机制,来避免重复创建字符串对象
    • 字符串变量拼接的原理是 StringBuilder(1.8)
    • 字符串常量拼接的原理是编译器优化
    • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
      • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
      • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

    七、StringTable位置

    • jdk6:StringTable存储在永久代
    • jdk8:StringTable存储在
    /**
     * 演示 StringTable 位置
     * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
     * 在jdk6下设置 -XX:MaxPermSize=10m
     */
    public class Demo1_6 {
    
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<String>();
            int i = 0;
            try {
                for (int j = 0; j < 260000; j++) {
                    list.add(String.valueOf(j).intern());
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
        }
    }
    

    八、StringTable垃圾回收(在堆中,会被垃圾回收)

    测试代码

    /**
     * 演示 StringTable 垃圾回收
     * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
     * -Xmx10m(最大堆内存)
     * -XX:+PrintStringTableStatistics(打印字符串表的统计信息,查看字符串的实例个数,占用的大小信息)
     * -XX:+PrintGCDetails -verbose:gc(打印垃圾回收的详细信息,垃圾回收的次数,时间等)
     */
    public class Demo1_7 {
        public static void main(String[] args) throws InterruptedException {
            int i = 0;
            try {
                for (int j = 0; j < 110000; j++) { // j=100, j=10000
                    String.valueOf(j).intern();
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
    
        }
    }
    

    运行结果:

    [GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->772K(9728K), 0.0008440 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 2536K->488K(2560K)] 2820K->812K(9728K), 0.0008588 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [PSYoungGen: 2536K->488K(2560K)] 2860K->836K(9728K), 0.0014016 secs] [Times: user=0.05 sys=0.00, real=0.00 secs] 
    110000
    Heap
    ....
    

    九、StringTable性能调优

    • 调整 -XX:StringTableSize=桶个数
    • 考虑将字符串对象是否入池(使用intern方法,多个相同值只存储一份)
    List<String> address = new ArrayList<>();
    System.in.read();
    for (int i = 0; i < 10; i++) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {
            String line = null;
            long start = System.nanoTime();
            while (true) {
                line = reader.readLine();
                if(line == null) {
                    break;
                }
                address.add(line.intern()); //使用intern方法后,line未被引用会被垃圾回收,减少了字符串的个数,减少堆内存的占用
            }
            System.out.println("cost:" +(System.nanoTime()-start)/1000000);
        }
    }
    System.in.read();
    
  • 相关阅读:
    下载ORACLE中BLOB内容到客户端
    VIEW_PK
    带下拉框的GridView的OnRowEditing
    HyperLinkField
    ArcEngine开发 退出系统报错
    【转载】Winform 中的控件透明设置要点
    【转载】大整数相乘
    c# oracle 分页
    view_fk
    ITopologicalOperator Intersect
  • 原文地址:https://www.cnblogs.com/flypig666/p/13975119.html
Copyright © 2011-2022 走看看