java内存区域
1)程序计数器
因为java可以多线程并发执行,因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要一个独立的程序计数器。记录正在执行的虚拟机字节码指令的地址。
这个区域不会产生内存溢出异常。
2)栈
java虚拟机栈
栈中主要存放了编译期可知的四类八种基本数据类型存(逻辑型 boolean、文本型char、整数型byte、short、int、float、浮点数型double、long),对象引用类型,和对象引用类型(reference)。
本地方法栈
本地方法栈和java虚拟机栈所发挥的作用非常相似,他们之前的区别是虚拟机栈为虚拟机执行java方法服务。而本地方法栈是为虚拟机使用到的Native方法服务。
-Xss参数可以设置本地方法栈的内存上限。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
程序中 递归如果找不到出口(或者递归过深)也会抛出此异常(不断的进行入栈操作)
3)堆
用于存放对象的实列
可通过-Xms和Xmx来扩展堆的大小。因为现在的收集器基本都采用分代收集算法,所以java堆中还可以细分:新生代和老年代
如果在堆中没有内存完成实列分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
-Xmx可以设置堆内存的上限 -Xms 可以设置内存初始化的大小(-X代表非标参数 m代表memory,x代表max)
4)方法区MethodArea是一个逻辑上的概念 (也叫非堆 ) 1.8以前是Perm Space实现 1.8以后是Meta Space实现
用于储存已被虚拟机加载的类的信息(编译后的class 包括动态代理产生的Class)、常量、静态变量、即时编译器编译后的代码等数据,Class在被Loader时就会被放到方法区中,加载类的类加载器本身也存在这里。
垃圾收集行为在这个区比较少出现的,有点类似它的名字,永久代(1.8以后叫元数据区)
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError(后面会跟PermGen(MetaSpace) space字符串)异常。
PermSpace(<1.8)
字符串常量池位于PermSpace
FGC不会清理
大小启动的时候指定,不能变
MetaSpace(>=1.8)
字符串常量池位于堆。(一直创建字符串常量,观察堆和Mataspace) 观察内存变化
会触发FGC清理
不设定的话 最大就是物理内存
-XX:MaxPermSize可以设置方法区的上限。
5)常量池(方法区的一部分JDK1.8以后 已经移动到了堆中)
用于存放编译期生成的各种字面量和符号的引用
以上
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。可通过-Xmx和-XX:MaxPermSize可以设置堆内存和方法区上限。-Xmx2048m -XX:MaxPermSize=512m
把-Xmx 和-Xms大小设置一样内存大小,可以提高JVM的运行性能(取消掉伸缩区,因扩容时会发生内存抖动)
-Xms等价于-XX:InitialHeapSize 初始化堆
-Xmx等价于-XX:MaxlHeapSize 最大堆
对象的finalize()方法简介
当垃圾回收器要释放无用对象的内存时,会先调用该对象的finalize()方法。对象的finalize()方法有以下特点
1)垃圾回收是否执行该方法,以及何时执行该方法都是不确定的。
换句话说 以下这种情况是有可能的:一个程序只占用了少量内存,没有造成严重的内存需求,于是垃圾回收器没有释放那些无用对象的内存,因此这些对象的finalize()方法还没有被调用,程序就终止了。
2)finalize()方法有可能使对象复活,使它恢复到可触及状态。
可触及状态:当一个对象 假定A对象被创建后,只要程序中还有引用变量在引用它,那么它就始终处于可触及状态。
可复活状态:当程序不在有任何变量引用A对象时,那么它就进入可复活状态。在这个状态中,垃圾回收期会准备释放它的内存,在释放之前,会调用其他处于可复活状态的finalize()方法,这些finalize()方法有可能使A对象重新转到可触及状态。
不可触及状态:当java虚拟机执行完所有可复活对象的finalize()方法后,假如这些方法都没有使A对象转到可触及状态,那么A对象就进入不可触及状态,垃圾回收才会真正的回收它的内存。
3)垃圾回收器在执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常执行。
判断对象是否需要回收
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就加1;当引用失效时,计数器值就减一;任何时刻计数器为0的对象就是不可能在被使用的。
这种方法实现简单,判定效率也很高,但是主流的java虚拟机里面没有采用这种方法,因为它很难解决循环引用的问题。如图:
ABC三个对象循环引用,但是没有其他引用指向它们。ABC一团垃圾,无法回收
根可达性分析算法(主流的商业语言所采用的算法)
这个算法的基本思路就是通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径成为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,则证明此对象是不可用的。
在JAVA语言中,可作为GC Roots的对象包括以下几种:
- 虚拟机栈中引用的对象(main()方法栈帧开始引用)
- 方法区中类静态变量引用的对象(class load到内存之后 会立刻对静态变量初始化,所以静态变量能够访问的到的对象 叫根对象)
- 常量池引用的对象(这个class会用到其他class那些类的对象,这些事根对象)
- 本地方法栈中JNI引用的对象(C C++本地方法用到的类、对象)
JVM垃圾收集算法
标记——清除算法(Mark-Sweep)
你把它标出来,然后清掉 就这么简单
缺点:两遍扫描。产生大量不连续的内存碎片。以后程序运行过程中产生较大的对象时,无法找到足够连续的内存 而不得不提前触发另一次垃圾收集动作。
存活对象比较多的情况下,效率比较高
复制算法(Copying)
将内存按容量划分大小相等的两块,每次只使用一块。当这一块的内存用完了,就将还存活的对象复制到另外一块内存上面。
缺点:没有碎片,但将内存的使用率缩小了一半
适用于存活对象比较少的情况
标记——整理算法(Mark-Compact)
首先标记出所有需要回收的对象,然后让所有存活的对象都向一端移动。然后直接清理掉端边界以外的内存
缺点:没有碎片,扫描两次,需要移动对象,效率偏低
分代收集算法(Generational Collection)
我们jdk采用的应该就是分代收集算法。根据对象存活周期的不同将内存划分为几块,针对每一块使用不同的算法
新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要对少量存活的对象复制就可以进行收集。
老年代中(新生代中经过多次垃圾回收仍然存活的对象,例如缓存对象),因为对象存货率高,没有额外空间对它进行分配担保,就需要使用 标记——整理或者标记——清除算法。
演示堆内存溢出和解决办法
@Controller public class OutOfMemoryController { List<TestVO> list = new ArrayList<>(); @RequestMapping(value="/heapMemory",method=RequestMethod.GET) @ResponseBody public void heapMemory() { while(true) { list.add(new TestVO(UUID.randomUUID().toString())); } } }
因为new出来的对象是放在堆上面的,所以这样死循环的时候 会报错堆内存溢出(建议通过-Xmx和-Xms把堆内存设置的小一点 容易报错)
Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: GC overhead limit exceeded
1.jps(虚拟机进程状况工具)查到进程的id
这个命令是不是很像linux的ps -ef
jsl命令只有在jdk的bin目录下才生效暂时不知为啥(可能是没path没配置好 不过不影响这篇博客的学习)
2.根据进程id使用jmap(java内存映像工具)命令导出内存
jmap(Memory Map for java)可以用于生成堆存储快照
上面打印了导出内存的路径和名称(默认当前目录)
3.使用MAT分析内存存储快照
https://www.cnblogs.com/onelikeone/p/7131793.html 这里写的比较简单 附上一篇MAT下载和使用教程
上面怀疑的OutOfMemoryController和我们测试的类名一样
4.也可以正则匹配搜索一下我们的类,进一步定位
思考
以上测试代码是演示了堆内存溢出,怎么演示非堆内存溢出(也就是方法区)
记得有个面试题是要快速填满方法区。 这个就要先看上面 方法区存放的是什么东西了
上文提到了方法区:用于储存已被虚拟机加载的类的信息(编译后的class)、常量、静态变量、即时编译器编译后的代码等数据
动态代理会不断的产生新的class对象,可以快速填满方法区。
基于JVisualVM的可视化监控
jdk的安装目录bin文件下有jvisualvm.exe可执行文件。可以用来监控虚拟机的性能和故障处理。
这款JDK自带用来监控内存的可视化工具的详细使用,更新中。。。。。。