内存泄漏(Memory Leak):指的是对象无法得到及时的回收,导致其持续占用内存空间,造成了内存空间的浪费。 内存泄露一般是强引用才会出现问题,其他像软引用,弱引用和虚引用影响不大。
内存溢出(Out Of Memory):内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。
这两个区别结合下面的问题2可以更好的理解。
我们先来看下面一个简单的例子:
package com.zwx.jvm;
public class JVMTuningDemo {
public static void main(String[] args) {
{
byte[] bytes = new byte[1024 * 1024 * 64];
}
System.gc();
}
}
调用之后打开gc日志,如果不知道怎么获取gc日志的,可以点击这里。
可以看到GC之后,对象并没有回收掉,从代码上来说,因为有{},所以理论上已经离开作用域了,bytes会被回收(如果不加{}是肯定不会被回收的,因为没有离开作用域),但是这里为什么还是没有被回收?
回答这个问题之前我们先对上面的代码改进一下
package com.zwx.jvm;
public class JVMTuningDemo {
public static void main(String[] args) {
{
byte[] bytes = new byte[1024 * 1024 * 64];
bytes = null;
}
System.gc();
}
}
这时候再来看,会发现已经被回收了
这是因为之前虽然已经离开作用域了,但是却并没有收回引用,也就是说栈帧中的局部变量表数组中所对应的slot(局部变量表中数组的每一个位置都被称之为slot)还是有值的,并没有被切断引用,而将其置为Null就等于切断了引用,所以可以被回收。
如果看过我的并发编程系列文章中对AQS同步队列以及阻塞队列的源码分析,那么也应该可以看到,这些源码中也是大量使用了这种方式来帮助虚拟机进行gc:
在有些场景这种设置为null的方式确实是一种解决方式,但是其实最优雅的方式还是以恰当的变量作用域来控制回收变量。
我们再对上面的例子进行改写:
package com.zwx.jvm;
public class JVMTuningDemo {
public static void main(String[] args) {
{
byte[] bytes = new byte[1024 * 1024 * 64];
}
int i = 0;
System.gc();
}
}
运行之后打开gc日志:
我们会发现,bytes对象确实也被回收了,这又是为什么呢?
这是因为栈帧中的局部变量表内的每一个slot都是可以复用的,当bytes变量离开了其作用域之后,Java虚拟机知道这个slot已经无效了,但是虽然无效,引用却还在,所以如果没有新的变量过来占用bytes变量所在的slot,是无法将bytes回收的,而一旦有新的变量过来占用slot,自然而然bytes对象的引用就被切断了,从而被gc掉。
答案是不一定的。
即使在可达性分析法中被判定不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,对象依然有“逃生”的机会。
一个对象在第一次被标记为不可达对象时,并不会立刻被回收,而是会进行判断是否有必要执行finalize()方法,那么什么时候会执行finalize()方法呢?有两种情况:
package com.zwx.jvm;
import java.util.ArrayList;
import java.util.List;
public class ObjEscapeByFinalize {
public static ObjEscapeByFinalize objEscapeByFinalize = null;
public static void main(String[] args) throws InterruptedException {
objEscapeByFinalize = new ObjEscapeByFinalize();
//首次自救
objEscapeByFinalize = null;
System.gc();
Thread.sleep(1000);//finalize()方法优先级比较低,稍微停顿一会等一等
print();
//再次自救
objEscapeByFinalize = null;
System.gc();
Thread.sleep(1000);
print();
}
static void print(){
if (null == objEscapeByFinalize){
System.out.println("obj has been gc");
}else{
System.out.println("obj escape success");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("come in method:finalize");
super.finalize();
objEscapeByFinalize = this;
}
}
运行结果为:
come in method:finalize
obj escape success
obj has been gc
从结果可以看到,第一次自救成功,而第二次已经没有了自救机会,因为当前对象已经执行过一次finalize()方法了,而如果我们把finalize()方法中的:
objEscapeByFinalize = this;
替换为:
objEscapeByFinalize = new ObjEscapeByFinalize();
这时候就可以一直自救成功,因为每次自救之后就产生了一个新的对象,新的对象并没有执行过finalize()方法。
上面的demo还有一点需要注意的是,finalize()方法针对的是对象,假如上面的静态对象换成一个其他对象,而finalize()方法又写在当前对象,那么是无效的,例如如下例子:
package com.zwx.jvm;
import java.util.ArrayList;
import java.util.List;
public class ObjEscapeByFinalize1 {
public static List
这里是无法实现自救的,因为这里要救的对象是List,而finalize()并不属于List,是属于ObjEscapeByFinalize1对象,所以这一点也是需要明确地。
不过虽然finalize()可以完成对象自救,但是由于这个方法的代价比较大而且运行时有不确定性,一般情况下还是不建议使用
不管是什么类型的GC,都会有 stop-the-world,只是发生时间的长短,目前Java中所有的垃圾回收器均需要STW,唯一的区别只是时间的长短问题。
之前我们提到了,Major GC通常会伴随着Minor GC,也就等于触发了Full GC,但是虽然如此,Major GC和Full GC并不是完全等价的,因为Full GC 的同时会对方法区(jdk1.8的metaspace,jdk1.7的永久代)进行GC,所以严格来说:Full GC=Major GC+Minor GC+方法区GC
答案是肯定的。虽然方法区中的回收收益一般都不高,但是也是会被GC的,而方法区中被回收的最主要的就是对废弃常量和无用类的回收,判定一个废弃常量比较简单,但是判定一个类是无用类是比较困难的,那么方法区中的怎么判断一个类是无用类呢?
判断一个类是否无用,需要达到以下三个条件:
这三个条件实际上是非常苛刻的,而即使达到以上三个条件,无用类也仅仅是可以被回收,但是是不是一定会被回收,还是取决于Java虚拟机。HotSpot虚拟机中提供了参数-Xnoclassgc来控制。
直接内存(Direct Memory)不属于运行时数据区,也被称之为堆外内存,通常访问直接内存的速度会优于Java堆。直接没存也有可能会发生OutOfMemoryError异常,Java 1.4中新加入的nio包下的ByteBuffer就操作了直接内存,直接内存可以通过参数-XX:MaxDirectMemorySize控制大小。
作为同样是并行的2款垃圾收集器,G1的目前是用来取代CMS收集器的,其主要有如下区别:
类加载机制主要经过了:加载(Loading),连接(Linking),初始化(Initialization),使用(Using),卸载(Unloading) 五个大阶段,而其中连接(Linking)又分为:验证(Verification),准备(Preparation),**解析(Resolution)**三个阶段。想要详细了解每个阶段做了什么事情,可以点击这里。
jstack -l 进程PID >jstack.log
输出之后,我们找到上面占用CPU最高的一个线程pid=11566,将其转换为16进制,得到的结果是2d2e,然后进入生成的jstack.log文件找到这个线程可以查看线程信息。
本文主要列举了一些其他比较经典的,而前面在JVM系列其他文章中又没有过多进行说明的问题,JVM学习之后需要不断实战积累调优经验,虽然还有一些理论在JVM系列中没有提及,但是我想如果可以认真把我 JVM系列至本篇为止的8篇文章相关知识和理论都掌握的话,那至少可以说已经具备了调优的理论基础了,剩下的就是不断积累经验,当然,推荐大家可以去通读一下JVM规范,毕竟所有的Java虚拟机都是按照JVM规范来实现的,或者有必要的可以自己去编译JDK来进行更深一步的研究。
页面更新:2024-04-22
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号