JVM

时间:2019-04-15
本文章向大家介绍JVM,主要包括JVM使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

初识JVM
JVM的概念
​ JVM是Java Virtual Machine的简称。意为Java虚拟机

​ 虚拟机:指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。

​ 有哪些虚拟:

​ VMWare

​ Visual Box

​ JVM

​ VMWare或者Visual Box都是使用软件模拟物理CPU的指令集

​ JVM使用软件模拟Java字节码的指令集

JVM的发展历史
1996年 SUN JDK 1.0 Classic VM
纯解释运行,使用外挂进行JIT,没有内置的即时编译的功能
1997年 JDK1.1 发布
AWT、内部类、JDBC、RMI、反射
1998年 JDK1.2 Solaris Exact VM
JIT 解释器混合
Accurate Memory Management 精确内存管理,数据类型敏感
提升的GC性能
JDK1.2开始 称为Java 2
J2SE J2EE J2ME 的出现
加入Swing Collections

2000年 JDK 1.3 Hotspot 作为默认虚拟机发布 加入JavaSound 声音相关的API
2002年 JDK 1.4 Classic VM退出历史舞 Assert 正则表达式 NIO IPV6 日志API 加密类库
2004年发布 JDK1.5 即 JDK5 、J2SE 5 、Java 5
泛型
注解
装箱
枚举
可变长的参数
Foreach循环
JDK1.6 JDK6
脚本语言支持
JDBC 4.0
Java编译器 API
2011年 JDK7发布
延误项目推出到JDK8
G1 GC收集器
动态语言增强 脚本语言
64位系统中的压缩指针
NIO 2.0
2014年 JDK8发布
Lambda表达式 模拟了函数式的编程语言的思想,试图去解决面向对象编程代码过程的问题
语法增强 Java类型注解
2016年JDK9
模块化
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
36
​ Java和JVM的历史 – 大事记

使用最为广泛的JVM为HotSpot
HotSpot 为Longview Technologies开发 被SUN收购
2006年 Java开源 并建立OpenJDK
HotSpot 成为Sun JDK和OpenJDK中所带的虚拟机
2008 年 Oracle收购BEA
得到JRockit VM
2010年Oracle 收购 Sun
得到Hotspot
Oracle宣布在JDK8时整合JRockit和Hotspot,优势互补
在Hotspot基础上,移植JRockit优秀特性
1
2
3
4
5
6
7
8
9
10
JVM种类
各式JVM

KVM
SUN发布
IOS Android前,广泛用于手机系统
CDC/CLDC HotSpot
手机、电子书、PDA等设备上建立统一的Java编程接口
J2ME的重要组成部分
JRockit
BEA
IBM J9 VM
IBM内部
Apache Harmony
兼容于JDK 1.5和JDK 1.6的Java程序运行平台
与Oracle关系恶劣 退出JCP ,Java社区的分裂
OpenJDK出现后,受到挑战 2011年 退役
没有大规模商用经历
对Android的发展有积极作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java语言规范
语法

语法定义:
IfThenStatement:
if ( Expression ) Statement
ArgumentList:
Argument
ArgumentList , Argument

if(true){do sth;}
add(a,b,c,d);
1
2
3
4
5
6
7
8
9
词法结构:
\u + 4个16进制数字 表示UTF-16
行终结符: CR, or LF, or CR LF.
空白符
空格 tab \t 换页 \f 行终结符
注释
标示符
关键字

Identifier:
IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
JavaLetter
IdentifierChars JavaLetterOrDigit
JavaLetter:
any Unicode character that is a Java letter (see below)
JavaLetterOrDigit:
any Unicode character that is a Java letter-or-digit (see below)

Int
0 2 0372 0xDada_Cafe 1996 0x00_FF__00_FF
Long
0l 0777L 0x100000000L 2_147_483_648L 0xC0B0L
Float
1e1f 2.f .3f 0f 3.14f 6.022137e+23f
Double
1e1 2. .3 0.0 3.14 1e-9d 1e137
操作
+= -= *= /= &= |= ^= %= <<= >>= >>>=
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
变量、类型

类型和变量:
元类型
byte short int long float char
变量初始值
boolean false
char \u0000
泛型
1
2
3
4
5
6
7
一个小例子:
class Value { int val; }

class Test {
public static void main(String[] args) {
int i1 = 3;
int i2 = i1;
i2 = 4;
System.out.print("i1==" + i1);
System.out.println(" but i2==" + i2);
Value v1 = new Value();
v1.val = 5;
Value v2 = v1;
v2.val = 6;
System.out.print("v1.val==" + v1.val);
System.out.println(" and v2.val==" + v2.val);
}
}

result:
i1==3 but i2==4
v1.val==6 and v2.val==6

i1 i2为不同的变量
v1 v2为引用同一个实例
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内存模型

类加载链接的过程

public static final abstract的定义

异常

数组的使用

…….

JVM规范
Java语言规范了什么是Java语言
Java语言和JVM相对独立
Groovy
Clojure
Scala
JVM主要定义二进制class文件和JVM指令集等
1
2
3
4
5
6
Class 文件格式
数字的内部表示和存储
Byte -128 to 127 (-27 to 27 - 1)
returnAddress 数据类型定义
指向操作码的指针。不对应Java数据类型,不能在运行时修改。Finally实现需要
定义PC


方法区
1
2
3
4
5
6
7
8
9
Class文件类型

运行时数据
帧栈
虚拟机的启动
虚拟机的指令集

整数的表示:
原码:第一位为符号位(0为正数,1为负数)
反码:符号位不动,原码取反
负数补码:符号位不动,反码加1
正数补码:和原码相同
打印整数的二进制表示
int a=-6;
for(int i=0;i<32;i++){
int t=(a & 0x80000000>>>i)>>>(31-i);
System.out.print(t);
}
result:
-6
原码: 10000110
反码: 11111001
补码: 11111010
5
00000101
-1
原码: 10000001
反码: 11111110
补码: 11111111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
为什么要用补码?
计算0的表示
0
负数:10000000
反码:11111111
补码:00000000

-6+5
11111010
+ 00000101
= 11111111
1
2
3
4
5
6
7
8
9
10
11
Float的表示与定义
支持 IEEE 754
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm

指数:8
尾数:23

e全0 尾数附加位为0 否则尾数附加位为1
s*m*2^(e-127)
例如:
-5
11000000101000000000000000000000
-1*2^(129-127)*(2^0+2^-2)

一些特殊的方法
<clinit> 类的初始化方法
<init> 对象的初始化方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
VM指令集

类型转化
l2i
出栈入栈操作
aload astore
运算
iadd isub
流程控制
ifeq ifne
函数调用
invokevirtual invokeinterface invokespecial invokestatic
1
2
3
4
5
6
7
8
9
10
JVM需要对Java Library 提供支持

反射 java.lang.reflect
ClassLoader
初始化class和interface
安全相关 java.security
多线程
弱引用
1
2
3
4
5
6
JVM的编译

源码到JVM指令的对应格式
Javap
JVM反汇编的格式
<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]

例如:
void spin() {
int i;
for (i = 0; i < 100; i++) { ;
// Loop body is empty
}
}

0 iconst_0 // Push int constant 0
1 istore_1 // Store into local variable 1 (i=0)
2 goto 8 // First time through don't increment
5 iinc 1 1 // Increment local variable 1 by 1 (i++)
8 iload_1 // Push local variable 1 (i)
9 bipush 100 // Push int constant 100
11 if_icmplt 5 // Compare and loop if less than (i < 100)
14 return // Return void when done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
小结:

JVM只是规范,Hospot只是实现
1
JVM运行机制
JVM启动流程
Java XXX--->装载配置--->根据配置寻找JVM.dll--->初始化JVM获得JNIEnv接口--->找到main方法并运行
装载配置---根据当前路径和系统版本寻找jvm.cfg
根据配置寻找JVM.dll---JVM.dll为JVM主要实现
初始化JVM获得JNIEnv接口---JNIEnv为JVM接口,findClass等操作通过它实现
1
2
3
4
JVM基本结构


PC寄存器

每个线程拥有一个PC寄存器
在线程创建时 创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined
1
2
3
4
方法区

保存装载的类信息
类型的常量池
字段,方法信息
方法字节码
通常和永久区(Perm)关联在一起

JDK6时,String等常量信息置于方法
JDK7时,已经移动到了堆
1
2
3
4
5
6
7
8
Java堆

和程序开发密切相关
应用系统对象都保存在Java堆中
所有线程共享Java堆
对分代GC来说,堆也是分代的
GC的主要工作区间

eden-s0-s1-tenured
复制算法
1
2
3
4
5
6
7
8
Java栈

线程私有
栈由一系列帧组成(因此Java栈也叫做帧栈)
帧保存一个方法的局部变量、操作数栈、常量池指针
每一次方法调用创建一个帧,并压栈
1
2
3
4
Java栈-局部变量表 包含参数和局部变量

public class StackDemo {

public static int runStatic(int i,long l,float f,Object o ,byte b){
return 0;
}
public int runInstance(char c,short s,boolean b){
return 0;
}
}
running result:
0 int int i
1 long long l
3 float float f
4 reference Object o
5 int byte b

0 reference this
1 int char c
2 int short s
3 int boolean b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java栈 – 函数调用组成帧栈

public static int runStatic(int i,long l,float f,Object o ,byte b){
return runStatic(i,l,f,o,b);
}

running-result:
这是一个帧
省略:操作数栈 返回地址等
0 int int i
1 long long l
3 float float f
4 reference Object o
5 int byte b

0 int int i
1 long long l
3 float float f
4 reference Object o
5 int byte b

0 int int i
1 long long l
3 float float f
4 reference Object o
5 int byte b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java栈 – 操作数栈

Java没有寄存器,所有参数传递使用操作数栈
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}

0: iconst_0 // 0压栈
1: istore_2 // 弹出int,存放于局部变量2
2: iload_0 // 把局部变量0压栈
3: iload_1 // 局部变量1压栈
4: iadd //弹出2个变量,求和,结果压栈
5: istore_2 //弹出结果,放于局部变量2
6: iload_2 //局部变量2压栈
7: ireturn //返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


Java栈 – 栈上分配

C++ 代码示例
class BcmBasicString{ ....}
堆上分配 每次需要清理空间
public void method(){
BcmBasicString* str=new BcmBasicString;
.... delete str;
}
栈上分配 函数调用完成自动清理
public void method(){
BcmBasicString str;
....
}
1
2
3
4
5
6
7
8
9
10
11
12
Java栈 – 栈上分配

public class OnStackTest {
public static void alloc(){
byte[] b=new byte[2];
b[0]=1;
}
public static void main(String[] args) {
long b=System.currentTimeMillis();
for(int i=0;i<100000000;i++){
alloc();
}
long e=System.currentTimeMillis();
System.out.println(e-b);
}
}

-server -Xmx10m -Xms10m
-XX:+DoEscapeAnalysis -XX:+PrintGC
输出结果 5
-server -Xmx10m -Xms10m
-XX:-DoEscapeAnalysis -XX:+PrintGC
……
[GC 3550K->478K(10240K), 0.0000977 secs]
[GC 3550K->478K(10240K), 0.0001361 secs]
[GC 3550K->478K(10240K), 0.0000963 secs]
564
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栈 – 栈上分配

小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
直接分配在栈上,可以自动回收,减轻GC压力
大对象或者逃逸对象无法栈上分配
1
2
3
栈、堆、方法区交互

public class AppMain
//运行时, jvm 把appmain的信息都放入方法区
{
public static void main(String[] args)
//main 方法本身放入方法区。
{
Sample test1 = new Sample( " 测试1 " );
//test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面
Sample test2 = new Sample( " 测试2 " );
test1.printName();
test2.printName();
}
}
}

public class Sample
//运行时, jvm 把appmain的信息都放入方法区
{
private name;
//new Sample实例后, name 引用放入栈区里, name 对象放入堆里
public Sample(String name)
{
this .name = name;
}
//print方法本身放入 方法区里。
public void printName()
{
System.out.println(name);
}
}
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


为了能让递归函数调用的次数更多一些,应该怎么做呢?

内存模型
每一个线程有一个工作内存和主存独立
工作内存存放主存中变量的值的拷贝

read,load-> use->
主内存<------->线程工作内存<--------->线程执行引擎
<-store,write <-assign

当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作

每一个操作都是原子的,即执行期间不会被中断

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中
如果需要在其他线程中立即可见,需要使用 volatile 关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14


volatile:
当一个线程修改共享数据时需要立即让其他线程保持数据实时有效性
public class VolatileStopThread extends Thread{
private volatile boolean stop = false;
public void stopMe(){
stop=true;
}

public void run(){
int i=0;
while(!stop){//线程不停监听值
i++;
}
System.out.println("Stop thread");
}

public static void main(String args[]) throws InterruptedException{
VolatileStopThread t=new VolatileStopThread();
t.start();
Thread.sleep(1000);
t.stopMe();
Thread.sleep(1000);
}
}
没有volatile -server 运行 无法停止

volatile 不能代替锁
一般认为volatile 比锁性能好(不绝对)
选择使用volatile的条件是:
语义是否满足应用
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
几个跟内存模型相关的概念:

可见性:
一个线程修改了变量,其他线程可以立即知道
保证可见性的方法:
volatile
synchronized (unlock之前,写变量值回主存)
final(一旦初始化完成,其他线程就可见)

有序性:
在本线程内,操作都是有序的
在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
指令重排:
线程内串行语义
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
以上语句不可重排
编译器不考虑多线程间的语义
可重排: a=1;b=2;
指令重排 – 破坏线程间的有序性:
class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a +1;
……
}
}
}
线程A首先执行writer()方法
线程B线程接着执行reader()方法
线程B在int i=a+1 是不一定能看到a已经被赋值为1
因为在writer中,两句话顺序可能打乱
线程A
flag=true
a=1
线程B
flag=true(此时a=0)

指令重排 – 保证有序性的方法:
class OrderExample {
int a = 0;
boolean flag = false;
public synchronized void writer() {
a = 1;
flag = true;
}
public synchronized void reader() {
if (flag) {
int i = a +1;
……
}
}
}
同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。
线程A
flag=true
a=1
--------------
线程B
flag=true(此时a=1)
加锁之后,多线程执行方法时会保持串行而不是并行

指令重排的基本原则:
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法

例:
a=4;
b=a+4;
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
编译和解释运行的概念
解释运行
解释执行以解释方式运行字节码
解释执行的意思是:读一句执行一句
编译运行(JIT)即时编译
将字节码编译成机器码
直接执行机器码
运行时编译
编译后性能有数量级的提升
性能相差10倍以上
1
2
3
4
5
6
7
8
9
常用JVM配置参数
Trace跟踪参数
-verbose:gc
-XX:+printGC
打开GC的开关
可以打印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]
-XX:+PrintGCDetails
打印GC详细信息
-XX:+PrintGCTimeStamps
打印CG发生的时间戳

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

-XX:+PrintGCDetails在程序运行后堆信息的输出
Heap
def new generation total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000)
eden space 12288K, 91% used [0x27e80000, 0x28975f20, 0x28a80000)
from space 1536K, 0% used [0x28a80000, 0x28a80000, 0x28c00000)
to space 1536K, 0% used [0x28c00000, 0x28c00000, 0x28d80000)
tenured generation total 5120K, 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)
注:
total = edent space + from space
used [低边界(起始位置),当前边界,最高边界(最多能申请的位置)]
(0x28d80000-0x27e80000)/1024/1024=15M
from == to
ro,rw 共享区间大小

一般GC信息都是在控制台的不方便分析
-Xloggc:log/gc.log
指定GC log的位置,以文件输出
帮助开发人员分析问题

-XX:+PrintHeapAtGC
每次一次GC后,都打印堆信息

{Heap before GC invocations=0 (full 0):
def new generation total 3072K, used 2752K [0x33c80000, 0x33fd0000, 0x33fd0000)
eden space 2752K, 100% used [0x33c80000, 0x33f30000, 0x33f30000)
from space 320K, 0% used [0x33f30000, 0x33f30000, 0x33f80000)
to space 320K, 0% used [0x33f80000, 0x33f80000, 0x33fd0000)
tenured generation total 6848K, used 0K [0x33fd0000, 0x34680000, 0x34680000)
the space 6848K, 0% used [0x33fd0000, 0x33fd0000, 0x33fd0200, 0x34680000)
compacting perm gen total 12288K, used 143K [0x34680000, 0x35280000, 0x38680000)
the space 12288K, 1% used [0x34680000, 0x346a3c58, 0x346a3e00, 0x35280000)
ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
[GC[DefNew: 2752K->320K(3072K), 0.0014296 secs] 2752K->377K(9920K), 0.0014604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap after GC invocations=1 (full 0):
def new generation total 3072K, used 320K [0x33c80000, 0x33fd0000, 0x33fd0000)
eden space 2752K, 0% used [0x33c80000, 0x33c80000, 0x33f30000)
from space 320K, 100% used [0x33f80000, 0x33fd0000, 0x33fd0000)
to space 320K, 0% used [0x33f30000, 0x33f30000, 0x33f80000)
tenured generation total 6848K, used 57K [0x33fd0000, 0x34680000, 0x34680000)
the space 6848K, 0% used [0x33fd0000, 0x33fde458, 0x33fde600, 0x34680000)
compacting perm gen total 12288K, used 143K [0x34680000, 0x35280000, 0x38680000)
the space 12288K, 1% used [0x34680000, 0x346a3c58, 0x346a3e00, 0x35280000)
ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000)
rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000)
}
[GC[DefNew: 2752K->320K(3072K), 0.0014296 secs] 2752K->377K(9920K), 0.0014604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

-XX:+TraceClassLoading
监控类的加载
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared objects file]
[Loaded java.lang.CharSequence from shared objects file]
[Loaded java.lang.String from shared objects file]
[Loaded java.lang.reflect.GenericDeclaration from shared objects file]
[Loaded java.lang.reflect.Type from shared objects file]

-XX:+PrintClassHistogram
按下Ctrl+Break后,打印类的信息:
num #instances #bytes class name
----------------------------------------------
1: 890617 470266000 [B
2: 890643 21375432 java.util.HashMap$Node
3: 890608 14249728 java.lang.Long
4: 13 8389712 [Ljava.util.HashMap$Node;
5: 2062 371680 [C
6: 463 41904 java.lang.Class
分别显示:序号、实例数量、总大小、类型
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
堆的分配参数
-Xmx –Xms
指定最大堆和最小堆
-Xmx20m -Xms5m
运行代码:
System.out.print("Xmx=");
System.out.println(Runtime.getRuntime().maxMemory()/1024.0/1024+"M");

System.out.print("free mem=");
System.out.println(Runtime.getRuntime().freeMemory()/1024.0/1024+"M");

System.out.print("total mem=");
System.out.println(Runtime.getRuntime().totalMemory()/1024.0/1024+"M");

打印:
Xmx=19.375M
free mem=4.342750549316406M
total mem=4.875M 当前可用的,分配到的大小
----------------------------
继续运行:
byte[] b=new byte[1*1024*1024];
System.out.println("分配了1M空间给数组");
打印:
分配了1M空间给数组
Xmx=19.375M
free mem=3.4791183471679688M
total mem=4.875M

Java会尽可能维持在最小堆,5m,当没有办法维持在5m,则会扩容
----------------------
b=new byte[4*1024*1024];
打印:
分配了4M空间给数组
Xmx=19.375M
free mem=3.5899810791015625M
total mem=9.00390625M
这时,总内存变多了
----------------------
System.gc();
打印:
回收内存
Xmx=19.375M
free mem=6.354591369628906M
total mem=10.75390625M
这时,空闲内存增多
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
36
37
38
39
40
41
42
43
44
-Xmx 和 –Xms 应该保持一个什么关系,可以让系统的性能尽可能的好呢?
如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢?

堆分配的其他参数

-Xmn
设置新生代大小
-XX:NewRatio
新生代(eden+2*s)和老年代(不包含永久区)的比值
4 表示 新生代:老年代=1:4,即年轻代占堆的1/5
-XX:SurvivorRatio
设置两个Survivor区和eden的比
8表示 两个Survivor :eden=2:8,即一个Survivor占年轻代的1/10
1
2
3
4
5
6
7
8
9
一个例子:
//运行程序
public static void main(String[] args) {
byte[] b=null;
for(int i=0;i<10;i++)
b=new byte[1*1024*1024];
}
打印模式:
-------1
-Xmx20m -Xms20m -Xmn1m -XX:+PrintGCDetails
结果:
1.没有触发GC
2.全部分配在老年代
-------2
-Xmx20m -Xms20m -Xmn15m -XX:+PrintGCDetails
结果:
1.没有触发GC
2.全部分配在eden
3.老年代没有使用代
--------3
-Xmx20m -Xms20m –Xmn7m -XX:+PrintGCDetails
结果:
1.进行了2次新生代GC
2.s0 s1 太小需要老年代担保
--------4
-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
结果:
1.进行了3次新生代GC
2.s0 s1 增大
--------5
-Xmx20m -Xms20m -XX:NewRatio=1
-XX:SurvivorRatio=2 -XX:+PrintGCDetails
结果:
1.进行了2次新生代GC
2.全部发生在新生代
--------6
-Xmx20m -Xms20m -XX:NewRatio=1
-XX:SurvivorRatio=3 -XX:+PrintGCDetails
--------7
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
36
37
38
39
40
运行结果:从上往下依次


其他的使用的参数

-XX:+HeapDumpOnOutOfMemoryError
OOM时导出堆到文件
-XX:+HeapDumpPath
导出OOM的路径
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
Vector v=new Vector();
for(int i=0;i<25;i++)
v.add(new byte[1*1024*1024]);

-XX:OnOutOfMemoryError
在OOM时,执行一个脚本
"-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p“
当程序OOM时,在D:/a.txt中将会生成线程的dump
可以在OOM时,发送邮件,甚至是重启程序

printstack.bat:D:/tools/jdk1.7_40/bin/jstack -F %1 > D:/a.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16


堆的分配参数-小结:

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

​ 官方推荐新生代占堆的3/8

​ 幸存代占新生代1/10

​ 在OOM时,记得Dump出堆,确保可以排查现场问题

永久区的分配参数
-XX:PermSize -XX:MaxPermSize
设置永久区的初始空间和最大空间
他们表示,一个系统可以容纳多少个类型
(一般系统也就是几十M或者几百M)
1
2
3
4
使用CGLIB等库的时候,可能会产生大量的类,这些类,有可能撑爆永久区导致OOM
for(int i=0;i<100000;i++){
CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap());
}
不断产生新的类
1
2
3
4
5


打开堆的Dump
堆空间实际占用非常少
但是永久区溢出 一样抛出OOM
如果堆空间没有用完也抛出了OOM,有可能是永久区导致的
1
2
3
4


栈的分配参数
栈大小分配

-Xss
通常只有几百K
决定了函数调用的深度
每个线程都有独立的栈空间
局部变量、参数 分配在栈上
1
2
3
4
5
例子

public class TestStackDeep {
private static int count=0;
public static void recursion(long a,long b,long c){
long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;
count++;
recursion(a,b,c);
}
public static void main(String args[]){
try{
recursion(0L,0L,0L);
}catch(Throwable e){
System.out.println("deep of calling = "+count);
e.printStackTrace();
}
}
}

递归调用
-Xss128K
deep of calling = 701
java.lang.StackOverflowError

-Xss256K
deep of calling = 1817
java.lang.StackOverflowError
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
疑问的补充:

1.
from和to其实只是一个逻辑概念,对于物理上来说,新生代其实就是分配对象的内存+待复制对象的内存空间
-XX:+PrintGCDetails
-XX:+PrintGCDetails这个是每次gc都会打印的,只是程序结束后才打印详细的堆信息
-Xmx不包含,持久代空间
堆空间是连续的
2.如果你要做一个Java的桌面产品,需要绑定JRE,但是JRE又很大,你如何做一下JRE的瘦身呢:
Java运行主要引赖于bin和Lib目录,bin目录主要存储了java命令和需要的dll
lib目录是java虚拟机要用到的class和配置文件。
bin目录精简的方式:
1、bin目录最主要的工具是java.exe,它用来执行class文件.
如果只是为了单纯运行Java程序的话,其他可执行文件一般都是用不到的(可剔除).
2、 bin目录里面的动态链接库文件dll是java.exe执行class文件过程中调用的.
执行class文件,java.exe需要哪个库文件就加载哪个dll,不需用的可以剔除.
查看java用到那个dll的,可以通过windows的任务管理器,查看进程号,再用其它工具(如360)
查看引用到的dll
lib精简方式:demo版
主要思想就是:
1、把程序运行所需要的class文件通过-XX:TraceClassLoading打印到文本文件
2、用自己写的程序把需要的class和rt路径,精简rt存放的路径设置好
3、然后将rt1里面的目录和文件打包成rt.zip,改名为rt.jar,然后替换原来的rt.jar
4、可以达到精简的作用,再将Java.exe和对应的dll copy到相应的目录,
5、写一个批处理命令,用于自带的Java去执行jar包。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.IOUtils;
public class CutJre {
private String needClazz = "d:\\needClazz.txt";//需要的class
private String rtPath = "D:\\Program Files\\Java\\jre6\\lib";//rt存放路径
private String dstRtPath = "D:/cutJar/";//精简后的路径
private JarFile rtJar;

public static void main(String[] args) throws Exception {
CutJre cutJre = new CutJre();
cutJre.rtJar = new JarFile(cutJre.rtPath + "\\rt.jar");
cutJre.copyClass("[Loaded sun.reflect.FieldAccessor from sda]");
// cutJre.execute();
}

private void execute() throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(needClazz)));
String string = br.readLine();
while (string != null) {
string = br.readLine();
}
}

private boolean copyClass(String traceStr) throws IOException {
if (traceStr.startsWith("[Loaded")) {
String className = traceStr.split(" ")[1];
//不是rt里面的Jar包,是自己有的
if(className.contains("zh")){
return true;
}
copyFile(className);
}
return false;
}

private void copyFile(String className) throws IOException {
String classFile = className.replace(".", "/") + ".class";
String classDir = classFile.substring(0,classFile.lastIndexOf("/"));

File dir=new File(dstRtPath+classDir);
System.out.println(dir);
if(!dir.exists()){
dir.mkdirs();
}
JarEntry jarEntry = rtJar.getJarEntry(classFile);
InputStream ins = rtJar.getInputStream(jarEntry);
File file = new File(dstRtPath+ classFile);
System.out.println(file);
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
IOUtils.copy(ins, fos);
ins.close();
fos.close();
}
}
-Xmx 和 –Xms 应该保持一个什么关系,可以让系统的性能尽可能的好呢?
-Xms25m -Xmx40m -Xmn7m -XX:+PrintGCDetails -XX:PermSize=16m

首先 def new generation total 6464K, used 115K [0x34e80000, 0x35580000, 0x35580000)
eden space 5760K, 2% used [0x34e80000, 0x34e9cd38, 0x35420000)
from space 704K, 0% used [0x354d0000, 0x354d0000, 0x35580000)
to space 704K, 0% used [0x35420000, 0x35420000, 0x354d0000)
通过这一行可以知道年轻代大小是7m.

通过 tenured generation total 18124K, used 8277K [0x35580000, 0x36733000, 0x37680000)

(0x37680000-0x35580000)/1024/1024得到的结果是33m

通过以上可以得到最大堆是40m。但通过eden大小和 tenured generation total 18124K计算出最小堆应该是25m

通过compacting perm gen total 16384K, 可以计算出持久堆-XX:PermSize=16m
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
GC算法和种类
GC的概念
​ Garbage Collection垃圾回收

​ 1960年list使用了GC

​ java中,gc的对象是堆空间和永久区

GC的算法
引用计数法
​ 老牌垃圾回收算法

​ 通过引用计算来回收垃圾

​ 使用者:

​ COM

​ ActionScript3

​ Python

​ 实现原理:

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

​ (注:其他对象引用该对象时,该对象的计数器会自加一,其他对象的引用失效时,该对象的计数器自减一,当该对象的计数器为0时且时间悬空时,GC会自动回收该对象。)

​ 引用计数法的问题:

​ 引用的算法伴随着加法和减法,会影响性能

​ 很难处理循环引用

标记-清除
​ 概念:

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

​ (标记-清除将垃圾回收细分为标记阶段和清除阶段两个阶段,标记阶段,在引用节点树中,通过对象的根节点,标记所有可标记的对象,而没有标记的对象称为垃圾对象,清除阶段,在引用对象树中,当未有标记时,清除对象。)

标记-压缩
​ 概念:

​ 适合于存活对象多较多的场景,如老年代。在标记清除算法上做了优化,标记和压缩,标记阶段,从根节点开始标记所有可标记的对象,但是真正回收垃圾阶段不是直接通过根节点检测未被标记的对象就去清除,而是将所有的存活对象压缩(复制)到内存另一端,当内存不够下次使用时,清理边界外所有空间。

标记压缩对标记清除而言,有什么优势?

复制算法
​ 概念:

​ 与标记清除算法相比,复制算法是一种相对高效的回收方法。

​ 不适合于存活对象较多的场合,如老年代

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

​ 两块空间完全相同,每次只用一块

​ 复制算法最大的问题是:空间浪费,整合了标记算法的思想

​ 老年代:多次回收都未被回收,每次回收年龄加一,长期有效的对象

例子:

-XX:+PrintGCDetails的输出
Heap
def new generation total 13824K, used 11223K [0x27e80000, 0x28d80000, 0x28d80000)
eden space 12288K, 91% used [0x27e80000, 0x28975f20, 0x28a80000)
from space 1536K, 0% used [0x28a80000, 0x28a80000, 0x28c00000)
to space 1536K, 0% used [0x28c00000, 0x28c00000, 0x28d80000)
tenured generation total 5120K, 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)

看这三个部分:
total=12288K+ 1536K
(0x28d80000-0x27e80000)/1024/1024=15M
(0x28d80000-0x27e80000)/1024/1024=15M
def 新生代空间
from,to 复制算法中的两块空间,大小保持相等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
分代思想:
​ 依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代。

​ 依据不同代的特点,选取合适的收集算法

​ 少量对象存活,适合复制算法 新生代对象存货量低

​ 大量对象存活,适合标记清理和标记压缩(对象经过多次GC都没有被回收,对象是长期存活的)

GC算法小结
​ 引用计数

​ 没有被Java采用,因为单纯的使用不能处理循环引用问题

​ 标记-清除

​ 标记-压缩

​ 复制算法

​ 新生代

​ 所有的算法,需要能够识别一个垃圾对象,因此需要给出一个可触及性的定义。

什么是垃圾对象?引出几个概念:

可触及性
概念:

​ 可触及的:

​ 现在是不能触及的,但是有以后有可能触及的

​ 从根节点可以触及到这个对象

​ 可复活性:

​ 一旦所有引用被释放,就是可复活状态

​ 因为在finalize()中可能复活该对象

​ 不可触及的:

​ 在finalize()后,可能会进入不可触及状态

​ 不可触及的对象不可能复活

​ 可以回收

案例:

public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString(){
return "I am CanReliveObj";
}

public static void main(String[] args) throws
InterruptedException{
obj=new CanReliveObj();
obj=null; //可复活 对象置空就表示可以回收的
System.gc();//通常是null值对象gc后就消失了,gc()会调用finalized(),所以又赋值引用该对象本身了,所以没有被回收掉,复活
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj=null; //不可复活 finallized()只会调用一次, 这里引出一个问题:如果没有设置该对象为null的话,该对象将永远不会被回收
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else{
System.out.println("obj 可用");
}
}
}

CanReliveObj finalize called
obj 可用
第二次gc
obj 是 null
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
36
37
38
39
40
可触及性小结:

​ 经验:避免使用finnalize(),操作不慎可能导致错误

​ 优先级低,何时被调用,不确定

​ 何时发生GC不确定

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

​ 根:

​ 栈中引用的对象(当前调用函数中的引用对象)

​ 方法区中静态成员或者常量引用的对象(全局对象(任何时候都能被任何对象使用))

​ JNI方法栈中引用对象

Stop-The-World

概念:

​ Java中一种全局暂停的现象

​ 全局停顿,所有Java代码停止,native代码可以执行,但不能和JVM交互,JVM处于挂起状态,不能处理应用层的代码

​ 多半由于GC引起

​ Dump线程

​ 死锁检查

​ 堆Dump

GC时为什么会有全局停顿:

​ 例子:

​ 在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净。

危害:

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

​ 遇到HA系统,可能引起主备切换,严重危害生产环境。主机长期没有响应就会启动备机,一段时间后,会导致主备同时处于启动状态

每秒打印10条
public static 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){

}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class MyThread extends Thread{
HashMap<Long,byte[]> map=new HashMap<Long,byte[]>();
@Override
public void run(){
try{
while(true){
if(map.size()*512/1024/1024>=450){
//大于450M时,清理内存
System.out.println(“=====准备清理=====:"+map.size());
map.clear();
}
//工作线程,消耗内存
for(int i=0;i<1024;i++){
map.put(System.nanoTime(), new byte[512]);
}
Thread.sleep(1);
}
}catch(Exception e){
e.printStackTrace();
}
}
}

启动:
-Xmx512M -Xms512M -XX:+UseSerialGC -Xloggc:gc.log -XX:+PrintGCDetails -Xmn1m -XX:PretenureSizeThreshold=50 -XX:MaxTenuringThreshold=1

运行结果:
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
======before clean map=======:921765
time:4349
time:4450
time:4551

GC情况:
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]
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
补充:

​ 垃圾回收算法:为让stw时间较长,增大年老代空间和选用serial old垃圾算法进行回收老年代

​ jvm垃圾回收参数:-Xms512m -Xmx512m -Xmn4m -XX:+PrintGCDetails -XX:+UseSerialGC

​ 尽可能减少一次STW停顿时间?由此带来的弊端是什么?

​ 减少一次STW停顿时间,我这里从三个方面回答,一个是垃圾算法选择,一个是程序使用堆设置,无用对象尽早释放
垃圾算法选择:现在都是多核cpu,可以采用并行和并发收集器,如果是响应时间优化的系统应用 ,则jdk6版本一般

​ 选择的垃圾回收算法是:XX:+UseConcMarkSweepGC,即cms收集器,这个收集器垃圾回收时间短,但是垃圾回收总时间变长,使的降低吞
吐量,算法使用的是标记-清除,并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生”碎片”,使得运行效率降低.
CMSFullGCsBeforeCompaction此值设置运行多少次GC以后对内存空间进行压缩,整理

​ 程序使用堆设置:应该根据程序运行情况,通过Jvm垃圾回收分析,设置一个比较合适的堆大小,不能一意味的将堆设置过大,导致
程序回收很大一块空间,所以会导致stw时间较长,

​ 无用对象尽早释放:使用的对象,如果没有用,尽早设置null,尽量在年轻代将对象进行回收掉,可以减少full gc停顿时长

GC参数
串行收集器
概念:

​ 最古老,最稳定

​ 效率高

​ 可能会产生较长的停顿,单线程进行回收,在多核上无法充分利用资源

​ -XX:+UseSerialGC

​ 新生代、老年代使用串行回收

​ 新生代复制算法

​ 老年代标记-压缩

运行时期:

​ 流程:

​ 应用程序线程(多个)————->GC线程应用程序暂停(单个)——->应用程序线程(多个)

​ log:

​ 0.844: [GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339 secs] 17472K->2375K(63360K), 0.0189186 secs][Times: user=0.01 sys=0.00, real=0.02 secs]

​ 8.259: [Full GC 8.259: [Tenured: 43711K->40302K(43712K), 0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)], 0.2961554 secs][Times: user=0.28 sys=0.02, real=0.30 secs]

并行收集器
概念:

​ ParNew

-XX:+UseParNewGC

​ 新生代并行

​ 老年代串行

Serial收集器新生代的并行版本

复制算法

多线程,需要多核支持

-XX:ParallelGCThreads 限制线程数量

应用程序线程(多)——>GC线程 多线程并发 应用程序暂停(多)——>应用程序线程

但是多线程不一定块,多cpu多核才快,

0.834: [GC 0.834: [ParNew: 13184K->1600K(14784K), 0.0092203 secs] 13184K->1921K(63936K), 0.0093401 secs][Times: user=0.00 sys=0.00, real=0.00 secs]

Parallel收集器

​ 类似ParNew

​ 新生代复制算法

​ 老年代 标记-压缩

​ 更加关注吞吐量

​ -XX:+UseParallelGC

​ 使用Parallel收集器+ 老年代串行

​ -XX:+UseParallelOldGC

​ 使用Parallel收集器+ 并行老年代

运行时期流程:

​ 应用程序线程(多)—>GC线程多线程并发 应用程序暂停(多)—->应用程序线程

log:

​ 1.500: [Full GC [PSYoungGen: 2682K->0K(19136K)] [ParOldGen: 28035K->30437K(43712K)] 30717K->30437K(62848K) [PSPermGen: 10943K->10928K(32768K)], 0.2902791 secs][Times: user=1.44 sys=0.03, real=0.30 secs]

一些其他的参数:

-XX:MaxGCPauseMills
最大停顿时间,单位毫秒
GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio 吞吐量---》决定系统性能---》cpu资源分配到应用层或者GC层
0-100的取值范围
垃圾收集时间占总时间的比
默认99,即最大允许1%时间做GC
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
1
2
3
4
5
6
7
8
9
CMS收集器
概念:

​ Concurrnet Mark Sweep并发标记清除

​ 并发:与用户线程一起执行

​ 标记-清除算法

​ 与标记-压缩相比

​ 并发阶段会降低吞吐量

​ -XX:+UseConcMarkSweepGC

CMS**运行过程**比较复杂,着重实现了标记的过程:

​ 初始标记(产生全局停顿,速度极快)

​ 根可以直接关联到的对象

​ 速度快

​ 并发标记(和用户线程一起)

​ 主要标记过程,标记全部对象

​ 重新标记(产生停顿)

​ 由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正

​ 并发清除(和用户线程一起)

​ 基于标记结果,直接清理对象

基本情况:

​ 应用程序线程(多)—->初始标记(单)—->并发标记(多)—->重新标记(多)—->并发清理(多)——>并发重置(多)

​ ———–>应用程序线程———>CMS线程

​ 算法:清除,而非清除-压缩

log

​ 1.662: [GC [1 CMS-initial-mark: 28122K(49152K)] 29959K(63936K), 0.0046877 secs][Times: user=0.00 sys=0.00, real=0.00 secs] 1.666: [CMS-concurrent-mark-start]1.699: [CMS-concurrent-mark: 0.033/0.033 secs][Times: user=0.25 sys=0.00, real=0.03 secs] 1.699: [CMS-concurrent-preclean-start]1.700: [CMS-concurrent-preclean: 0.000/0.000 secs][Times: user=0.00 sys=0.00, real=0.00 secs] 1.700: [GC[YG occupancy: 1837 K (14784 K)]1.700: [Rescan (parallel) , 0.0009330 secs]1.701: [weak refs processing, 0.0000180 secs] [1 CMS-remark: 28122K(49152K)] 29959K(63936K), 0.0010248 secs][Times: user=0.00 sys=0.00, real=0.00 secs] 1.702: [CMS-concurrent-sweep-start]1.739: [CMS-concurrent-sweep: 0.035/0.037 secs][Times: user=0.11 sys=0.02, real=0.05 secs] 1.739: [CMS-concurrent-reset-start]1.741: [CMS-concurrent-reset: 0.001/0.001 secs][Times: user=0.00 sys=0.00, real=0.00 secs]

特点:

​ 尽可能降低停顿

​ 会影响系统整体吞吐量和性能

​ 比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半

​ 清理不彻底

​ 因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理

​ 因为和用户线程一起运行,不能在空间快满时再清理

​ -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值(当占用到了百分之多少的时候就会触发硬性gc)

​ 如果不幸内存预留空间不够,就会引起concurrent mode failure

例子:

​ 33.348: [Full GC 33.348: [CMS33.357: [CMS-concurrent-sweep: 0.035/0.036 secs][Times: user=0.11 sys=0.03, real=0.03 secs]
(concurrent mode failure): 47066K->39901K(49152K), 0.3896802 secs] 60771K->39901K(63936K), [CMS Perm : 22529K->22529K(32768K)], 0.3897989 secs][Times: user=0.39 sys=0.00, real=0.39 secs]

使用串行收集器作为后备

有关碎片:

​ 标记-清除和标记-压缩

0 0
0
0
0
标记-清除,如上,,很容易产生碎片,无法申请连续单位空间[数组]

0 0 0 0 0
标记-压缩,如上,没有碎片的产生

cms更关注吞吐量(停顿复制)且与应用程序并行(安全)

cms如何处理碎片问题呢?

-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理
整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction
设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads
设定CMS的线程数量(一般为可用CPU的数量)
1
2
3
4
5
6
为了减轻GC压力,程序员需要注意什么:

​ 软件如何设计架构

​ 代码如何写

​ 堆空间如何分配

GC参数整理:

-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Tomcat实例演示
环境:

​ Tomcat 7

​ JSP 网站

​ 测试网站吞吐量和延时

工具:

​ JMeter

目的:

​ 让Tomcat有一个不错的吞吐量

系统结构:

​ Tomcat——-JMeter(JVM机,不放在同一台机器上,放置JMeter对Tomcat的运行产生影响)

​ 局域网连接

Jmeter

​ 性能测试工具

​ 建立10个线程,每个线程请求Tomcat 1000次共10000次请求

测试:

​ ——————————–1

​ JDK6:使用32M堆处理请求

​ 参数:

​ set CATALINA_OPTS=-server -Xloggc:gc.log -XX:+PrintGCDetails -Xmx32M -Xms32M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M

​ 现象:

​ Throughput(吞吐量):540/sec

​ perm:321768k

​ ——————&md