深入JVM虚拟机(三) Java GC垃圾收集

深入JVM虚拟机(三)
Java GC垃圾收集

1 Java GC垃圾收集

1.1 GC的概念

Java GC(Garbage
Collection,垃圾收集,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对
JVM(Java Virtual Machine)中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息(Nerver
Stop)的保证JVM中的内存空间,放置出现内存泄露和溢出问题。

1.2 GC算法

1、引用计数法:

引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。

缺点:

-         引用和去引用伴随加法和减法,影响性能

-         很难处理循环引用

图中3个对象引用值都为1,它们都不可回收。

注:在JAVA中未使用引用计数法。

2、标记清除法:

标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。

标记出存活对象,将未标记的垃圾对象全部清降,或者标记出拉圾对象,将拉圾对象全部清除。

3、标记压缩法:

标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。

4、复制算法:

优势:与标记-清除算法相比,复制算法是一种相对高效的回收方法。不适用于存活对象较多的场合如老年代。

原理:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

缺点:空间浪费

优化:整合标记清理思想:

-         将大对象复制到担保空间(保留空间)回收垃圾几次后,将大对象放到老年代。左侧的表格中将小对象复制到右侧表格中空闲空间。最后清空原来使用的空间。

5、分代思想:

依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。根据不同代的特点,选取合适的收集算法:

-         少量对象存活,适合复制算法。

-         大量对象存活,适合标记清理或者标记压缩。

1.3 可触及性

1、可触及性:

可触及的:从根节点开始进行扫描,可以触及到这个对象,那么这个对象就是可触及的。

可复活的:一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象。

不可触及的:在finalize()后,可能会进入不可触及状态,不可触及的对象不可能复活,可以回收。

JAVA代码:

public class CanReliveObj {
	public static CanReliveObj obj;

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("CanReliveObj finalize called");
		// GC垃圾回收器,只会调用一次finalize(),obj赋值当前对象,变成了可触及状态
		obj = this;
	}

	@Override
	public String toString() {
		return "I am CanReliveObj";
	}

	public static void main(String[] args) throws InterruptedException {
		// 声明对象
		obj = new CanReliveObj();
		// 将对象赋值给null,一般赋值为null,垃圾回收器,将回收值为null的对象
		obj = null; // 可复活

		// 调用gc()方法,调用对象的finalize()方法,此时obj赋值this
		System.gc();

		// 当前线程睡眠1秒
		Thread.sleep(1000);
		if (obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}

		/*
		 * 由于finalize()方法只会在调用gc()的时候调用一次,
		 * 调用gc()方法过后,不会再调用finalize()方法,此时对象为null。
		 */
		System.out.println("第二次gc");
		obj = null; // 不可复活
		System.gc();
		Thread.sleep(1000);
		if (obj == null) {
			System.out.println("obj 是 null");
		} else {
			System.out.println("obj 可用");
		}
	}
}

经验:

避免使用finalize(),操作不慎可能导致错误。

优先级低,何时被调用,不确定,何时发生GC不确定。

可以使用try-catch-finally来替代它。

2、根:

-         栈中引用的对象

-         方法区中静态成员或者常量引用的对象(全局对象)

-         JNI方法栈中引用对象

1.4 Stop-The-World

产生Stop The World的原因:

Java中一种全局暂停的现象,所有的线程全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互,多半由于GC引起。

当GC开始工作时,将现在在进行的线程全部都停止,以保证不会再有先的垃圾产生。如果不能暂定正在进行线程,垃圾清理的清况就无法得到保证(Sun将这件事情称为“Stop
The World”)。

影响:

长时间服务停止,没有响应。

遇到HA系统,可能引起主备切换,严重危害生产环境。

Java代码:

	public class PrintThread extends Thread{
		public static final long starttime=System.currentTimeMillis();
		@Override
		public void run(){
			try{
				while(true){
					long t=System.currentTimeMillis()-starttime;
					System.out.println("time:"+t);
					Thread.sleep(100);
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		public static void main(String[] args) {
			PrintThread printThread = new PrintThread();
			printThread.start();
		}
	}

预期,应该是每秒中有10条输出:


执行结果


GC垃圾回收日志


time:2018

time:2121

time:2221

time:2325

time:2425

time:2527

time:2631

time:2731

time:2834

time:2935

time:3035

time:3153

time:3504

time:4218

time:4349

time:4450

time:4551


3.292: [GC3.292: [DefNew: 959K->63K(960K), 0.0024260 secs] 523578K->523298K(524224K), 0.0024879 secs] [Times:
user=0.02 sys=0.00, real=0.00 secs]

3.296: [GC3.296: [DefNew: 959K->959K(960K), 0.0000123 secs]3.296: [Tenured: 523235K->523263K(523264K), 0.2820915
secs] 524195K->523870K(524224K), [Perm : 147K->147K(12288K)], 0.2821730 secs] [Times: user=0.26 sys=0.00, real=0.28 secs]

3.579: [Full GC3.579: [Tenured: 523263K->523263K(523264K), 0.2846036 secs] 524159K->524042K(524224K), [Perm
: 147K->147K(12288K)], 0.2846745 secs] [Times: user=0.28 sys=0.00, real=0.28 secs]

3.863: [Full GC3.863: [Tenured: 523263K->515818K(523264K), 0.4282780 secs] 524042K->515818K(524224K), [Perm
: 147K->147K(12288K)], 0.4283353 secs] [Times: user=0.42 sys=0.00, real=0.43 secs]

4.293: [GC4.293: [DefNew: 896K->64K(960K), 0.0017584 secs] 516716K->516554K(524224K), 0.0018346 secs] [Times:
user=0.00 sys=0.00, real=0.00 secs]

……省略若干…..

4.345: [GC4.345: [DefNew: 960K->960K(960K), 0.0000156 secs]4.345: [Tenured: 522929K->12436K(523264K), 0.0781624
secs] 523889K->12436K(524224K), [Perm : 147K->147K(12288K)], 0.0782611 secs] [Times:
user=0.08 sys=0.00, real=0.08 secs]

红色加粗的地方是GC引起的线程停顿(Stop
The World)现象。垃圾回收的时间基本上是等于停顿的时间。

2 GC参数设置

2.1 简要GC信息

在Eclipse中设置eclipse.ini文件。

3、打开eclipse安装的根目录下%{Eclipse_HOME}\eclipse.ini,在文件的末尾添加参数:

-XX:+PrintGC

-verbose:gc

-Xloggc:../logs/jvm-gc/gc.log

4、PrintGC打印GC的简要信息

[GC 4790K->374K(15872K), 0.0001606 secs]

[GC 4790K->374K(15872K), 0.0001474 secs]

[GC 4790K->374K(15872K), 0.0001563 secs]

[GC 4790K->374K(15872K), 0.0001682 secs]

GC之前使用:4790K

GC之后使用:374K

整个堆的大小:15872K

2.2 详细GC信息

1、打开eclipse安装的根目录下%{Eclipse_HOME}\eclipse.ini,在文件的末尾添加参数:

-XX:+PrintHeapAtGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-verbose:gc

-Xloggc:../logs/jvm-gc/gc.log

2、PrintGCDetails打印详信息,PrintGCTimeStamps打印时间戳,如:

[GC[DefNew: 4416K->0K(4928K), 0.0001897secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]

GC之前使用:4416K

GC之后使用:0

整个堆的大小:4928K

3、PrintHeapAtGC打印详信息

程序运行结束后会将整个堆的运行状态,打进行印:

Heap

def new generation   total13824K, used 11223K [0x27e80000,0x28d80000,0x28d80000)

eden space12288K, 
91% used [0x27e80000, 0x28975f20, 0x28a80000)

from space1536K,  
0% used [0x28a80000, 0x28a80000, 0x28c00000)

to   space1536K,  
0% used [0x28c00000,0x28c00000, 0x28d80000)

tenured generation   total5120K, used 0K [0x28d80000, 0x29280000, 0x34680000)

the space 5120K,   0%used [0x28d80000, 0x28d80000, 0x28d80200, 0x29280000)

compacting perm gen  total 12288K,used 142K [0x34680000, 0x35280000, 0x38680000)

the space 12288K,   1%used [0x34680000, 0x346a3a90, 0x346a3c00, 0x35280000)

ro space 10240K,  44%used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)

rw space 12288K,  52% used [0x39080000,0x396cdd28, 0x396cde00, 0x39c80000)

新生代def new generation空间共:total
13824K

已经使用:used 11223K

低边界:0x27e80000

当前边界:0x28d80000

最高边界:0x28d80000

新生代内存:(0x28d80000-0x27e80000)/1024/1024=15M

新生代总合:(12288K+1536K+1536K) /1024=15M

新生代可申请内存:13824K = 12288K + 1536K

生成对象eden空间:space12288K,使用量为91%

新生代from to两个值是相等的。

老年代tenured generation空间共:total
5120K

已经使用:used 0k

方法区compacting perm gen共total
12288K

已经使用:used 142K

2.3 指定最大堆和最小堆

-Xmx参数:最大堆

-Xms参数:最小堆

1、-Xmx1024 –Xms256m

运行代码:

        public static void main(String[] args) {
            System.out.print("Xmx=");
            System.out.println(Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M");
    
            System.out.print("freemem=");
            System.out.println(Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M");
    
            System.out.print("totalmem=");
            System.out.println(Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M");
        }

运行结果:

Xmx=1811.5M

freemem=120.04971313476562M

total mem=122.0M

2、堆分配参数总结:

-  根据实际事情调整新生代和幸存代的大小。

-  官方推存新生代占堆的3/8

-  幸存代占新生代的1/10

3、内存参数总结:


参数名称


含义


默认值


说明


-Xms


初始堆大小


物理内存的1/64(<1GB)


默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.


-Xmx


最大堆大小


物理内存的1/4(<1GB)


默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到
-Xms的最小限制


-Xmn


年轻代大小(1.4or lator)


 


注意:此处的大小是(eden+
2 survivor space).与jmap -heap中显示的New gen是不同的。


整个堆大小=年轻代大小 +年老代大小 +持久代大小.


增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8


-XX:NewSize


设置年轻代大小(for 1.3/1.4)


 


 


-XX:MaxNewSize


年轻代最大值(for 1.3/1.4)


 


 


-XX:PermSize


设置持久代(perm gen)初始值


物理内存的1/64


 


-XX:MaxPermSize


设置持久代最大值


物理内存的1/4


 


-Xss


每个线程的堆栈大小


 


JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行
调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右


一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长)


和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"”


-Xss is translated in a VM flag named ThreadStackSize”


一般设置这个值就可以了。


-XX:ThreadStackSize


Thread Stack Size


 


(0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64
bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]


-XX:NewRatio


年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)


 


-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5


Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。


-XX:SurvivorRatio


Eden区与Survivor区的大小比值


 


设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10


-XX:LargePageSizeInBytes


内存页的大小不可设置过大, 会影响Perm的大小


 


=128m


-XX:+UseFastAccessorMethods


原始类型的快速优化


 


 


-XX:+DisableExplicitGC


关闭System.gc()


 


这个参数需要严格的测试


-XX:MaxTenuringThreshold


垃圾最大年龄


 


如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代.对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活
时间,增加在年轻代即被回收的概率


该参数只有在串行GC时才有效.


-XX:+AggressiveOpts


加快编译


 


 


-XX:+UseBiasedLocking


锁机制的性能改善


 


 


-Xnoclassgc


禁用垃圾回收


 


 


-XX:SoftRefLRUPolicyMSPerMB


每兆堆空闲空间中SoftReference的存活时间


1s


softly reachable objects will remain alive for some amount of time after the last time they were referenced.
The default value is one second of lifetime per free megabyte in the heap


-XX:PretenureSizeThreshold


对象超过多大是直接在旧生代分配


0


单位字节 新生代采用Parallel Scavenge GC时无效


另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象.


-XX:TLABWasteTargetPercent


TLAB占eden区的百分比


1%


 


-XX:+CollectGen0First


FullGC时是否先YGC


FALSE


 

4、辅助信息


-XX:+PrintGC


 


输出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs]

[Full GC 121376K->10414K(130112K), 0.0650971 secs]


-XX:+PrintGCDetails


 


输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K),
0.0436268 secs]


 


 


 


-XX:+PrintGCTimeStamps


 


 


-XX:+PrintGC:PrintGCTimeStamps


 


可与-XX:+PrintGC -XX:+PrintGCDetails混合使用

输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]


-XX:+PrintGCApplicationStoppedTime


打印垃圾回收期间程序暂停的时间.可与上面混合使用


输出形式:Total time for which application threads were stopped: 0.0468229 seconds


-XX:+PrintGCApplicationConcurrentTime


打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用


输出形式:Application time: 0.5291524 seconds


-XX:+PrintHeapAtGC


打印GC前后的详细堆栈信息


 


-Xloggc:filename


把相关日志信息记录到文件以便分析.


 


与上面几个配合使用


-XX:+PrintClassHistogram


garbage collects before printing the histogram.


 


-XX:+PrintTLAB


查看TLAB空间的使用情况


 


XX:+PrintTenuringDistribution


查看每次minor GC后新的存活周期的阈值


 


-XX:+PrintTLAB


查看TLAB空间的使用情况


 


XX:+PrintTenuringDistribution


查看每次minor GC后新的存活周期的阈值


Desired survivor size 1048576 bytes, new threshold 7 (max 15)


new threshold 7即标识新的存活周期的阈值为7。

--以上为《深入JVM虚拟机(三) Java GC垃圾收集》,如有不当之处请指出,我后续逐步完善更正,大家共同提高。谢谢大家对我的关注。

——厚积薄发(yuanxw)

时间: 2024-07-28 13:12:23

深入JVM虚拟机(三) Java GC垃圾收集的相关文章

深入JVM虚拟机(四) Java GC收集器

深入JVM虚拟机(四) Java GC收集器 1 GC收集器 1.1 Serial串行收集器 串行收集器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收:第二,它独占式的垃圾回收. 在串行收集器进行垃圾回收时,Java 应用程序中的线程都需要暂停("StopThe World"),等待垃圾回收的完成,这样给用户体验造成较差效果.虽然如此,串行收集器却是一个成熟.经过长时间生产环境考验的极为高效的收集器.新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销

java - GC垃圾收集器详解(三)

以前收集器的特点 年轻代和老年代是各自独立且连续的内存块 年轻代收集必须使用单个eden+S0+S1进行复制算法 老年代收集扫描整个老年代区域 都是以尽可能少而快速地执行GC为设计原则 G1是什么 G1(Garbage-Frist)收集器,是一款面向服务端应用的收集器 从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在提高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求.另外,它还具有以下特性: 像CMS收集器一样,能与应用程序线程并发执行 整理空闲空间

[深入理解JVM虚拟机]第3章-垃圾收集器、内存分配策略

垃圾收集器 判断对象是否需存活 回收堆 判断对象是否存活: 方法一:引用计数法.对象被引用一次就+1,当为0时回收对象.缺点:无法解决循环引用问题. 方法二:可达性分析算法.记录当前对象是否有和GC Roots中对象的引用链.(其中,可以作为GCRoots对象的有:虚拟机栈中引用的对象.方法去中类静态属性引用的对象.方法区中常量引用的对象.本地方法栈中引用的对象.) 不可达对象并不是一定被垃圾收集的,当这个对象有必要执行finalize()并finalize里自己和某个对象建立关联,即可在第二次

JVM虚拟机垃圾回收(GC)算法及优缺点

一.什么是GC GC是jvm的垃圾回收,垃圾回收的规律和原则为: 次数上频繁收集新生区(Young) 次数上较少收集养老区(Old) 基本上不动永久区(Perm) 二.GC算法(分代收集算法) GC总共有四大算法,分别是: ①引用计数法 ②复制算法(Copying) ③标记清除(Mark-Sweep) ④标记压缩(Mark-Compact) ⑤标记清除压缩(Mark-Sweep-Compact) 1.1 引用计数法 1.2 复制算法(Copying) 复制算法主要用在新生代中. 1.2.1 复制

java - GC垃圾收集器详解(二)

CMS收集器 CMS收集器(ConcurrentMarkSweep:并发标记清除)是一种以获取最短回收停顿时间为目标的收集器. 适合应用在互联网站或者B/S系统的服务器上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短. CMS非常适合堆内存大.CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器. Concurrent Mark Sweep 并发标记清除,并发收集低停顿,并发指的是与用户线程一起执行 开启该收集器的JVM参数:-XX:+UseConcMarkSreepGC,

深入理解JVM虚拟机开篇:JVM介绍与知识脉络梳理

微信公众号[Java技术江湖]一位阿里 Java 工程师的技术小站.作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux.网络.多线程,偶尔讲点Docker.ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”Java“即可领取 Java基础.进阶.项目和架构师等免费学习资料,更有数据库.分布式.微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南.Java程序员面试指

Java GC 总结

Java GC垃圾收集器 概述 关于JavaGC,有三件事情需要了解 1.那些内存需要回收 2.什么时候回收 3.这么回收 这么判断对象已死 引用计数法 用一个计数器表示对象的引用,对象被引用就+1,,引用失效-1.不过这个算法有一个问题,就是对象的相互引用,会使对象无法回收.比如A引用B,B引用A,这两个对象实际上已经没用了,但是无法回收.现在的虚拟机都不会通过该算法来判断对象 可达性分析算法 这个算法通过一系列称为"GC Roots"的对象作为起始点,当一个对象到GC Roots没

Android虚拟机与Java虚拟机 两种虚拟机的比较

在Android的体系框架中有一部分叫做Android Runtime,即Android运行时环境,这个环境包括了两个部分,一个是Android的核心类库,还有一个就是Dalvik虚拟机了. Android之所以开发Dalvik虚拟机而不使用JAVA自带的JVM是出于以下两点考虑(个人认为,不代表广泛意义): 1.版权问题,如果使用JVM就涉及到了版权问题,所以google需要在JVM的基础上做一些改进,创造自己的虚拟机. 2.性能问题.当然jvm虚拟机对Java开发来说性能已经足够了,但是相对

java内功 ---- jvm虚拟机原理总结,侧重于GC

写作日期 2016-08-22-23 交流qq:992591601 参考资料:<深入理解java虚拟机>.<thinking in java>.<Effective Java> 直接从最要紧的地方讲,Java GC算法.需说明一点,GC机制只是涉及堆内存的.因为堆内存是动态的,在程序运行期间分配的. 一.判断什么需要被回收: 1.引用计数法 问题在于两个对象互相引用,然后各种指向null.堆内存中的对象是不会被回收的. 上图是体现一种变化,绘画技巧不行,用绿框表示对象成