Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

java调试-jstat命令

发表于 2016-08-18

jstat命令使用

jstat是一个可以用于观察java应用程序运行时相关信息的工具,功能非常强大,可以通过它查看堆信息的详细情况。

基本用法

jstat命令的基本使用语法如下:

jstat -option [-t] [-h] pid [interval] [count]

  • 选项option可以由以下值构成。
    • -class:显示ClassLoader的相关信息。
    • -compiler:显示JIT编译的相关信息。
    • -gc:显示与gc相关的堆信息。
    • -gccapacity:显示各个代的容量及使用情况。
    • -gccause:显示垃圾回收的相关信息(同-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因。
    • -gcnew:显示新生代信息。
    • -gcnewcapacity:显示新生代大小与使用情况。
    • -gcold:显示老生代和永久代的信息。
    • -gcoldcapacity:显示老年代的大小。
    • -gcpermcapacity:显示永久代的大小。
    • -gcutil:显示垃圾收集信息。
    • -printcompilation:输出JIT编译的方法信息。
  • -t参数可以在输出信息前面加上一个Timestamp列,显示程序运行的时间。
  • -h参数可以在周期性的数据输出时,输出多少行数据后,跟着输出一个表头信息。
  • interval参数用于指定输出统计数据的周期,单位为毫秒(ms)。
  • count参数用于指定一共输出多少次数据。

详细使用

-class使用

下面命令输出pid为2500这个进程的ClassLoader相关信息,每秒统计一次信息,一共输出3次。

1
2
3
4
5
nalideMacBook-Pro-4:~ nali$ jstat -class -t 13640 1000 3
Timestamp Loaded Bytes Unloaded Bytes Time
1522.7 414 827.5 0 0.0 0.13
1523.7 414 827.5 0 0.0 0.13
1524.7 414 827.5 0 0.0 0.13

Loaded表示载入的类的数量,第一个Bytes表示载入的类的合计大小,Unloaded表示卸载的类数量,第二个Bytes表示卸载的类的合计大小,Time表示加载和卸载类花的总的时间。

-compiler使用

下面的命令查看JIT编译的信息:

1
2
3
nalideMacBook-Pro-4:~ nali$ jstat -compiler -t 13640
Timestamp Compiled Failed Invalid Time FailedType FailedMethod
1814.8 99 0 0 0.51 0

Compiled表示编译任务执行的次数,Failed表示编译失败的次数,Invalid表示编译不可用的次数,Time表示编译的总耗时,FailedType表示最后一次编译的类型,FailedMethod表示最后一次编译失败的类名和方法名。

-gc使用

下面的命令显示与gc相关的堆信息的输出:

1
2
3
4
5
nalideMacBook-Pro-4:~ nali$ jstat -gc 16309
S0C S1C S0U S1U EC EU OC OU MC MU
45056.0 55296.0 44588.0 0.0 336896.0 222238.4 171520.0 86902.0 - -
CCSC CCSU YGC YGCT FGC FGCT GCT
- - 8 0.312 1 1.328 1.640
  • S0C:s0(from)的大小(KB)
  • S1C:s1(from)的大小(KB)
  • S0U:s0(from)已使用的空间(KB)
  • S1U:s1(from)已经使用的空间(KB)
  • EC:eden区的大小(KB)
  • EU:eden区已经使用的空间(KB)
  • OC:老年代大小(KB)
  • OU:老年代已经使用的空间(KB)
  • MC:方法区大小(KB)
  • MU:方法区使用大小(KB)
  • CCSC:压缩类空间大小(KB)
  • CCSU:压缩类空间使用大小(KB)
  • YGC:新生代gc次数
  • YGCT:新生代gc耗时
  • FGC:Full gc次数
  • FGCT:Full gc耗时
  • GCT:gc总耗时
-gccapacity使用

下面的命令显示了各个代的信息,与-gc相比,它不仅输出了各个代的当前大小,还输出了各个代的最大值与最小值:

1
2
3
4
5
6
nalideMacBook-Pro-4:~ nali$ jstat -gccapacity 16309
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX
44032.0 699392.0 626176.0 45056.0 55296.0 336896.0 87040.0 1397760.0
OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
171520.0 171520.0 - - - - - - 8 1
nalideMacBook-Pro-4:~ nali$
  • NGCMN:新生代最小值(KB)
  • NGCMX:新生代最大值(KB)
  • NGC:当前新生代大小(KB)
  • OGCMN:老年大最小值(KB)
  • OGCMX:老年代最大值(KB)
  • OGC:当前老年代大小(KB)
  • MCMN:方法区最小值(KB)
  • MCMX:方法区最大值(KB)
  • CCSMN:压缩类空间最小值(KB)
  • CCSMX:压缩类空间最大值(KB)
-gccause使用

下面命令显示最近一次gc的原因,以及当前gc的原因:

1
2
3
4
5
nalideMacBook-Pro-4:~ nali$ jstat -gccause 16309
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
98.96 0.00 65.97 50.67 - - 8 0.312 1 1.328 1.640
LGCC GCC
Allocation Failure No GC
  • LGCC:上次gc的原因,可以看到上次gc的原因是Allocation Failure
  • GCC:当前gc的原因,当前没有gc
-gcnew使用

下面的命令显示新生代的详细信息:

1
2
3
nalideMacBook-Pro-4:~ nali$ jstat -gcnew 16309
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
45056.0 55296.0 44588.0 0.0 5 15 55296.0 336896.0 222238.4 8 0.312
  • TT:新生代对象晋升到老年代对象的年龄。
  • MTT:新生代对象晋升到老年代对象的年龄的最大值。
  • DSS:所需的Survivor区的大小。
-gcnewcapacity使用

下面的命令详细输出了新生代各个区的大小信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
nalideMacBook-Pro-4:~ nali$ jstat -gcnewcapacity 16309
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC
44032.0 699392.0 626176.0 232960.0 45056.0 232960.0 55296.0 698368.0 336896.0
YGC FGC
8 1
```
- S0CMX:s0区的最大值(KB)
- S1CMX:s1区的最大值(KB)
- ECMX:eden区的最大值(KB)
##### -gcold使用
下面的命令显示老年代gc概况:
``` bash
nalideMacBook-Pro-4:~ nali$ jstat -gcold 16309
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
- - - - 171520.0 86902.0 8 1 1.328 1.640
-gcoldcapacity使用

下面的命令用于显示老年代的容量信息:

1
2
3
nalideMacBook-Pro-4:~ nali$ jstat -gcoldcapacity 16309
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
87040.0 1397760.0 171520.0 171520.0 8 1 1.328 1.640

java调试-jps命令

发表于 2016-08-18

jps命令使用

jps命令类似于linux下的ps命令,用于列出当前正在运行的所有java进程。

基本用法

直接运行不加任何参数就能列出所有java进程的pid和类的短名称。例如:

1
2
3
4
nalideMacBook-Pro-4:~ nali$ jps
13698 Jps
13640 TestJava
50360

常用参数

  • -q参数

-q可以指定jps只列出pid,而不输出类的短名称,例如:

1
2
3
4
nalideMacBook-Pro-4:~ nali$ jps -q
13959
13640
50360
  • -m参数

-m参数可以用于列出传递给java进程主函数的参数,例如:

1
2
3
4
nalideMacBook-Pro-4:~ nali$ jps -m
13640 TestJava
50360
14029 Jps -m

可以看到传递给jps(jps本身也是java进程)进程的参数就是-m

  • -l参数

-l参数用于输出主类的完整路径,例如:

1
2
3
4
nalideMacBook-Pro-4:~ nali$ jps -l
14198 sun.tools.jps.Jps
13640 com.david.test.TestJava
50360
  • -v参数

-v参数可以列出传递给java虚拟机的参数,例如:

1
2
3
4
nalideMacBook-Pro-4:~ nali$ jps -v
14290 Jps -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home -Xms8m
13640 TestJava -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:56353 -Dfile.encoding=UTF-8
50360 -Dosgi.requiredJavaVersion=1.7 -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts -XX:MaxPermSize=256m -Xms256m -Xmx2048m -Xdock:icon=../Resources/Eclipse.icns -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts

Hashtable

发表于 2016-08-17

OpenJDK 源代码阅读之 Hashtable


概要

  • 类继承关系
1
2
3
java.lang.Object
java.util.Dictionary<K,V>
java.util.Hashtable<K,V>
  • 定义
1
2
3
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
  • hashtable put()方法
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
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
//在此处计算key的hash值,如果此处key为null,则直接抛出空指针异常。
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
  • hashtable get()方法
1
2
3
4
5
6
7
8
9
10
11
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}

get()和put()都使用了synchronized加锁,容易阻塞,效率低。hashtable不允许value为null

  • hashMap put()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
  • hashMap get()方法
1
2
3
4
5
6
7
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}

hashMap多线程环境不安全。

ConcurrentHashMap

发表于 2016-08-17

OpenJDK 源代码阅读之 ConcurrentHashMap


ConcurrentHashMap 作用

多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。虽然已经有一个线程安全的HashTable,但是HashTable容器使用synchronized(他的get和put方法的实现代码如下)来保证线程安全,在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,访问其他同步方法的线程就可能会进入阻塞或者轮训状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

Hashtable get()方法:

1
2
3
4
5
6
7
8
9
10
11
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}

Hashtable put()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}

实现原理

ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:

从图中可以看到,ConcurrentHashMap内部分为很多个Segment,每一个Segment拥有一把锁,然后每个Segment(继承ReentrantLock)下面包含很多个HashEntry列表数组。对于一个key,需要经过三次(为什么要hash三次下文会详细讲解)hash操作,才能最终定位这个元素的位置,这三次hash分别为:

  • 对于一个key,先进行一次hash操作,得到hash值h1,也即h1 = hash1(key);
  • 将得到的h1的高几位进行第二次hash,得到hash值h2,也即h2 = hash2(h1高几位),通过h2能够确定该元素的放在哪个Segment;
  • 将得到的h1进行第三次hash,得到hash值h3,也即h3 = hash3(h1),通过h3能够确定该元素放置在哪个HashEntry。
阅读全文 »

BlockingQueue

发表于 2016-08-16

OpenJDK 源码阅读之 BlockingQueue

引言

在java.util.Concurrent包中,BlockingQueue很好的解决了在多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。同时,BlockingQueue也用于java自带线程池的缓冲队列中,了解BlockingQueue也有助于理解线程池的工作模型。

BlockingQueue接口

该接口属于队列,所以继承了Queue接口,该接口最重要的五个方法分别是offer方法,poll方法,put方法,take方法和drainTo方法。

offer方法和poll方法分别有一个静态重载方法,分别是offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)方法。其意义是在限定时间内存入或取出对象,如果不能存入取出则返回false。

put方法会在当队列存储对象达到限定值时阻塞线程,而在队列不为空时唤醒被take方法所阻塞的线程。take方法是相反的。

drainTo方法可批量获取队列中的元素。

常见的BlockingQueue实现

  • LinkedBlockingQueue

LinkedBlockingQueue是比较常见的BlockingQueue的实现,他是基于链表的阻塞队列。在创建该对象时如果不指定可存储对象个数大小时,默认为Integer.MAX_VALUE。当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时,才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

LinkedBlockingQueue内部使用了独立的两把锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

put方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}

offer方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
}

这两个方法的区别是put方法在容量达到上限时会阻塞,而offer方法则会直接返回false。

阅读全文 »
1…262728…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces