Java虚拟机内存原型
寄存器:咱们在程序没法控制
栈:存放基本类型的数据和对象的引用,但对象自己不存放在栈中,而是堆中
存取速度比堆块,仅次于寄存器,栈数据能够共享,栈的数据大小与生存期必须是肯定的,缺少灵活性。
堆:存放new产生的数据
能够动态分配内存大小,生存期也没必要事先告诉编译器,由于它在运行时动态分配内存,Java的垃圾收集器会自动收走这些再也不使用的数据,但缺点是,因为在运行时分配内存,存取速度较慢
静态域:存放在对象中用static定义的静态成员
常量池:存放常量(利用final关键字修饰的)
非RAM存储:硬盘等永久存储空间html
Java引用的种类
>对象在内存中状态
对于JVM的垃圾回收机制来讲,若是一个对象,没有一个引用指向它,那么它就被认为是一个垃圾。那该对象就会回收。能够把JVM内存中对象引用理解成一种有向图,把引用变量、对象都当成有向图的顶点,将引用关系当成图的有向边(注意:有向边老是从引用变量指向被引用的Java对象)
一、可达状态
当一个对象被建立后,有一个或一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序能够经过引用变量来调用该对象的方法和属性。
二、可恢复状态
若是程序中某个对象再也不有任何引用变量引用它,它就进入了可恢复状态,在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存空间,在回收该对象以前,系统会调用全部对象的finalize方法进行资源的清理,若是系统在调用finalize方法从新让一个引用变量引用该对象,则这个对象会再次变为激活状态,不然该 对象状进入不可达状态。
三、不可达状态
当对象与全部引用变量的关联都被切继,且系统已经调用全部对象的finalize方法依然没有该对象变成可达状态,那这个对象将永久性地失去引用,最后变成不可达状态,只有当一个对象处于不可达状态时统才会真正回收该对象所占有的资源。java
*对象的状态转换图以下:
程序员
>强引用
强引用是Java编程中使用普遍的引用类型,被强引用所引用的Java对象毫不会被垃圾回收,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足的问题,所以强引用是形成Java内存泄漏的主要缘由之一
*代码示例算法
class Person
{
String name;
int age; public Person(String name,int age) { this.name=name; this.age=age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class ReferenceTest { public static void main(String[] args) {
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
运行结果(结果说明内存仍是足够的)
咱们来把修改java虚拟机内存,把堆内存减小到2m
(操做:程序右键选属性->run/debug settings->选中应用程序->编辑->Arguments->VM arguments输入框输入 -Xmx2m -Xms2m )
再运行(程序由于内存不足而停止)
数据库
>软引用
软引用经过SoftReference类来实现,当系统内存空间足够,软引用的对象不会被系统回收,程序也可使用该对象,当系统内存不足时,系统将会回收
*代码示例编程
运行结果(结果说明内存足够的)
修改java虚拟机内存,把堆内存减小到2m,再运行
从运行结果能够看出,内存不足时,垃圾回收机制会回收SoftReference引用的对象数组
>弱引用
弱引用与软引用有点类似,区别是弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。
弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
*代码示例缓存
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
内存分配图
运行结果
从运行结果看出,当系统垃圾回收机制启动后,弱引用的对象就会被清除掉。null表面该对象已经被清除了安全
>虚引用
虚引用经过PhantomReference类实现,相似没有引用,主要做用是跟踪对象被垃圾回收的状态,程序能够经过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,从而了解虚引用所引用的对象是否即将被回收,虚引用不能单独使用,必需要和引用队列(ReferenceQueue)联合使用
*代码示例
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
运行结果
从运行结果能够看出,在未强制进行垃圾回收,程序输出null,说明系统没法经过虚引用访问被引用的对象,当程序强制回收垃圾后,虚引用引用的对象被回收,而后该引用会添加到关联的引用队列中,因此输出true,因此说程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收
Java的内存泄漏
无用对象(再也不使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而形成的内存空间的浪费,就是内存泄漏
一、静态变量引发内存泄露:
根据分代回收机制(后面有讲),JVM会将程序中obj引用变量存在Permanent代里,这致使Object对象一直有效,从而使obj引用的Object得不到回收
例:
class Person { static Object obj=new Object(); }
二、当集合里面的对象属性被修改后,再调用remove()方法时不起做用。
例:
public static void main(String[] args) { Set<Person> set = new HashSet<Person>(); Person p1 = new Person("唐僧","pwd1",25); Person p2 = new Person("孙悟空","pwd2",26); Person p3 = new Person("猪八戒","pwd3",27); set.add(p1); set.add(p2); set.add(p3); System.out.println("总共有:"+set.size()+" 个元素!");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
三、监听器
在java 编程中,咱们都须要和监听器打交道,一般一个应用当中会用到不少监听器,咱们会调用一个控件的诸如addXXXListener()等方法来增长监听器,但每每在释放对象的时候却没有记住去删除这些监听器,从而增长了内存泄漏的机会。
四、各类链接
好比数据库链接(dataSourse.getConnection()),网络链接(socket)和io链接,除非其显式的调用了其close()方法将其链接关闭,不然是不会自动被GC 回收的。对于Resultset 和Statement 对象能够不进行显式回收,但Connection 必定要显式回收,由于Connection 在任什么时候候都没法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会当即为NULL。可是若是使用链接池,状况就不同了,除了要显式地关闭链接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另一个也会关闭),不然就会形成大量的Statement 对象没法释放,从而引发内存泄漏。这种状况下通常都会在try里面去的链接,在finally里面释放链接。
五、内部类和外部模块等的引用
内部类的引用是比较容易遗忘的一种,并且一旦没释放可能致使一系列的后继类对象没有释放。此外程序员还要当心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要很是当心了,传入了一个对象,极可能模块B就保持了对该对象的引用,这时候就须要注意模块B 是否提供相应的操做去除引用。
六、单例模式
单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),若是单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,致使内存泄露,考虑下面的例子:
class A{
public A(){ B.getInstance().setA(this); } .... }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下若是A是个比较复杂的对象或者集合类型会发生什么状况
垃圾回收机制
垃圾回收的基本算法
标记压缩法
先从根节点开始对全部可达对象作一次标记,但以后,它并不简单地清除未标记的对象,而是将全部的存活对象压缩到内存的一端以后,清理边界外全部的空间。这种方法既避免了碎片的产生,又不须要两块相同的内存空间,所以,其性价比比较高。
标记回收法
从“GC Roots”(GC Roots指垃圾收集器的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象)集合开始,将内存整个遍历一次,保留全部被GC Roots直接或者间接引用到的对象,而剩下的对象都看成垃圾对待并回收,这个算法须要中断进程内其余组件的执行而且可能产生碎片化
复制回收法
将内存分为大小相等的两部分(假设A、B两部分),每次呢只使用其中的一部分(这里咱们假设为A区),等这部分用完了,这时候就将这里面还能活下来的对象复制到另外一部份内存(这里设为B区)中,而后把A区中的剩下部分所有清理掉。这样内存碎片的问题就解决了

分代回收法
根据对象的生命周期将内存划分,而后进行分区管理,在Java虚拟机分代垃圾回收机制中,应用程序可用的堆空间能够分为年轻代与老年代,年轻代有被分为Eden区,From区与To区
分代回收法更详细连接http://blog.csdn.net/sinat_36246371/article/details/52998505
>堆内存的分代回收
>与垃圾回收的附加选项
下面两个选项用于设置java虚拟机内存大小
-Xms :设置java虚拟机堆内存的最大容量如java -Xmx256m XxxClass
-Xms :设置java虚拟机堆内存的初始容量,如java -Xms128m XxxClass
下面选项都是关于java垃圾回收的附加选项
-xx:MinHeapFreeRatio =40 :设置java堆内存最小的空闲百分比,默认为40,如java -xx:MinHeapFreeRadio = 40 XxxClass
-xx:MaxHeapFreeRatio=70 :设置Java堆内存最大的空闲百分比,默认为70,如java -XX:MaxHeapFreeRatio =70 XxxClass
-xx:NewRatio=2 ;设置Yonng/Old内存的比例,如java -XX:NewRatio=1 XxxClass
-xx:NewSize=size:设置Yonng代内存的默认容量,如java -XX:Newsize=64m XxxClass
-xx:SurvivorRatio = 8;设置Yonng代中eden/survivor的比例,如java -xx:MaxNewSize=128m XxxClass
注意 当设置Young代的内存超过了-Xmx设置的大小时,Young设置的内存大小将不会起做用,JVM会自动将Young代内存设置为与-Xmx设置的大小相等。
-XX:PermSIze=size;设置Permnanent代内存的默认容量,如java –XX:PermSize=128m XxxClass
-XX:MaxPermSize=64m;设置Permanent代内存的最大容量,如java -XX:MaxPermSize=128m XxxClass
>常见垃圾回收器
- 串行回收器(Serial Garbage Collector)
Serial Garbage Collector经过暂停全部应用的线程来工做。它是为单线程工做环境而设计的。它中使用一个线程来进行垃圾回收。这种暂停应用线程来进行垃圾回收的方式可能不太适应服务器环境。它最适合简单的命令行程序。
经过 -XX:+UseSerialGC 参数来选用Serial Garbage Collector。
- Parallel Garbage Collector
Parallel Garbage Collector也被称为吞吐量收集器(throughput collector)。它是Java虚拟机的默认垃圾收集器。与Serial Garbage Collector不一样,Parallel Garbage Collector使用多个线程进行垃圾回收。与Serial Garbage Collector类似的地方时,它也是暂停全部的应用线程来进行垃圾回收。
3. CMS Garbage Collector
Concurrent Mark Sweep (CMS) Garbage Collector使用多个线程来扫描堆内存来标记须要回收的实例,而后再清除被标记的实例。CMS Garbage Collector只有在以下两种情景才会暂停全部的应用线程:
当标记永久代内存空间中的对象时;
当进行垃圾回收时,堆内存同步发生了一些变化。
相比Parallel Garbage Collector,CMS Garbage Collector使用更多的CPU资源来确保应用有一个更好的吞吐量。若是分配更多的CPU资源能够得到更好的性能,那么CMS Garbage Collector是一个更好的选择,相比Parallel Garbage Collector。
经过 XX:+USeParNewGC 参数来选用CMS Garbage Collector。
内存管理的小技巧
>尽可能使用直接量
当须要使用字符串,还有Byte,Short、integer、Long、Float、Double、Boolean、Character包装类的实例时,不该该采用new的方式来建立对象,而应该使用直接量来建立它们
应该是String str="hello";
而不是String str=new String("hello");
后者除了在建立一个缓存在字符串缓冲池的“hello”字符串,str所引用的String对象底层还包含一个存放了h、e、l、l、o的char[ ]数组
>使用StringBuilder和StringBuffer进行字符串链接
String表明字符序列不可变的字符串,StringBuilderheStringBuffer都表明字符序列可变的字符串
建议
StringBuilder st = new StringBuilder()
不建议
String c = a+b;
由于这样会在运行的时候生成大量临时字符串,这些字符串会保存在内存中从而致使程序性能降低
若是使用少许的字符串操做,使用 (+运算符)链接字符串;
若是频繁的对大量字符串进行操做,则使用
1:全局变量或者须要多线程支持则使用StringBuffer;
2:局部变量或者单线程不涉及线程安全则使有StringBuilder
>尽早释放无用对象的引用
>尽可能少用静态变量
>避免在常常调用的方法、循环中建立Java对象
>缓存常用的对象
>尽可能不要使用finalize方法
>考虑使用SoftReference
参考:《疯狂java 突破程序员基本功的16课》