jvm 知识
Posted xxMYxx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jvm 知识相关的知识,希望对你有一定的参考价值。
Java中并没有一个类似的运算符。事实上,Java也不需要这种运算符。Java中基本类型的大小在语言规范中已经定义了,而C/C++中基本类型大小则跟平台相关。Java有自己的通过序列化构建的IO框架。再者,由于Java中没有指针,因此指针运算和内存块拷贝之类的操作也不存在。但是,Java程序员有时还是希望能知道一个Java对象到底用了多少内存的。不过这个问题的答案并不简单。
首先要区分清楚的是shallow size和deep size。Shallow size是指对象自身占用的内存大小,其引用对象的大小不算在内。而deep size,则是自身所占内存大小和其递归引用的所有对象所占内存大小的总和。大多数情况下,你会希望获得一个对象的deep size,但是为了知道这个值,首先要知道怎么算shallow size,下面我来介绍一下。
有人抱怨JVM规范中没有针对运行时Java对象的内存结构的说明,这也就是说JVM供应商可以按照自己的需要来实现这一点。后果就是,同一个类在不同的JVM上运行的实例对象占用的内存大小会有差别。好在是世界上大部分人(包括我在内)都使用Sun HotSpot虚拟机,这就大大简化了这个问题。我们接下来的讨论也会基于32位的Sun公司的JVM。下面我介绍一些规则来辅助解释JVM如何组织对象在内存中的布局的。
没有实例属性的类的内存布局
在Sun JVM中,(除了数组之外的)对象都有两个机器字(words)的头部。第一个字中包含这个对象的标示哈希码以及其他一些类似锁状态和等标识信息,第二个字中包含一个指向对象的类的引用。另外,任何对象都是8个字节为粒度进行对齐的。这就是对象内存布局的第一个规则:
规则1:任何对象都是8个字节为粒度进行对齐的。
比如,如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。
继承了Object的类的内存布局
除了上面所说的8个字节的头部,类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是性价比更高的。
为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:
1. 双精度型(doubles)和长整型(longs)
2. 整型(ints)和浮点型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布尔型(booleans)和字节型(bytes)
5. 引用类型(references)
内存使用率会通过这个机制得到优化。例如,如下声明一个类:
class MyClass
byte a;
int c;
boolean d;
long e;
Object f;
如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 4 bytes] 16
[d: 1 byte ] 17
[padding: 7 bytes] 24
[e: 8 bytes] 32
[f: 4 bytes] 36
[padding: 4 bytes] 40
此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:
[HEADER: 8 bytes] 8
[e: 8 bytes] 16
[c: 4 bytes] 20
[a: 1 byte ] 21
[d: 1 byte ] 22
[padding: 2 bytes] 24
[f: 4 bytes] 28
[padding: 4 bytes] 32
这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。
因此,对象内存布局的第二个规则是:
规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。
现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:
[HEADER: 8 bytes] 8
[value: 1 byte ] 9
[padding: 7 bytes] 16
Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。
继承其他类的子类的内存布局
JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下:
规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。
举例如下:
class A
long a;
int b;
int c;
class B extends A
long d;
类B的实例在内存中的存储如下:
[HEADER: 8 bytes] 8
[a: 8 bytes] 16
[b: 4 bytes] 20
[c: 4 bytes] 24
[d: 8 bytes] 32
如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:
规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。
举例如下:
class A
byte a;
class B
byte b;
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[b: 1 byte ] 13
[padding: 3 bytes] 16
注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。
最后一条规则在下面情况下用来节省一些空间:如果子类成员是长整型或双精度类型,并且父类并没有用完8个字节。
规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。
举例如下:
class A
byte a;
class B
long b;
short c;
byte d;
其内存布局如下:
7 [HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 2 bytes] 14
[d: 1 byte ] 15
[padding: 1 byte ] 16
[b: 8 bytes] 24
在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。
数组的内存布局
数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。
下面是一个有3个元素的字节数组的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[[0]: 1 byte ] 13
[[1]: 1 byte ] 14
[[2]: 1 byte ] 15
[padding: 1 byte ] 16
下面是一个有3个元素的长整型数字的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[padding: 4 bytes] 16
[[0]: 8 bytes] 24
[[1]: 8 bytes] 32
[[2]: 8 bytes] 40
内部类的内存布局
非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。
最后的一点想法
我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。
下一篇文章中,会有些示例代码,这些代码会把相关内容整理到一起,用反射(reflection)来计算一个对象的deep size。如果你感兴趣,请订阅此源或者等待这个博客的更新吧!
Java中并没有一个类似的运算符。事实上,Java也不需要这种运算符。Java中基本类型的大小在语言规范中已经定义了,而C/C++中基本类型大小则跟平台相关。Java有自己的通过序列化构建的IO框架。再者,由于Java中没有指针,因此指针运算和内存块拷贝之类的操作也不存在。
但是,Java程序员有时还是希望能知道一个Java对象到底用了多少内存的。不过这个问题的答案并不简单。
首先要区分清楚的是shallow size和deep size。Shallow size是指对象自身占用的内存大小,其引用对象的大小不算在内。而deep size,则是自身所占内存大小和其递归引用的所有对象所占内存大小的总和。大多数情况下,你会希望获得一个对象的deep size,但是为了知道这个值,首先要知道怎么算shallow size,下面我来介绍一下。
有人抱怨JVM规范中没有针对运行时Java对象的内存结构的说明,这也就是说JVM供应商可以按照自己的需要来实现这一点。后果就是,同一个类在不同的JVM上运行的实例对象占用的内存大小会有差别。好在是世界上大部分人(包括我在内)都使用Sun HotSpot虚拟机,这就大大简化了这个问题。我们接下来的讨论也会基于32位的Sun公司的JVM。下面我介绍一些规则来辅助解释JVM如何组织对象在内存中的布局的。
没有实例属性的类的内存布局
在Sun JVM中,(除了数组之外的)对象都有两个机器字(words)的头部。第一个字中包含这个对象的标示哈希码以及其他一些类似锁状态和等标识信息,第二个字中包含一个指向对象的类的引用。另外,任何对象都是8个字节为粒度进行对齐的。这就是对象内存布局的第一个规则:
规则1:任何对象都是8个字节为粒度进行对齐的。
比如,如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。
继承了Object的类的内存布局
除了上面所说的8个字节的头部,类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是性价比更高的。
为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:
1. 双精度型(doubles)和长整型(longs)
2. 整型(ints)和浮点型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布尔型(booleans)和字节型(bytes)
5. 引用类型(references)
内存使用率会通过这个机制得到优化。例如,如下声明一个类:
1
2
3
4
5
6
7
8
9
10
11
12
13 class MyClass
byte a;
int c;
boolean d;
long e;
Object f;
如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:
1
2
3
4
5
6
7
8
9 [HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 4 bytes] 16
[d: 1 byte ] 17
[padding: 7 bytes] 24
[e: 8 bytes] 32
[f: 4 bytes] 36
[padding: 4 bytes] 40
此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:
1
2
3
4
5
6
7
8 [HEADER: 8 bytes] 8
[e: 8 bytes] 16
[c: 4 bytes] 20
[a: 1 byte ] 21
[d: 1 byte ] 22
[padding: 2 bytes] 24
[f: 4 bytes] 28
[padding: 4 bytes] 32
这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。
因此,对象内存布局的第二个规则是:
规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。
现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:
1
2
3 [HEADER: 8 bytes] 8
[value: 1 byte ] 9
[padding: 7 bytes] 16
Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。
继承其他类的子类的内存布局
JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下:
规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。
举例如下:
1
2
3
4
5
6
7
8
9 class A
long a;
int b;
int c;
class B extends A
long d;
类B的实例在内存中的存储如下:
1
2
3
4
5 [HEADER: 8 bytes] 8
[a: 8 bytes] 16
[b: 4 bytes] 20
[c: 4 bytes] 24
[d: 8 bytes] 32
如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:
规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。
举例如下:
1
2
3
4
5
6
7
8
9
10
11
12 class A
byte a;
class B
byte b;
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[b: 1 byte ] 13
[padding: 3 bytes] 16
注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。
最后一条规则在下面情况下用来节省一些空间:如果子类成员是长整型或双精度类型,并且父类并没有用完8个字节。
规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。
举例如下:
1
2
3
4
5
6
7
8
9 class A
byte a;
class B
long b;
short c;
byte d;
其内存布局如下:
1
2
3
4
5
6
7 [HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 2 bytes] 14
[d: 1 byte ] 15
[padding: 1 byte ] 16
[b: 8 bytes] 24
在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。
数组的内存布局
数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。
下面是一个有3个元素的字节数组的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[[0]: 1 byte ] 13
[[1]: 1 byte ] 14
[[2]: 1 byte ] 15
[padding: 1 byte ] 16
下面是一个有3个元素的长整型数字的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[padding: 4 bytes] 16
[[0]: 8 bytes] 24
[[1]: 8 bytes] 32
[[2]: 8 bytes] 40
内部类的内存布局
非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。
最后的一点想法Java中并没有一个类似的运算符。事实上,Java也不需要这种运算符。Java中基本类型的大小在语言规范中已经定义了,而C/C++中基本类型大小则跟平台相关。Java有自己的通过序列化构建的IO框架。再者,由于Java中没有指针,因此指针运算和内存块拷贝之类的操作也不存在。
但是,Java程序员有时还是希望能知道一个Java对象到底用了多少内存的。不过这个问题的答案并不简单。
首先要区分清楚的是shallow size和deep size。Shallow size是指对象自身占用的内存大小,其引用对象的大小不算在内。而deep size,则是自身所占内存大小和其递归引用的所有对象所占内存大小的总和。大多数情况下,你会希望获得一个对象的deep size,但是为了知道这个值,首先要知道怎么算shallow size,下面我来介绍一下。
有人抱怨JVM规范中没有针对运行时Java对象的内存结构的说明,这也就是说JVM供应商可以按照自己的需要来实现这一点。后果就是,同一个类在不同的JVM上运行的实例对象占用的内存大小会有差别。好在是世界上大部分人(包括我在内)都使用Sun HotSpot虚拟机,这就大大简化了这个问题。我们接下来的讨论也会基于32位的Sun公司的JVM。下面我介绍一些规则来辅助解释JVM如何组织对象在内存中的布局的。
没有实例属性的类的内存布局
在Sun JVM中,(除了数组之外的)对象都有两个机器字(words)的头部。第一个字中包含这个对象的标示哈希码以及其他一些类似锁状态和等标识信息,第二个字中包含一个指向对象的类的引用。另外,任何对象都是8个字节为粒度进行对齐的。这就是对象内存布局的第一个规则:
规则1:任何对象都是8个字节为粒度进行对齐的。
比如,如果调用new Object(),由于Object类并没有其他没有其他可存储的成员,那么仅仅使用堆中的8个字节来保存两个字的头部即可。
继承了Object的类的内存布局
除了上面所说的8个字节的头部,类属性紧随其后。属性通常根据其大小来排列。例如,整型(int)以4个字节为单位对齐,长整型(long)以8个字节为单位对齐。这里是出于性能考虑而这么设计的:通常情况下,如果数据以4字节为单位对齐,那么从内存中读4字节的数据并写入到处理器的4字节寄存器是性价比更高的。
为了节省内存,Sun VM并没有按照属性声明时的顺序来进行内存布局。实际上,属性在内存中按照下面的顺序来组织:
1. 双精度型(doubles)和长整型(longs)
2. 整型(ints)和浮点型(floats)
3. 短整型(shorts)和字符型(chars)
4. 布尔型(booleans)和字节型(bytes)
5. 引用类型(references)
内存使用率会通过这个机制得到优化。例如,如下声明一个类:
1
2
3
4
5
6
7
8
9
10
11
12
13 class MyClass
byte a;
int c;
boolean d;
long e;
Object f;
如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:
1
2
3
4
5
6
7
8
9 [HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 4 bytes] 16
[d: 1 byte ] 17
[padding: 7 bytes] 24
[e: 8 bytes] 32
[f: 4 bytes] 36
[padding: 4 bytes] 40
此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:
1
2
3
4
5
6
7
8 [HEADER: 8 bytes] 8
[e: 8 bytes] 16
[c: 4 bytes] 20
[a: 1 byte ] 21
[d: 1 byte ] 22
[padding: 2 bytes] 24
[f: 4 bytes] 28
[padding: 4 bytes] 32
这次,用于占位的只有6个字节,这个对象使用了32个字节的内存空间。
因此,对象内存布局的第二个规则是:
规则2:类属性按照如下优先级进行排列:长整型和双精度类型;整型和浮点型;字符和短整型;字节类型和布尔类型,最后是引用类型。这些属性都按照各自的单位对齐。
现在我们知道如何计算一个继承了Object的类的实例的内存大小了。下面这个例子用来做下练习: java.lang.Boolean。这是其内存布局:
1
2
3 [HEADER: 8 bytes] 8
[value: 1 byte ] 9
[padding: 7 bytes] 16
Boolean类的实例占用16个字节的内存!惊讶吧?(别忘了最后用来占位的7个字节)。
继承其他类的子类的内存布局
JVM所遵守的下面3个规则用来组织有父类的类的成员。对象内存布局的规则3如下:
规则3:不同类继承关系中的成员不能混合排列。首先按照规则2处理父类中的成员,接着才是子类的成员。
举例如下:
1
2
3
4
5
6
7
8
9 class A
long a;
int b;
int c;
class B extends A
long d;
类B的实例在内存中的存储如下:
1
2
3
4
5 [HEADER: 8 bytes] 8
[a: 8 bytes] 16
[b: 4 bytes] 20
[c: 4 bytes] 24
[d: 8 bytes] 32
如果父类中的成员的大小无法满足4个字节这个基本单位,那么下一条规则就会起作用:
规则4:当父类中最后一个成员和子类第一个成员的间隔如果不够4个字节的话,就必须扩展到4个字节的基本单位。
举例如下:
1
2
3
4
5
6
7
8
9
10
11
12 class A
byte a;
class B
byte b;
[HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[b: 1 byte ] 13
[padding: 3 bytes] 16
注意到成员a被扩充了3个字节以保证和成员b之间的间隔是4个字节。这个空间不能被类B使用,因此被浪费了。
最后一条规则在下面情况下用来节省一些空间:如果子类成员是长整型或双精度类型,并且父类并没有用完8个字节。
规则5:如果子类第一个成员是一个双精度或者长整型,并且父类并没有用完8个字节,JVM会破坏规则2,按照整形(int),短整型(short),字节型(byte),引用类型(reference)的顺序,向未填满的空间填充。
举例如下:
1
2
3
4
5
6
7
8
9 class A
byte a;
class B
long b;
short c;
byte d;
其内存布局如下:
1
2
3
4
5
6
7 [HEADER: 8 bytes] 8
[a: 1 byte ] 9
[padding: 3 bytes] 12
[c: 2 bytes] 14
[d: 1 byte ] 15
[padding: 1 byte ] 16
[b: 8 bytes] 24
在第12字节处,类A“结束”的地方,JVM没有遵守规则2,而是在长整型之前插入一个短整型和一个字节型成员,这样可以避免浪费3个字节。
数组的内存布局
数组有一个额外的头部成员,用来存放“长度”变量。数组元素以及数组本身,跟其他常规对象同样,都需要遵守8个字节的边界规则。
下面是一个有3个元素的字节数组的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[[0]: 1 byte ] 13
[[1]: 1 byte ] 14
[[2]: 1 byte ] 15
[padding: 1 byte ] 16
下面是一个有3个元素的长整型数字的内存布局:
1
2
3
4
5 [HEADER: 12 bytes] 12
[padding: 4 bytes] 16
[[0]: 8 bytes] 24
[[1]: 8 bytes] 32
[[2]: 8 bytes] 40
内部类的内存布局
非静态内部类(Non-static inner classes)有一个额外的“隐藏”成员,这个成员是一个指向外部类的引用变量。这个成员是一个普通引用,因此遵守引用内存布局的规则。内部类因此有4个字节的额外开销。
最后的一点想法
我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。
下一篇文章中,会有些示例代码,这些代码会把相关内容整理到一起,用反射(reflection)来计算一个对象的deep size。如果你感兴趣,请订阅此源或者等待这个博客的更新吧!
我们已经学习了在32位Sun JVM中如何计算Java对象的shallow size。知道内存是如何组织的有助于理解类实例占用的内存数。
每个Java程序员迟早都会碰到下面这个错误:
java.lang.OutOfMemoryError
这个时候一般会建议采用如下方式解决这个错误:
增加MaxPermSize值
增加最大堆内存到512M(-xmx参数)
这篇文章会具体介绍Java堆空间和参数MaxPermSize的含义。这篇文章涉及下列主题,并采用Hotspot JVM:
垃圾回收器(Garbage Collector,GC)
哪个JVM?
JVM命令行选项
垃圾回收器
垃圾回收器负责:
分配内存
保证所有正在被引用的对象还存在于内存中
回收执行代码已经不再引用的对象所占的内存
应用执行时,定位和回收垃圾对象的过程会占用总执行时间的将近25%,这会拖累应用的执行效率。
Hotspot VM提供的垃圾回收器是一个分代垃圾回收器(Generational GC)[9,16,18]-将内存划分为不同的阶段,也就是说,不同的生命周期的对象放置在不同的地址池中。这样的设计是基于弱年代假设(Weak Generational Hypothesis):
1.越早分配的对象越容易失效;
2.老对象很少会引用新对象。
这种分代方式可以减少垃圾回收的停顿时间以及大范围对象的回收成本。Hotspot VM将其堆空间分为三个分代空间:
1. 年轻代(Young Generation)
○ Java应用在分配Java对象时,这些对象会被分配到年轻代堆空间中去
○ 这个空间大多是小对象并且会被频繁回收
○ 由于年轻代堆空间的垃圾回收会很频繁,因此其垃圾回收算法会更加重视回收效率
2. 年老代(Old Generationn)
○ 年轻代堆空间的长期存活对象会转移到(也许是永久性转移)年老代堆空间
○ 这个堆空间通常比年轻代的堆空间大,并且其空间增长速度较缓
○ 由于大部分JVM堆空间都分配给了年老代,因此其垃圾回收算法需要更节省空间,此算法需要能够处理低垃圾密度的堆空间
3. 持久代(Permanent Generation)
○ 存放VM和Java类的元数据(metadata),以及interned字符串和类的静态变量
次收集(Minor GC)和全收集(Full GC)
当这三个分代的堆空间比较紧张或者没有足够的空间来为新到的请求分配的时候,垃圾回收机制就会起作用。有两种类型的垃圾回收方式:次收集和全收集。当年轻代堆空间满了的时候,会触发次收集将还存活的对象移到年老代堆空间。当年老代堆空间满了的时候,会触发一个覆盖全范围的对象堆的全收集。
次收集
当年轻代堆空间紧张时会被触发
相对于全收集而言,收集间隔较短
全收集
当老年代或者持久代堆空间满了,会触发全收集操作
可以使用System.gc()方法来显式的启动全收集
全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。不过,如果全收集时间超过3到5秒钟,那就太长了[1]
全收集通常时间最长,并且是程序无法延迟执行或者无法达到吞吐量目标的主因。GC的目标是去减少程序运行过程中垃圾回收的频率。为了达到这个目的,可以从这两方面入手:
从系统方面考虑:
○ 尽量采用大堆,但是不要大到需要系统从磁盘上“换”页。一般而言,可用的RAM(没有被系统进程占用的)的80%都应该分配给JVM。
○ Java堆空间越大,垃圾回收器和java应用在吞吐量(throughput)和延迟执行(latency)方面的效果越好。
从应用方面考虑:
○ 减少对象分配(object allocations)操作,或者采用对象保留(object retention)方式有助于减小存活的数据大小,这也可以反过来帮助垃圾回收做的更好。
○ 参考这篇文章—Java性能提升窍门[19]
内存溢出错误(OutOfMemoryError)
可怕的内存溢出错误是Java程序员最不愿意看到的。然而这个错误还是会出现,尤其应用中涉及到大量的数据处理时,或应用运行时间过长时。
一个应用所占内存大小包括:
Java堆大小
线程栈
I/O缓冲区
原生库所分配的内存
当一个应用耗尽了内存并且JVM GC也无法回收任何对象空间的时候,就会发生内存溢出错误。但是,内存溢出错误并不一定就意味着内存泄露(memory leak)。也有可能只是一个配置问题,例如设置的堆大小(如果没有设置那就是缺省的堆大小)对于应用来说是不够用的。
JVM命令行参数
无论是客户端应用还是服务器端应用,一旦系统运行缓慢并且垃圾回收所占时间过长,你就会希望通过调整堆大小来改善这一点。不过,为了不影响其他也跑在同一个系统中的应用,不应该将堆大小设置的过大。
GC调优是很重要的。找到最佳的分代堆空间是一个迭代的过程[3,10,12]。这里我们假定你已经为你的应用找到了最佳堆大小。那么你可以采用下面的JVM命令来进行设置:
GC 命令行选项 描述
-Xms 设置Java堆大小的初始值/最小值。例如:-Xms512m (请注意这里没有”=”).
-Xmx 设置Java堆大小的最大值
-Xmn 设置年轻代对空间的初始值,最小值和最大值。请注意,年老代堆空间大小是依赖于年轻代堆空间大小的
-XX:PermSize=<n>[g|m|k] 设置持久代堆空间的初始值和最小值
-XX:MaxPermSize=<n>[g|m|k] 设置持久代堆空间的最大值
最后一点,最早在Java SE 5.0中有对服务器的人机工程学的介绍[13]。这个可以很好的减少服务器端应用的调优时间,尤其是在堆大小测量和复杂GC调优方面。很多情况下,服务器端调优的最好方式就是不去调优。
以上是关于jvm 知识的主要内容,如果未能解决你的问题,请参考以下文章