在加载阶段,就是找到需要加载的类并把类的信息加载到jvm的方法区中。,
1.根据类的全路径名找到相应的class文件,然后从class文件中读取文件内容;
2. 从jar文件中读取。
图中constant pool部份,他就是常量池的一部份。它会被加载至方法区中的运行常量池中,而类中的其它部份(类的元数据)则放在另一块。
在JDK8中,运行常量池已被放入堆中,而元数据放入元空间。
元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存(内存概述与JAVA进程内存中的末使用区域)限制,但可以通过以下参数来指定元空间的大小:
-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
因为JAVA类的加载的动态性,在JDK7的时候,如果你一开始给方法区设置了很小的值,JAVA在运行时候类越加越多,则会OOM,如果一开始过大,JAVA运行时没多少的类,则会浪费空间。而现在放在元空间的话,他过大则会向本地内存继续申请,过小就会把内存还回去(末使用区域由操作系统调节,置换出内存页)。
类加载器—双亲委派机制
从右图中ClassLoader中loadClass源码中可以看到,从当前加载器进行逆向查找一个类是不是已被加载,如果被加载过了,则返回父级的加载器。如果找不到才调用 findClass查找。再结合左图,就是BootSrap加载了一个类,其它的加载器都不能加载。
JVM中的类加载器使用的是双亲委派机制。为何要用?
判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。如果没有双亲委派机制,会出现什么样的结果呢?
比如我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下(ClassPath).那样这两个相同的类采用的是不同的类加载器,系统中就会出现两个不同的HashMap类,这样引用程序就会出现一片混乱。
常量池及元空间实验
字符串常量池是jdk6是方法区的一部分。用于存放编译期间生成的字符串常量《JVM常量池》。现在已移到堆区(图中的OOM是从堆中报出来的)
一直用不同的加载器加载类,他会导致我们的元空间一直变大,因为元空间默认为系统空闲内存的大小
给他加上-XX:MaxMetaspaceSize=10M参数的时候,他分分钟钟的爆了。