多标识位存储优化方案

背景

在做程序开发的过程中常常会遇到数据的标识位(取值为”是”与”否”)需要进行存储,
如神马专车系统消息中心中的一个场景:新建一个消息需要勾选发送对象,可选的对象有:司机,下单人,乘车人。
最常见的Mysql存储方式为一个消息表中包含三个字段sendDriver,sendUser,sendPassenger
按用户新建消息所勾选的情况,依次存储到数据表中并在后续的业务中直接获取值进行对比。

优化方案

利用”与或非”的运算可将这多个标识符存储到一个字段中。
实例如下:
按业务这里有三个标识符需要存储,因此可以定义一个长度为3的二进行序列:000,从左至右第一位表示司机,第二位表示下单人,第三位表示乘车人。
那么,如果只需要发送给司机则可标识为100的二进行序列,如果下单人也需要通知,则可标识为110的二进行序列,依次,如果都需要通知则为111的二进制序列。
存储的值则为二进行制序列对应的十进制即可,当需要判断时,则可采用”与”运算符进行判定。

示例Java代码如下:

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
public static final byte NOTIFY_PASSENGER = 0B001; //二进制为001
public static final byte NOTIFY_SUBSCRIBER = 0B010; //二进制为010
public static final byte NOTIFY_DRIVER = 0B100; //二进制为100

/**
* 获取需要存储的值则为notifyFlag
*/
public byte getStoreValue() {
byte notifyFlag = 0B000; //二进制为000
if (request.isNotifyDriver()) {
notifyFlag |= NOTIFY_DRIVER;
}
if (request.isNotifyPassenger()) {
notifyFlag |= NOTIFY_PASSENGER;
}
if (request.isNotifySubscriber()) {
notifyFlag |= NOTIFY_SUBSCRIBER;
}
return notifyFlag;
}

/**
* 判断是否需要通知
* @param notifyTarget 预计需要通知的目标
* @return true-需要通知,false-不需要通知
*/
public boolean isNotify(byte notifyTarget) {
byte notifyFlag = 0B111;//二进制为111 , 从数据库取出
return notifyFlag & notifyTarget == notifyTarget;
}

在Java中byte的最大值为:127,二进行为0B1111111, 足够标识七个标识位!


观点仅代表自己,期待你的留言。

问题分析 之 no transaction is in progress

问题现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:301)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:365)
at $Proxy34.flush(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
at $Proxy34.flush(Unknown Source)
... 53 more

示例代码 - Proxy

1
2
3
4
5
6
7
8
9
10
11
public class classA {

public void doA {
this.doB();
}

@Transaction
public void doB{
// do save or update
}
}

问题分析:
如上代码段,由于doB为对象内方法,而Spring事务的开启依赖到AOP(Proxy),在doA方法调用doB方法时,
由于是对象内的方法调用,造成doB方法的@Transaction不会被Proxy对象代理,进而造成Transaction失效。

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ClassA {
private ClassB classB;

public void doA() {
classB.doB();
}
}

public class ClassB {

@Transaction
public void doB() {
// do save or update
}

}

将需要事务的方法doB通过Proxy进行代理,doA在使用时则是通过Spring开启事务的代理进行的调用。


观点仅代表自己,期待你的留言。

问题分析 之 Transaction marked as rollbackOnly

问题现象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:521)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:291)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy49.lazyAssignOrder(Unknown Source)
at com.smzc.provider.order.facade.OrderFacade.lazyAssignOrder(OrderFacade.java:1015)
at com.smzc.provider.order.schedule.LazyAssignOrderTask$2.run(LazyAssignOrderTask.java:51)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
... 13 more

代码示例-事务嵌套

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
@Transaction
public void doA() {
// do something
try {
doB();
} catch(Exception e) {
doC();
}
// do something
}

@Transaction
public void doA2() {
// do something
try {
doB();
} catch(Exception e) {

}
// do save or update
}

@Transaction
public void doB() {
// do save or update
if(true) {
throw new Exception("模拟异常");
}
}

@Transaction
public void doC() {
// do save or update
}

问题分析:
如上代码段,由于事务的传播性,doA,doB,doC方法其实共用的是由doA开启的同一个事务对象。
当doB方法抛出异常后事务被标记为回滚状态,再尝试执行doC方法或者执行任何的更改方法,在进行数据更新后进行事务commit时,此时则为抛出以上的异常。

解决方法:

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
public class ClassA {
private ClassB classB;
//@Transaction
public void doA() {
try {
classB.doB();
} catch(Exception e) {
classB.doC();
}
}
}

public class ClassB {
@Transaction
public void doB() {
if(1==1) {
throw new Exception("模拟异常");
}
}

@Transaction
public void doC() {
//有数据更改动作
}
}

以上的解决方法去掉了doA的事务,交由doB与doC分别开启两个事务解决,当doB失败时只标记doB的事务回滚,doC的事务依然能进行提交。


观点仅代表自己,期待你的留言。

Spring InitializingBean趟坑笔记

问题现象

在Spring完成上下文初始化完成后, InitializingBean的实现类中重写的afterPropertiesSet方法并未执行.

一、类对象延迟初始

1
2
3
4
5
6
7
8
9
10
11
12
public interface InitializingBean {
/**
* Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).
* <p>This method allows the bean instance to perform initialization only
* possible when all bean properties have been set and to throw an
* exception in the event of misconfiguration.
* @throws Exception in the event of misconfiguration (such
* as failure to set an essential property) or if initialization fails.
*/
void afterPropertiesSet() throws Exception;
}

由于此接口的方法afterPropertiesSet是在对象的所有属性被初始化后才会调用。当Spring的配置文件中设置类初始默认为”延迟初始”(default-lazy-init="true",此值默认为false)时,
类对象如果不被使用,则不会实例化该类对象。所以 InitializingBean子类不能用于在容器启动时进行初始化的工作,则应使用Spring提供的ApplicationListener接口来进行程序的初始化工作。

另外,如果需要InitializingBean子类对象在Spring容器启动时就初始化并则容器调用afterPropertiesSet方法则需要在类上增加org.springframework.context.annotation.Lazy注解并设置为false即可(也可通过spring配置bean时添加lazy-init="false")。


观点仅代表自己,期待你的留言。

分布式系统下并发读取不重复数据

问题背景

分布式系统模块存在定时任务,当模块被部署多个实例时,不同实例的定时任务在运行时就存在从数据源中获取相同数据的情况,
虽然通过乐观锁(数据记录增加version,每次update则set verion+=1 where version=?)的处理方式可以避免多实例更新数据造成脏数据入库的问题,
但这无疑是降低了分布式系统部署的初衷(通过横向部署多实例增加系统的处理能力)。

经过分析,总结如下几种处理方式。

处理方式

1、数据置标识位

在数据库记录中增加flag,当数据被读取后则更改flag的值,避免记录被再次读取。

具体步骤如下:

  • 锁定分布式锁
  • 查询指定数量的数据
  • 更新已查询出数据的标识位
  • 释放分布式锁

当处理完成时: 增加数据version,重置标识位

2、单实例读取,多实例处理

读取数据Job:利用Redis的分布式锁保障一个实例拥有读取数据的步骤,读取出来的数据再存放到Redis中的需处理数据队列。

处理业务Job:所有部署实例通过监控Redis的需处理数据队列如果有数据则take一个数据出来进行业务处理。

具体步骤如下:

  • 锁定分布式锁
  • 检查需要处理的数据队列是否为空,如为空,则跳过当前读取任务。
  • 获取需要处理的数据并存入Redis的处理队列中
  • 释放分布式锁
    由于读取数据的步骤由单进程来获取,所以就避免了并发读取数据的问题

add by 2017.8.10 : 增加处理中的数据队列,在读取数据job时需要排除到正在处理的数据。用于当有数据在处理且数据未更新时,加载数据进行加载时将数据加载出的Bug。
add by 2017.9.13 : 将锁定分布式锁扩展到第一步骤,防止并发情况下通过队列容量检查,成功获取到锁后加载到重复记录。

3、利用自增ID

利用分布式锁+Redis存储已处理ID+数据的自增ID来获取数据数据。

具体步骤如下:

  • 锁定分布式锁
  • 从Redis中取出已处理数据的最大ID值(此处用x代替)
  • 从数据库中取出大于x获取出y(固定数据量)的数据
  • 更新Redis已处理的数据最大ID值
  • 释放分布式锁

存在的问题

问题描述: 数据任务在进行业务处理的过程中,如处理失败则再也不会被以上步骤再次获取。

解决方法1: 当业务处理失败时,在Redis中记录下失败的数据ID。在获取数据步骤中增加获取失败数据的步骤。

解决方法2: 当业务处理失败时,在Redis中记录下失败的数据信息。在业务处理环节增加处理已失败的数据重试步骤。

4、数据取余

利用zk获取到总实例数,计算模块实例需要处理的余值。

具体步骤如下:

  • 模块启动注册到zk,并计算当前实例需要处理的余值为x (当前实例index % 总实例数)
  • 从数据库取数据时,将数据主键%总实例数,如果余值为x,则取出数据,否则忽略此数据。
  • 当模块总实例数变化时,重新计算当前实例需要处理的余值。

风险: 由于模块实例所需要处理的数据固定,当数据取余后的值可能会命中同一个实例,造成模块各实例处理压力不平均。


观点仅代表自己,期待你的留言。

Linux开机自动启动Tomcat应用

前言

刚过国庆长假,工作机房断电,重回工作岗位发现N多的应用需要手动启动。感觉烦燥?
so.. 配置linux开机自启动

配置

在_/etc/rc.local_文件末尾增加以下内容

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

O_Path=`pwd`
cd /myapps/servers/apache-tomcat-6.0.43/bin/
sh startup.sh
cd $O_Path

需要注意以下几点:

  • 检查startup.sh是否已授执行权限,如果没有,执行chmod +x /myapps/servers/apache-tomcat-6.0.43/bin/startup.sh
  • 所需要执行的shell文件不能带参数。

经过以上配置则可以通过sudo reboot进行验证。当Linux重启成功后,可通过__/var/log/boot.log__来查看开机时的日志输出。

其它

who -r 查看开机级别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
who --help
用法:who [选项]... [ 文件 | 参数1 参数2 ]
显示当前已登录的用户信息。

-a, --all 等于-b -d --login -p -r -t -T -u 选项的组合
-b, --boot 上次系统启动时间
-d, --dead 显示已死的进程
-H, --heading 输出头部的标题列
-l,--login 显示系统登录进程
--lookup 尝试通过 DNS 查验主机名
-m 只面对和标准输入有直接交互的主机和用户
-p, --process 显示由 init 进程衍生的活动进程
-q, --count 列出所有已登录用户的登录名与用户数量
-r, --runlevel 显示当前的运行级别
-s, --short 只显示名称、线路和时间(默认)
-T, -w, --mesg 用+,- 或 ? 标注用户消息状态
-u, --users 列出已登录的用户
--message 等于-T
--writable 等于-T
--help 显示此帮助信息并退出
--version 显示版本信息并退出

观点仅代表自己,期待你的留言。

查看jvm堆内GC情况利器 - jstat

简介

jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。
以命令行方式运行对系统资源占用很小,在大压力下很少影响性能。并且运行要求低,只要通过Telnet或SSH等方式远程登录到服务器所在机器,就可以进行监控。在与jstat、jstack等工具结合使用时非常方便。

命令安装

  1. Oracle JDK在安装完成后bin目录下就会发现有jstat命令。
  2. Open JDK在安装完成后需要安装openjdk-devel开发包才能得到jstat命令。
    安装方法如下(以CentOS为例):
    使用 yum whatprovides '*/jstat'查包含jstat命令开发包的名称。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@wujianjun-linux bin]# yum whatprovides '*/jstat'
    Loaded plugins: fastestmirror, security
    Loading mirror speeds from cached hostfile
    base/filelists_db | 6.4 MB 00:00
    extras/filelists_db | 38 kB 00:00
    updates/filelists_db | 1.5 MB 00:00
    1:java-1.7.0-openjdk-devel-1.7.0.99-2.6.5.1.el6.x86_64 : OpenJDK Development Environment
    Repo : base
    Matched from:
    Filename : /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.99.x86_64/bin/jstat
    ......
    .....
    ...
    找到与jdk版本相对应的开发包进行安装
    1
    [root@wujianjun-linux bin]# yum install java-1.7.0-openjdk-devel
    安装完成后查找命令所在目录
    1
    2
    [root@wujianjun-linux bin]# whereis jstat
    jstat: /usr/bin/jstat /usr/share/man/man1/jstat.1.gz
    只要能显示相应目录,则表示安装成功。

    jstat使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [root@order-zhiMaoQu-com ~]# jstat --help
    invalid argument count
    Usage: jstat -help|-options
    jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

    Definitions:
    <option> An option reported by the -options option
    <vmid> Virtual Machine Identifier. A vmid takes the following form:
    <lvmid>[@<hostname>[:<port>]]
    Where <lvmid> is the local vm identifier for the target
    Java virtual machine, typically a process id; <hostname> is
    the name of the host running the target Java virtual machine;
    and <port> is the port number for the rmiregistry on the
    target host. See the jvmstat documentation for a more complete
    description of the Virtual Machine Identifier.
    <lines> Number of samples between header lines.
    <interval> Sampling interval. The following forms are allowed:
    <n>["ms"|"s"]
    Where <n> is an integer and the suffix specifies the units as
    milliseconds("ms") or seconds("s"). The default units are "ms".
    <count> Number of samples to take before terminating.
    -J<flag> Pass <flag> directly to the runtime system.
  • -gcutil: 查看gc情况
  • -class: 显示加载class的数量,及所占空间等信息
  • -compiler: 显示VM实时编译的数量等信息。
  • –gccapacity: 可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
    如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
    其他的可以根据这个类推,OC是old内纯的占用量。
  • -gcnew: new对象的信息
  • -gcnewcapacity: new对象的信息及其占用量
  • -gcold: old对象的信息。
  • -gcoldcapacity: old对象的信息及其占用量
  • -gcpermcapacity: perm对象的信息及其占用量
  • -printcompilation: 当前VM执行的信息

附GC显示术语解释:

  • S0C - 年轻代中第一个survivor(幸存区)的容量 (字节)
  • S1C - 年轻代中第二个survivor(幸存区)的容量 (字节)
  • S0U - 年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
  • S1U - 年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
  • EC - 年轻代中Eden的容量 (字节)
  • EU - 年轻代中Eden目前已使用空间 (字节)
  • OC - Old代的容量 (字节)
  • OU - Old代目前已使用空间 (字节)
  • PC - Perm(持久代)的容量 (字节)
  • PU - Perm(持久代)目前已使用空间 (字节)
  • YGC - 从应用程序启动到采样时年轻代中gc次数
  • YGCT - 从应用程序启动到采样时年轻代中gc所用时间(s)
  • FGC - 从应用程序启动到采样时old代(全gc)gc次数
  • FGCT - 从应用程序启动到采样时old代(全gc)gc所用时间(s)
  • GCT - 从应用程序启动到采样时gc用的总时间(s)
  • NGCMN - 年轻代(young)中初始化(最小)的大小 (字节)
  • NGCMX - 年轻代(young)的最大容量 (字节)
  • NGC - 年轻代(young)中当前的容量 (字节)
  • OGCMN - old代中初始化(最小)的大小 (字节)
  • OGCMX - old代的最大容量 (字节)
  • OGC - old代当前新生成的容量 (字节)
  • PGCMN - perm代中初始化(最小)的大小 (字节)
  • PGCMX - perm代的最大容量 (字节)
  • PGC - perm代当前新生成的容量 (字节)
  • S0 - 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
  • S1 - 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
  • E - 年轻代中Eden已使用的占当前容量百分比
  • O - old代已使用的占当前容量百分比
  • P - perm代已使用的占当前容量百分比
  • S0CMX - 年轻代中第一个survivor(幸存区)的最大容量 (字节)
  • S1CMX - 年轻代中第二个survivor(幸存区)的最大容量 (字节)
  • ECMX - 年轻代中Eden的最大容量 (字节)
  • DSS - 当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
  • TT - 持有次数限制
  • MTT - 最大持有次数限制

观点仅代表自己,期待你的留言。

jvm查内存泄漏利器 - jmap

简介

Oracle将jmap描述为一种“输出进程、核心文件、远程调试服务器的共享对象内存映射和堆内存细节”的程序。
通过它可以查看内存中对象实例,从而解决程序出现不正常的高内存负载、频繁无响应或内存溢出等问题。

命令安装

  1. Oracle JDK在安装完成后bin目录下就会发现有jmap命令。
  2. Open JDK在安装完成后需要安装openjdk-devel开发包才能得到jmap命令。
    安装方法如下(以CentOS为例):
    使用 yum whatprovides '*/jmap'查包含jmap命令开发包的名称。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@wujianjun-linux bin]# yum whatprovides '*/jmap'
    Loaded plugins: fastestmirror, security
    Loading mirror speeds from cached hostfile
    base/filelists_db | 6.4 MB 00:00
    extras/filelists_db | 38 kB 00:00
    updates/filelists_db | 1.5 MB 00:00
    1:java-1.7.0-openjdk-devel-1.7.0.99-2.6.5.1.el6.x86_64 : OpenJDK Development Environment
    Repo : base
    Matched from:
    Filename : /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.99.x86_64/bin/jmap
    ......
    .....
    ...
    找到与jdk版本相对应的开发包进行安装
    1
    [root@wujianjun-linux bin]# yum install java-1.7.0-openjdk-devel
    安装完成后查找命令所在目录
    1
    2
    [root@wujianjun-linux bin]# whereis jmap
    jmap: /usr/bin/jmap /usr/share/man/man1/jmap.1.gz
    只要能显示相应目录,则表示安装成功。

    jmap使用

    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
    root@wujianjun-linux ~]# jmap --help
    Usage:
    jmap [option] <pid>
    (to connect to running process)
    jmap [option] <executable <core>
    (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
    (to connect to remote debug server)

    where <option> is one of:
    <none> to print same info as Solaris pmap
    -heap to print java heap summary
    -histo[:live] to print histogram of java object heap; if the "live"
    suboption is specified, only count live objects
    -permstat to print permanent generation statistics
    -finalizerinfo to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
    dump-options:
    live dump only live objects; if not specified,
    all objects in the heap are dumped.
    format=b binary format
    file=<file> dump heap to <file>
    Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F force. Use with -dump:<dump-options> <pid> or -histo
    to force a heap dump or histogram when <pid> does not
    respond. The "live" suboption is not supported
    in this mode.
    -h | -help to print this help message
    -J<flag> to pass <flag> directly to the runtime system
  • jmap pid #打印内存使用的摘要信息
  • jmap –heap pid #java heap信息
  • jmap -histo:live pid #统计对象count ,live表示在使用
  • jmap -histo pid >mem.txt #打印比较简单的各个有多少个对象占了多少内存的信息,一般重定向的文件
  • jmap -dump:format=b,file=mem.dat pid #将内存使用的详细情况输出到mem.dat 文件
    通过jhat -port 7000 mem.dat可以将mem.dat的内容以web的方式暴露到网络,访问http://ip-server:7000查看。

针对内存泄漏这一问题,可以通过查看堆内存中存活对象实例及所占内存数查到对象是否一直被占用导致不能被GC回收。
如:

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
[root@wujianjun-linux ~]# jps |grep -v Jps
9495 Bootstrap

[root@wujianjun-linux ~]# jmap -histo:live 9495
num #instances #bytes class name
----------------------------------------------
1: 294149 29662584 [C
2: 187061 27752880 <constMethodKlass>
3: 187061 23955120 <methodKlass>
4: 15897 19221104 <constantPoolKlass>
5: 61107 13783928 [B
6: 15897 11445056 <instanceKlassKlass>
7: 13934 11267712 <constantPoolCacheKlass>
8: 280651 6735624 java.lang.String
9: 102892 5761952 org.springframework.asm.Item
10: 234489 5627736 org.springframework.asm.Edge
11: 84750 5424000 org.springframework.asm.Label
12: 66211 5296880 java.lang.reflect.Method
13: 63388 4503312 [I
14: 75275 4215400 org.springframework.asm.Item
15: 62580 4005120 org.springframework.asm.Label
16: 132458 3178992 org.springframework.asm.Edge

[root@wujianjun-linux ~]# jmap -histo:live 9495|grep com.yqyw
num #instances #bytes class name
----------------------------------------------
2788: 1 48 com.yqyw.user.rpc.client.UserClient
3570: 1 32 com.yqyw.pnr.sdk.client.ShopClient
3731: 1 32 com.yqyw.pnr.sdk.client.ShopClient
3842: 1 24 com.yqyw.user.rpc.client.impl.FavoriteServiceImpl

从以上输出可以看出目前堆内存中自己程序中的对象实例以及占用bytes,从而确定程序中哪些位置对这些对象进行创建使用且未被释放(典型的情况是将对象存入List等集合类而未被remove)。
也通过多次执行jmap -histo:live pid >mem.txt将每次结果定向到不同的txt中,然后对象对比。发现对象实例的增长与占用字节的变化。

附 - jmap输出中class name非自定义类的说明:

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L; reference an instance of class
S short signed short
Z boolean true or false
[ reference one array dimension

观点仅代表自己,期待你的留言。