下一篇: java 字节码文件解析 https://blog.youkuaiyun.com/chenjianhuideyueding/article/details/109786394
java内存管理
我这里使用的版本为:java version "1.8.0_31",64位的机器
首先,这里会先用一个工具查看内存的信息:jconsole
相关的介绍:https://docs.oracle.com/javase/1.5.0/docs/guide/management/jconsole.html
装了jdk,并且配置了环境变量,可以直接在控制台中输入jconsole,就会弹出对应的界面。
这里我首先写一段代码,仅仅是sleep一段时间,这里就可以使用jconsole来查看这一段运行着
的代码。
package cn.yishijie.jvm;
import java.util.concurrent.TimeUnit;
public class JDK8Memory {
public static void main(String[] args) throws InterruptedException{
// 睡一个小时
TimeUnit.HOURS.sleep(1L);
}
}
运行起来,然后直接在控制台输入jconsole:弹出以下界面
点击箭头的哪个进程,然后选择不安全连接然后就可以进去查看这个类运行的具体情况了。
直接进入到内存的那个菜单进入:
点开A区域可以看到内存的划分情况;点B可以执行一次GC;C可以查看对应的内存的使用情况;
D可以图示的看到内存情况,鼠标移动到该区域停住可以弹出该区域的名称,点击可以选中对应的区域
并展示该区域的信息。
这里分为:堆和非堆。
堆:
PS Old Gen: 老年代
PS Eden Space: 新生代eden区
PS Survivor Space: 新生代幸存区
非堆:
Meta Data: 元空间
Code Cache: 代码缓存
Compressed Class Space: 压缩类空间
启动的时候,增加VM参数:-XX:+PrintCommandLineFlags
然后可以看到:
-XX:InitialHeapSize=133094272 -XX:MaxHeapSize=2129508352 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
可以发现这里使用的垃圾回收器: ParallelGC ,它会和Parallel Old搭配使用。其实上面的PS就是表示这个垃圾收集器组合。
1、方法区
方法区是线程共享的内存区域,用于存储jvm加载的
类型信息,用本地内存元空间(meta space)实现。
垃圾回收会被该内存进行处理,主要是对类型的卸载和
常量池的回收。
方法区在JVM启动的时候被创建,它的大小决定了能保存
的类的数量,如果超出,会出现oom的异常。
方式区使用上述的Meta Data来实现,使用的不是jvm的内存,而是物理机器的内存,
所以它的上限是物理机器的内存。
启动的时候加入虚拟机参数:-XX:+PrintFlagsFinal,在一堆输出中找到下面元空间的初始值和最大值
MetaspaceSize = 21807104 = 20.8M
MaxMetaspaceSize = 4294901760 = 4G
当元空间的大小使用量到达初始值大小就会触发一次full gc,如果还不够的话,就会扩展大小,但是最大不能
超过MaxMetaspaceSize的大小。
这里验证下full gc的产生,回看到原来的那个图,然后选中Meta Data
可以看到,我这个程序运行起来,Meta Data基本就8M多,所以我们可以通过:
-XX:MetaspaceSize=6M 6M内存
-XX:MaxMetaspaceSize=200M 200M内存
-XX:+PrintGCDetails 打印gc详情
来设置大小,从而触发full gc
这里设置了元空间的初始大小为6M,同时也发生了元空间也发生了Full GC。这里是需要为什么要jconsole连接上才会Full Gc。
后来我试了设置成4M,发现确实可以一下就打印出来。看来应该是程序本身加载的类并没有达到6M,应该是连接jconsole的时候,还会加载类,占用了一部分空间,达到8M就触发了。
这个我也做个实验了(vm参数中加入:-verbose:class即可),确实是这样子。这里我只是截取出打印的一部分记录:
[Loaded sun.rmi.transport.SequenceEntry from D:\software\program-tool\javaSE1.8\jdk1.8\jre\lib\rt.jar]
[Loaded java.util.concurrent.locks.LockSupport from D:\software\program-tool\javaSE1.8\jdk1.8\jre\lib\rt.jar]
[Loaded sun.rmi.server.MarshalOutputStream$1 from D:\software\program-tool\javaSE1.8\jdk1.8\jre\lib\rt.jar]
当元空间经过Full gc也不够的时候,就会抛出异常。
-XX:MaxMetaspaceSize=4M 设置最大大小为4M,那么对于这个程序来说肯定是不够内存的。运行程序,打印结果如下图:
可以发现,已经抛出异常。如果不是抛出这个异常,而是
Error occurred during initialization of VM MaxMetaspaceSize is too small.
那么可能你的版本比我这个java版本要小,因为这个最小值的检查,在https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8024945 里面提到的版本之后就被移除了。
一般来说,元空间垃圾回收的机会比较小,因为卸载类的信息是非常严格的,所以你可以试下在jconsole中,那个执行gc的按钮,进行gc的操作,但是你会发现,基本没有回收元空间的内存,一般会触发这里gc的原因,都是那个自定义加载器加载的那些类。比如cglib技术加载的类,自定义类加载器加载的类等。
cglib的测试代码:这里你设置下-XX:MaxMetaspaceSize=14M -verbose:class 参数,然后通过jconsole,可以看到元空间在不停增大,最后就抛出oom异常的现象!
package jdk8;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import java.util.concurrent.TimeUnit;
public class CglibAddClass {
public static void main(String[] args) throws InterruptedException{
while (true){
TimeUnit.MILLISECONDS.sleep(100L);
Enhancer enhancer =new Enhancer();
enhancer.setSuperclass(CglibAddClass.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor)(obj, method, arg, proxy)->
proxy.invokeSuper(obj, arg));
enhancer.create();
}
}
}