微信互动问答

最近微信上的几个问题,我觉得还是有必要单独发一条信息来回答,:),也欢迎大家继续发送问题,分享你排查的case(如果有投稿,那是极度的欢迎),还有Java使用中碰到的一些“坑”。

问:btrace能否介绍一下调试经验?
答:btrace绝对是我排查问题工具集中的神器(内核级别systemtap也是神器,但它对性能的影响显然要比btrace大多了),btrace是生产环境运行时可使用的(也有些小问题,例如退出的时候可能有些残留等),btrace的使用非常简单,从官网上把btrace的tgz包下载解压后,通常修改下bin/btrace,主要是增加JAVA_HOME,将btrace改为可执行,接着就可以编写Java作为脚本来执行了,具体btrace使用的一些简单脚本的例子可见此篇文章,另外我可以给的建议是btrace这东西想用的熟练的话,就在自己本机的java代码里多试试就OK了,例如随便写段程序,跟踪下里面java.util.HashMap.put的调用堆栈,跟踪下调用时间消耗、传入的参数和返回值等,话说前几天我在生产环境用btrace还碰到个问题,我跟踪的那个方法输出的信息太多,结果为了让btrace停止,我按了多次ctrl+c,然后回车,估计按太多次了,竟然导致目标进程的heap被用满了,一直fgc。

问:最近有人说,在java里。尽量不要用大的try catch. 因为那样try块中的代码执行效率会降低,我也是第一次听说,之前我们都会在业务层把所有的代码的异常都捕获掉。这个问题还没找到答案。你怎么看的。
答:话说这个说法我还真不知道理由是什么,按照我知道的Java执行原理来说,不要在一个方法体里写过多行数的代码倒是有道理的,因为确实会导致性能下降,这种情况下性能下降的原因有两个:一是因为Java对运行时从bytecode–>native code的编译的方法默认有大小的限制,默认限制的大小为8000bytes,当一个方法超过8000bytes时,将不会对其进行编译,可通过-XX:-DontCompileHugeMethods来强制允许编译,但那样会导致codecache的区域被用满的可能性大增(上一个RT下降的Case就是因为code cache被用满);二是因为Java对inline方法的大小也有大小的限制,inline方法的大小限制通常很小,例如默认值是35bytes(可用jinfo -flag MaxInlineSize [pid]来查看),inline后方法的执行会加快一些(毕竟少了跳的步骤)。

问:你好,请问有办法查看java线程的网络流量么。我有一个程序要访问很多网络服务,运行一段时间后带宽就被占满了,并且一直维持这个状态。请问怎么排查这种情况。谢谢。
答:这种我还真不知道,我能想到的办法就是tcpdump看看主要是哪些频繁的请求,或者就是根据使用的网络框架等用btrace跟踪收发包部分的代码(例如可跟踪序列化/反序列化部分的代码),看看是什么地方在频繁做动作或收发较大的包。

问:jdbc和hibernate的选择
答:我的回答是对性能要求很高的场景,还是直接用jdbc吧,hibernate固然好用,但有个主要的问题是生成的sql看起来很痛苦,另外是还得等运行时生成,对于很多对性能敏感的应用而言,最好是所有用到的sql都能提前评估。

问:也碰见’占用系统内存不释放’查到os:malloc占用大多数”’没有思路了
答:这个问题的背景就是堆外内存被耗光,显然这已经用google perf-tools跟踪过了,看到了os::malloc占用了最多,这个时候可以进一步用pprof –gv来图形化的跟踪下os::malloc的堆栈,如果看到是unsafe_allocate,又如果是server端程序,那基本上可以肯定是由于Direct ByteBuffer造成的(因为Java中通常只有Direct ByteBuffer和AWT会调用unsafe来做allocate,实在不行还可以用btrace跟跟),这种情况下通过增加-XX:MaxDirectMemorySize可解决(具体也可参见这篇文章中的几个Case)。

问:请教您一个经验问题,就是缓存系统一般设置访问超时时间是多少?我们一个后台核心系统最近上了缓存memcached,用于实时计数,但是由于环境以及压测不够的原因,系统上线后跑了一段时间后突然出现大量缓存访问超时,而我们的超时时间是设置了两秒,导致整个系统都处理缓慢了。。我想要的是即使缓存在出问题的时候也不要拖慢系统。所以想问下您这里缓存系统的访问超时时间一般是多少比较合理?
答:对于大访问量系统而言,超时时间绝对是个超级敏感的值,当年我上线一个系统的时候就是由于其中一个远程调用的默认超时时间是1分钟而导致了一次严重故障,缓存这种无论如何都应该是非常快速完成的一个动作,因此通常建议其超时时间设置为50ms或100ms,即使是复杂逻辑的远程调用,我们通常也会把超时时间控制在1s以下,避免依赖的服务端变慢而导致自己的请求线程被耗光。

ps: 最近的一个排查了将近一周多时间的case是和Groovy相关的case,之后再来专门写篇文章说说Groovy的使用,要在生产环境中用好Groovy(尤其是大量使用)还是要小心不少“坑”的,但Groovy确实有一些好处,所以有些事。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

一个RT下降的奇怪Case

前天碰到一个有点怪异的case,应用运行一段时间后,RT会开始突然下降,只要重启就恢复,也就是说理论上和后端没什么关系,应用本身在RT下降的时候唯一能看出的就是cpu us比较高,但从线程堆栈、gc情况、top -H + jstack等都看不出什么,悲催的是出问题的一些机器都是2.6.18的,用不了perf。

还好昨天整个集群中为数不多的几台2.6.32的也出现了问题,于是赶紧perf top看看,于是看到了如下信息:
samples pcnt function DSO
1223.00 11.8% _ZN9CodeCache9find_blobEPv libjvm.so

主要是消耗在_ZN9CodeCache9find_blobEPv,看到CodeCache,不得不瞎蒙下是不是因为Code Cache满了造成的,按照这个思路,先grep下日志里是不是有类似的信息,grep后很快看到日志里有如下信息:
VM warning: CodeCache is full. Compiler has been disabled.
话说主要是上面这一行日志在一堆的日志里太不显眼了,所以很难注意到,个人觉得这种情况下jvm都可以考虑更为激进的退出等。

Code Cache区域是JVM用来存储经过C1/C2编译优化后的代码的,因为是存在内存中的,所以肯定得限制大小,Code Cache的最大大小可通过jinfo -flag ReservedCodeCacheSize来获取,通常在64 bit机器上默认是48m,当code cache用满了后,编译优化就被禁掉了,此时会回归到解释执行,RT可想而知不会好到哪去。

为什么这个应用会造成这样的现象呢?
和应用方了解了下,应用会采用groovy来加载一些脚本,在这些脚本修改后会动态更新,而且更新的是比较频繁的,这些groovy script执行的也是比较多的,这样的话基本是可以解释通的,意味着C2需要在groovy script更新后和执行到达阈值时重新编译,这自然会导致随着运行时间越长,Code Cache使用的就越多。

解决方法
Code Cache用满一方面是因为空间可能不够用,另一方面是Code Cache是不会回收的,所以会累积的越来越多(其实在不采用groovy这种动态更新/装载class的情况下的话,是不会太多的),所以解法一可以是增大code cache的size,可通过在启动参数上增加-XX:ReservedCodeCacheSize=256m(Oracle JVM Team那边也是推荐把code cache调大的),二是启用code cache的回收机制(关于Code Cache flushing的具体策略请参见此文),可通过在启动参数上增加:-XX:+UseCodeCacheFlushing来启用。

如果想在运行时查看code cache的大小,需要写段代码,目前只能通过JMX来获取到Code Cache区域的使用状况,代码类似如下(JDK 6下可以这样,JDK 5不支持通过pid直接连上VM,只能通过jmx remote server):
[code]
import java.io.File;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import com.sun.tools.attach.VirtualMachine;

public class CodeCacheUsage {

private static final String CONNECTOR_ADDRESS =
“com.sun.management.jmxremote.localConnectorAddress”;

public static void main(String[] args) throws Exception {
if(args.length != 1){
System.err.println(“Must enter one arg: pid”);
System.exit(0);
}
VirtualMachine vm = VirtualMachine.attach(args[0]);
JMXConnector connector = null;
try {
String connectorAddress = vm.getAgentProperties().getProperty(
CONNECTOR_ADDRESS);

if (connectorAddress == null) {
String agent = vm.getSystemProperties()
.getProperty(“java.home”)
+ File.separator
+ “lib”
+ File.separator + “management-agent.jar”;
vm.loadAgent(agent);

connectorAddress = vm.getAgentProperties().getProperty(
CONNECTOR_ADDRESS);
}

JMXServiceURL url = new JMXServiceURL(connectorAddress);
connector = JMXConnectorFactory.connect(url);
MBeanServerConnection mbeanConn = connector.getMBeanServerConnection();
ObjectName name = new ObjectName(
“java.lang:type=MemoryPool,name=Code Cache”);
System.out.println(mbeanConn.getAttribute(name, “Usage”));
}
finally {
if(connector != null)
connector.close();
vm.detach();
}
}

}
[/code]
传入pid,执行上面的代码后,会输出类似下面的信息:
javax.management.openmbean.CompositeDataSupport(compositeType=javax.management.openmbean.CompositeType(name=java.lang.management.MemoryUsage,items=((itemName=committed,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=init,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=max,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)),(itemName=used,itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)))),contents={committed=50331648, init=2555904, max=50331648, used=48281152})
上面的信息显示Code Cache区域初始化的时候为2555904,最大为50331648,已占用了50331648,使用了48281152。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

Spring 3.1.4之前版本中一个deadlock bug

有个应用碰到重启后没响应的问题,于是jstack -l dump下线程,看看都在做什么,jstack -l执行后,看到最后有提示有deadlock,这两个造成deadlock的线程的堆栈如下:
线程A:
[code]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinitionNames(DefaultListableBeanFactory.java:192)
– waiting to lock <0x000000077151f4a0> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:209)
at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:652)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:610)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:412)
at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:105)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:959)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:472)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
– locked <0x000000076d022290> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
[/code]
线程B:
[code]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:181)
– waiting to lock <0x000000076d022290> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:166)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:206)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429)
– locked <0x000000077151f4a0> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
– locked <0x000000076e0f2250> (a java.lang.Object)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
[/code]
可以看到线程A持有0x000000076d022290,在等0x000000077151f4a0,而线程B持有0x000000077151f4a0,在等0x000000076d022290,于是就deadlock了。

有意思的是deadlock的相关代码是Spring的,这个应用用到的Spring版本为2.5.6SEC02,翻了下代码,可以很容易看到这两段代码逻辑确实会造成deadlock。

从Spring的行为上来说,在这个场景中之所以会产生deadlock。
原因为B线程正在将所有singleton的spring bean进行初始化,在做这步动作时,需要synchronized (this.beanDefinitionMap),此后会遍历beanDefinitionMap所有的bean,对其中singleton的bean进行初始化,在进行初始化时,会需要synchronized (this.singletonObjects),以确保这些singleton的bean只会创建一次。

A线程此时的一个spring bean正在处理请求,对这个spring bean有配置aop的处理,当调用这个spring bean的某方法时会经过aop增强执行另外一个spring bean的一些动作,因此先通过DefaultSingletonBeanRegistry.getSingleton获取这个singleton的bean,此时会先synchronized (this.singletonObjects),然后逻辑一路会执行到DefaultListableBeanFactory.getBeanDefinitionNames,此时又必须synchronized (this.beanDefinitionMap),于是就死锁了。

因此按照这样的逻辑,其实只要是两个spring bean,假设一个是A,另外一个aop增强配置的spring bean B,在创建spring ApplicationContext时,如A已完成初始化,但B还未完成,此时又有请求A已经在处理,那么就会碰到上面的deadlock。

这个bug在Spring官方的jira上也有:https://jira.springsource.org/browse/SPR-7718
在3.1.4的版本中fixed,因此如果是此前的版本都有可能会不幸的碰到这个问题,解决方法一种是升级版本,另一种方法则是绕开这个bug,例如上面的A、B两个bean,可以在bean A上配置depends-on=”B”,强制让B在A之前完成初始化,这样也没问题。
3.1.4中的修复方法为修改了DefaultListableBeanFactory.preInstantiateSingletons():
synchronized (this.beanDefinitionMap) {
beanNames = new ArrayList(this.beanDefinitionNames);
}
相当于不再一直锁着beanDefinitionMap,而是cp出当前的beanDefinitionNames来遍历,这样就避免了deadlock问题,同时也提高了性能。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

Java程序员也应该知道的一些网络知识

对于需要编写网络通信的Java程序员而言,OS/网卡/网络结构等也需要有一些了解,以确保程序运行符合期望。

Java层面本身可通过Socket API来控制一些系统层面的参数(其他的诸如Netty/Mina/Grizzly这些通信框架也都提供设置这些参数的方法),主要是下面几个:
1. setTcpNoDelay(boolean on)
在不设置的情况下,默认为false,即禁用Nagle’s algorithm,具体这个算法的细节请自行google。
从经验上来说,只有在网络通信非常大时(通常指已经到100k+/秒了),设置为false会有些许优势,因此建议大部分情况下均应设置为true。
2. setSoLinger(boolean on,int linger)
这个的作用仅为socket关闭时如发送缓冲区里还有没发送完的包,等多久后关闭,通常来说不对这个做设置。
3. setSoTimeout(int timeout)
这个的作用是在执行socket read/write等block操作时,超时的时间(单位为ms),默认为0,也就是永不超时,nio是通知机制,因此通常不设置这个参数。
4. setSendBufferSize(int size)
缓冲区的大小决定了网络通信的吞吐量,理想的计算公式为:
Throughput = buffer size / latency
例如latency为1ms,buffer size为64KB,那么Throughput = 64KB / 0.001 = 62.5MB/s = 500Mb/s,理论上跑到了千兆网卡的一半。
通常建议buffer size设置为buffer size = RTT * bandwidth,其中RT为ping的rt,bandwidth为网卡的带宽,例如ping为1ms,bandwidth为1000Mb/s,那么buffer size = 1 * 0.001 * 1000/8 * 1024 = 128KB

这个的默认值取决于os的net.ipv4.tcp_wmem中的第二个值/2,例如net.ipv4.tcp_wmem为:4096 65536 16777216,那么Java Socket默认的sendbuffersize会为65536/2 = 32768Bytes = 32KB

也可通过调用setSendBufferSize来设置,但这个不确保调用会生效(例如设置的最大值超过了os的最大值,如os同时设置了net.core.wmem_max则以这个为最大值,则会以os的最大值为准),因此最好在调用后再get下确认是否生效。

sendBufferSize如果太小,对于往外发东西的server而言,如果碰到其中某x台client接收慢,可能会很快导致send buffer满,这个时候会写不了,对于像mina/netty等框架而言,通常而言在写不了的情况下会放入队列,但不幸的是通常这都是一个没有限制大小的队列,所以有些时候在这种情况下可能会导致OOM,因此在写这块代码时要特别注意对内存的保护(宁愿写失败也不能导致OOM)。

5. setReceiveBufferSize(int size)
设置接收缓冲区的大小,和sendBufferSize的行为一致。
6. setReuseAddress(boolean on)
设置为true,即表示在连接还在timewait状态时,就可复用其端口,这个对于大量连接且经常有timewait时适用,例如短连接的http server,默认为false,因此建议显式的设置为true。

除了Socket的这些API外,对于ServerSocket,还有一个参数比较重要,不过这个参数是在构造器中传入的:
ServerSocket(int port,int backlog)
这里的backlog主要是指当ServerSocket还没执行accept时,这个时候的请求会放在os层面的一个队列里,这个队列的大小即为backlog值,这个参数对于大量连接涌入的场景非常重要,例如服务端重启,所有客户端自动重连,瞬间就会涌入很多连接,如backlog不够大的话,可能会造成客户端接到连接失败的状况,再次重连,结果就会导致服务端一直处理不过来(当然,客户端重连最好是采用类似tcp的自动退让策略),backlog的默认值为os对应的net.core.somaxconn,调整backlog队列的大小一定要确认ulimit -n中允许打开的文件数是够的。
os上还提供了net.core.netdev_max_backlog和net.ipv4.tcp_max_syn_backlog来设置全局的backlog队列大小(一个是建立连接的queue总队列的大小,一个是等到客户端ACK的SYN队列的大小)。

除了上面这些Java API层面可设置的参数外,还有一些常见的网络问题需要调整os的参数来控制,例如大量TIME_WAIT,TIME_WAIT是主动关闭连接的一端所处的状态,TIME_WAIT后需要等到2MSL(Max Segment Lifetime,linux上可通过sysctl net.ipv4.tcp_fin_timeout来查看具体的值,单位为秒)才会被彻底关闭,而处于TIME_WAIT的连接也是要占用打开的文件数的,因此如果太多的话会导致打开的文件数到达瓶颈,要避免TIME_WAIT太多,通常可以调整以下几个os参数:
net.ipv4.tcp_tw_reuse = 1 #表示可重用time_wait的socket
net.ipv4.tcp_tw_recycle = 1 #表示开启time_wait sockets的快速回收
net.ipv4.tcp_fin_timeout = 30 #表示msl的时间

如果碰到的是大量的CLOSE_WAIT则通常都是代码里的问题,就是服务器端关闭连接了,但客户端一直没关。

除了上面这些参数外,对于长连接,尤其是对于需要在断开后自动重连的长连接场景:
1、最好采用心跳机制确保连接没断开
这里有两个原因,一是有些交换机会对连接有自动断开的机制(通常不会),二是像直接拔网线等这种连接本身是不知道已经断开了的。
2、如长连接是经过了中间的负载均衡设备
那有可能会导致的现象是realserver的连接数不均衡,这种情况是长连接很悲催的场景,通常只能是采用连接每处理多少个请求就自动断开重连来缓解这个问题。

通常服务器是两块网卡或更多,网卡的bonding模式会决定能使用的带宽,因此也最好能有些了解,bonding模式可通过cat /proc/net/bonding/*bond*来查看,通常都可以在输出里看到
Bonding Mode: …
常看到的例如:
fault-tolerance (active-backup),表示的为主备模式,即只有一块网卡是活跃的,因此最大的带宽是单块网卡的带宽;
Bonding Mode: IEEE 802.3ad Dynamic link aggregation,表示的为两块网卡都启用,这种情况下通常可跑到双网卡的带宽。

最后推荐下我自己以前写nfs-rpc框架的时候的一些优化经验的总结的文章:http://bluedavy.me/?p=334

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

cpu sy高case和static case(续)

关于前一篇的cpu sy high的那个case,问题的代码为:
[code]
private Semaphore sp = new Semaphore(1);

private AtomicBoolean readyFlag = new AtomicBoolean(true);

public long next(){
while (!this.readyFlag.get())
{
Thread.yield();
}

long m = this.max.get();
long rtn = this.idGenerator.next();

if (rtn > m) {
if (!this.readyFlag.get())
return next();
try
{
this.sp.acquire();
this.readyFlag.set(false);

do sth…

this.readyFlag.set(true);
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
println(“released”);
this.sp.release();
}

return next();
}
return rtn;
}
[/code]

这段代码除了Thread.yield这个地方需要吐槽外,还有好几个地方要吐槽…

首先要吐槽的是目前这样的写法,如代码执行到do sth…那个部分,然后触发了InterruptedException,就会直接导致readyFlag永远为false,于是之后的代码只要调用到next(),就永远会陷入Thread.yield,cpu sy很快就会很high。

第二个要稍微吐槽的是检查readyFlag的那段代码在sp.acquire之以后也应该放下。

这段代码我写的话,会改造成类似如下:
[code]
private AtomicBoolean readyFlag = new AtomicBoolean(true);

private ReentrantLock lock = new ReentrantLock();

private Condition waitReadyFlag = lock.newCondition();

public long next(){
while(!readyFlag.get()){
try{
lock.lock();
if(!readyFlag.get())
waitReadyFlag.await();
}
catch(InterruptedException e){
// IGNORE
}
finally{
lock.unlock();
}
}

long m = this.max.get();
long rtn = this.idGenerator.next();

if (rtn > m) {
try{
lock.lock();
if(readyFlag.get()){
return next();
}
readyFlag.set(false);
// do sth…
}
finally{
if(!readyFlag.get()){
readyFlag.set(true);
waitReadyFlag.signalAll();
}
lock.unlock();
}
return next();
}

return rtn;
}
[/code]

————分隔线,再说另外一个话题————

类的load/link/initialization是Java中最为基础的知识,细节的信息在JVM规范中有描述,这里还是拿之前的那篇文章里的例子来继续说下关于static的初始化(有些时候还是挺容易写错)。

假设case的代码修改为如下:
[code]
public class A {

public static void main(String[] args) {
System.out.println(B.NICK);
System.out.println(B.NAME);
}

}

public class B{

public static final String NICK = “nick”;

public static final String NAME = getName();

private static String getName(){
return “bluedavy”;
}
static{
System.out.println(“hellojava”);
}
}
[/code]

上面这段代码执行的输出为:
nick
hellojava
bluedavy

不按规范里那么学术化的来解释上面的原因,感兴趣的可以对上面编译后的A、B两个类用javap -c -verbose -private来查看里面的具体信息,javap -c -verbose B后可以看到如下的关键信息:
[code]
public static final java.lang.String NICK;
Constant value: String nick

public static final java.lang.String NAME;

static {};
Code:
Stack=2, Locals=0, Args_size=0
0: invokestatic #14; //Method getName:()Ljava/lang/String;
3: putstatic #18; //Field NAME:Ljava/lang/String;
6: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #26; //String hellojava
11: invokevirtual #28; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
14: return
[/code]
可以看到NICK后面标为Constant value,而NAME的赋值则放在了static block中,java在将源码编译为字节码时,会将static对应的赋值动作或源码中的多个static block(例如{…} static{…})按照顺序全部放到static block中,如用btrace在运行时要跟踪这个static block的执行,可跟踪方法,就类似构造器在运行时对应的方法名为
在编译为字节码后,对于B的代码而言,其实相当于变成了:
[code]
public class B{

public static final String NICK = “nick”;

public static final String NAME;

private static String getName(){
return “bluedavy”;
}
static{
NAME = getName();
System.out.println(“hellojava”);
}
}
[/code]

至于为什么在调用B.NICK的时候没触发static block的执行,javap看下A的字节码就懂了,查看时可看到如下关键信息:
[code]
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22; //String nick
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
8: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
11: getstatic #30; //Field com/bluedavy/spike/B.NAME:Ljava/lang/String
;
14: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
17: return
[/code]

可看到System.out.println(B.NICK)这行相当于被直接改为了System.out.println(“nick”),仔细看过JVM规范的同学会知道对于constant类型的static final变量,在编译为字节码阶段就会直接被替换为对应的值,这里有个小小的坑是可能会碰到的,就是编译的时候只编译了对应的static final变量的代码,但没重编译相应引用的代码,那就悲催了,运行的时候还会是之前的值。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

一个cpu sy高排查的case

前几天碰到了一起cpu sy高的case,当时cpu的sy已经跑到了80%左右,cpu sy基本都是上下文切换多造成的,于是dump下线程堆栈,看看都在做什么,看到线程堆栈里一大堆线程的在Thread.yield这步,这就很容易理解为什么cpu sy跑到那么高了。

于是翻查了下堆栈对应的代码,代码类似如下:
[code]
public long next(){
while (!this.readyFlag.get()){
Thread.yield();
}

long m = this.max.get();
long rtn = this.idGenerator.next();

if (rtn > m) {

this.readyFlag.set(false);

this.readyFlag.set(true);

return next();
}
return rtn;
}
[/code]

看到这段代码实在是想吐槽…无非就是想在有线程做rtn > m中间的动作时,其他进入next()的代码就等在最开始,竟然用Thread.yield这种方式,介个…

这段代码如果我写呢,会去掉readyFlag,改成用ReentrantReadWriteLock,rtn > m中用WriteLock,外面用ReadLock,这样可以避免当代码进入到rtn > m后,要通过Thread.yield这样的方式去等到readyFlag变成true。

感兴趣的也可以用下面的代码去跑跑,可以立刻看到cpu sy high起来…
[code]
public class Spike {

public static void main(String[] args) throws Exception{
new Spike().go();
}

public void go(){
for (int i = 0; i < 200; i++) { new Thread(new Yield()).start(); } } class Yield implements Runnable{ public void run() { while(true){ Thread.yield(); try { Thread.sleep(1); } catch (InterruptedException e) { // IGNORE } } } } } [/code] --------------微信互动-------------- 之前发的信息里留过一道static的题,那道题的正确答案是:bluedavy,如果B.NAME对应的是一个B中的static方法,那么static块中的代码会先执行,因此如果B的代码是:
[code]
public class B{
public static final String NAME=getName();
private static String getName(){
return “bluedavy”;
}
static{
System.out.println(“hellojava”);
}
}
[/code]
那么那道题输出的就会是
hellojava
bluedavy

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

诡异的http请求返回499的case排查(终)

上篇文章中,关于http请求返回499的这个case,并没有排查到根本原因,当时猜测是classloader deadlock的bug,没想到在前几天这个问题又再次重现,这次吸取了上次的经验,不仅做了jstack,还用gcore做了core dump,在拿到了core dump的情况下,好处是即使jstack -m不work,也可以通过gdb + jstack捞出线程的java stack和native stack,于是当时的做法就是先通过core dump文件拿到了线程的完整堆栈信息(java stack & native stack),当时猜想的是既然是死锁,应该可以很容易看到。

但不幸的是,即使拿到了这个,看了很久仍然是完全没头绪,开始瞎折腾,大部分的线程都停在了类似FinalClass.staticMethod这一步,统计了下停在这步的线程数,发现是245个,而处理的线程池是250个,于是捞出了另外5个线程的信息,看看都在干什么。

这5个线程中其中有3个竟然也停在了另外一个Class的static method的调用上,而另外2个线程,一个的堆栈类似如下:
[code]
Thread 29548: (state = BLOCKED)
– C.loadConfigs() @bci=6, line=465 (Interpreted frame)
– C.() @bci=346, line=448 (Interpreted frame)
– A.() @bci=235, line=143 (Interpreted frame)
[/code]

另一个堆栈类似如下:
[code]
Thread 30132: (state = BLOCKED)
– D.getContentList() @bci=4, line=91 (Interpreted frame)
– D.initContainer() @bci=9, line=67 (Interpreted frame)
– D.() @bci=23, line=49 (Interpreted frame)
– D.() @bci=4, line=41 (Interpreted frame)
– M.() @bci=27, line=188 (Interpreted frame)
[/code]

从这个堆栈也看不出什么问题,于是决定去翻看下D和B这两个类的对应的行的动作是什么,看代码的时候发现C.loadConfigs里会去调用D的一个static final的field,而D的getContentList里面会调用C的一个static方法,代码类似如下:
[code]
public class C{
public static void getList(){

}
private static void loadConfigs(){
D.INSTANCE.list();
}
static{
loadConfigs();
}
}

public class D{
private static final D INSTANCE = new D();
private D(){
initContainer();
}
private void initContainer(){
getContentList();
}
private void getContentList(){
C.getList();
}
}
[/code]
看到这两段代码,基本可以猜测到问题所在了,脑袋里对这两段代码运行了,发现如果一个线程正执行到D的getContentList的C.getList那一行,而另外一个线程正执行到C的D.INSTANCE.list()那一行,那就deadlock了,只能说,这算是一个比较隐藏的bug,因为其锁是在static Object或Field上,而不是主动编写的并发锁等。

但为什么另外那245个线程会停在看起来和这5个线程的动作完全没关系的一步上呢,翻查那一步动作的代码,发现其堆栈类似如下:
FinalClass.
S.parse()
而对应的FinalClass的static代码里会调用C的一个static的field(并且这个field对应的是C的一个static的方法),这样就可以解释了,由于C的static陷入了deadlock,所以这245个线程也都在进入了等待。

根据上面的分析,终于彻底解决了这个case,这个case给自己的教训就是对于这样的现象要更加仔细的分析stack,这次完全是靠后面仔细的看了stack猜出的原因,这个case不好排查的原因是不像普通的deadlock问题,在jstack中都可以直接看出来,偏偏是由于static Object/Field的整个过程是有锁的,但其是在native stack上的,导致了很不好排查,再加上代码层面是绕了几圈,因此导致从jstack上很难看出关联性。

根据这个case我写了一个简单的demo,感兴趣的可以从下面的url下载:
http://hellojava.info/cases/deadlockcase/demo.zip
解压后直接执行java Main,即可看到和我的case一模一样的状况,可以体验下怎么去排查这样的case,:)

对应的源码请从这下载:
http://hellojava.info/cases/deadlockcase/demo-src.zip

最后贴一个问题吧,请问下面的代码运行时会输出什么?
[code]
public class A{
public static void main(String[] args){
System.out.println(B.NAME);
}
}

public class B{
public static final String NAME=”bluedavy”;
static{
System.out.println(“hellojava”);
}
}
[/code]

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

Java程序员也应该知道的too many open files的一些事

Java应用也有很大的概率会碰到too many open files这样的异常,此异常的主要原因是进程打开了太多的文件(可通过lsof | grep [pid] | wc -l来查看),linux对每个用户/进程都会有打开的文件数的限制。

当前进程打开的文件数的限制可通过cat /proc/[pid]/limits来查看,看到的内容类似如下:
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 10485760 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 387075 387075 processes
Max open files 131072 131072 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 387075 387075 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
其中的max open files即为进程可打开的文件数,所有的io都会涉及到文件的打开等,例如建立连接等。

那么进程的这些limits又是怎么被赋值的,一种是在启动脚本里通过ulimit来主动赋值,例如ulimit -n 10000,表示把要启动的进程的max open files限制为10000,另一种是在代码中通过setlimit的方式来设置,在不做主动赋值的情况下,那么会继承父进程的limits,如没有父进程,则继承当前启动的用户的。

用户的limits可通过/etc/security/limits.conf来控制,也可通过放入/etc/security/limits.d/*.conf来控制,limits.conf内容怎么写这个在文件本身有很详细的解释,就不在这里多说了。

跟随机器启动的进程会继承init的limits信息,init的max open files在2.6.18/2.6.32里默认都是1024,这个是在代码里写S的固定值:
/*
* boot-time rlimit defaults for the init task:
*/
#define INIT_RLIMITS
{

[RLIMIT_NOFILE] = { INR_OPEN, INR_OPEN },
….
}
#define INR_OPEN 1024 /* Initial setting for nfile rlimits */

至于如何查到对应的Java代码中为什么打开了这么多的文件,主要可通过btrace监控文件API和socket open的相关API的调用,从而确定原因。

顺带说下,max processes是用于限制进程本身最多派生的子进程数以及线程数个数,对于java而言,主要就是限制线程数,可通过ulimit -u来做设置。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

诡异的http请求返回499的case排查

前天的时候碰到了这个超级诡异的case,现象是在本机curl一个本机的静态页面,nginx返回499,google了下知道499的原因通常是服务端处理时间过长,导致客户端关闭了连接造成的,因此问题的核心就是要排查为什么服务端处理时间过长。

curl访问的静态页面是从nginx代理访问到jboss的,于是直接改为用jboss的端口去curl那个静态页面,仍然是非常的慢,因此看起来问题出在了jboss端的处理。

要看为什么处理时间过长,主要就看java进程在做什么,于是jstack看了下线程都在做什么,jstack后看到了所有的jboss处理线程全部都被占满了,这显然是导致为什么curl一个静态页面都那么慢的原因。

继续看看为什么处理线程全被占满了,从堆栈信息来看,所有的线程几乎都在同一个步骤,诡异的是线程的信息状态,我第一次看到这样的线程信息:
“http-0.0.0.0-7001-228” daemon prio=10 tid=0x00007f7b9a1d0000 nid=0xae5c in Object.wait() [0x00000000619ec000]
java.lang.Thread.State: RUNNABLE
这个线程信息的诡异就在于动作是在Object.wait,但Thread的state却是Runnable….

先不管这个,继续根据堆栈执行到的那一行代码去排查,当时猜测的是八成是死循环之类的造成的,不过cpu消耗不高,因此也不太像,反编译代码后看到确实不可能是死循环,因为那行代码对应的就是类似这样的一个调用:
Object result = FinalClass.staticMethod(xxx);
这就相当诡异了,看起来无论如何都不应该停在这一步,要停也应该停在staticMethod里面的堆栈…

于是瞎蒙,继续用pstack来查查,pstack后看到对应的这些线程是停在了以下的步骤:
Thread 306 (Thread 0x4fada940 (LWP 69941)):
#0 0x0000003d6640af59 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x00007f234fb3138b in os::PlatformEvent::park() () from libjvm.so
#2 0x00007f234fb0ffc5 in ObjectMonitor::wait(long, bool, Thread*) () from libjvm.so
#3 0x00007f234fa748eb in ObjectSynchronizer::waitUninterruptibly(Handle, long, Thread*) () from libjvm.so
#4 0x00007f234fa4ce66 in instanceKlass::initialize(Thread*) () from libjvm.so
#5 0x00007f234fba30a7 in LinkResolver::resolve_static_call(CallInfo&, KlassHandle&, symbolHandle, symbolHandle, KlassHandle, bool,
bool, Thread*) () from libjvm.so
#6 0x00007f234fa3da96 in LinkResolver::resolve_invokestatic(CallInfo&, constantPoolHandle, int, Thread*) () from libjvm.so
#7 0x00007f234fa3c514 in InterpreterRuntime::resolve_invoke(JavaThread*, Bytecodes::Code) () from libjvm.so

看到这个就可以解释为什么之前线程上的信息是in Object.wait,至于为什么jstack上看到Thread的State是Runnable,原因是那里是根据java stack来看的,看不到native stack上的状况(在比较高的一些jdk版本上可通过执行jstack -m直接将java stack和native stack一起打印出来,那样会方便很多),否则的话只能自己通过pstack + jstack,并结合线程id这个来做对应关系。

从pstack的堆栈,可以解释为什么java stack停在了FinalClass.staticMethod那行,因为这个时候才刚开始初始化这个static类,多个线程初始化static类时,只有一个会执行,其他的都会在等待中,但这里没法解释的是为什么一直在wait….

后来就这个问题求助了下@rednaxelafx ,因为没有现场了,猜测可能是classloader的deadlock bug,具体见rednaxelafx写的这个case
从这个case描述来看,只有jdk 7/+才彻底解决了这个问题。

关于这个case的排查,只能说目前猜测是这样的,因为现在没法重现,所以也不能完全确认,rednaxelafx给我对这个case如果重现的排查的建议值得学习,就是如重现,可以先做下core dump,这样之后定位就会很好做了。
如果是class loader deadlock的bug,那就不太好解决了,基本上只能自己强行先做上面提到的FinalClass的装载,避免掉多线程同时来做这件事时的竞争问题。

ps: 最近又碰到了两起并发场景下错误使用HashMap导致的cpu耗光的case,其中一个case中代码是这样写的:
if (this.newCacheMap.get(key) == null) {
this.newCacheMap.put(key, Integer.valueOf(1));
return null;
}
// do sth through rpc…
怎么改比较合适呢,我的建议是将newCacheMap换为ConcurrentHashMap,然后把代码修改为:
if (this.newCacheMap.get(key) == null) {
if(this.newCacheMap.putIfAbsent(key, Integer.valueOf(1)) == null){
return null;
}
}

———微信互动——–
问:记得你之前说过cms gc更适用于64 bit系统,原因是?
答:其实原因是32 bit的系统上,例如linux单个进程最多用3g的空间,在3g这种heap大小的情况下,cms gc压根就没任何优势,ParallelOldGC就OK了,因此说了上面这话,不过即使是64 bit其实也需要看heap size多大,我的建议是在可接受的fgc暂停时间的情况下,都不应该选cms gc,cms gc实现的复杂度(这和它的目标有关系)决定了它的稳定性很难和Parallel GC去比,另外cms gc的碎片问题绝对是让人头疼的问题。

问:OutOfMemoryError: requested …bytes for Chunk::new. Out of swap space,这个能做个专题么?
答:这个在我以前写的《常见Java问题的排查方法》中有写到过,所以就不再专门讲了,简单来说,造成这个的可能原因是地址空间耗尽(64 bit上基本不可能),另外一个就真的是内存耗尽,具体的解决方法去看看我的那篇排查方法的blog吧,在http://bluedavy.me上。

=============================
欢迎关注微信公众号:hellojavacases
关于此微信号:
用于分享Java的一些小知识点,Java问题排查的Cases,Java业界的动态,以及和大家一起互动讨论一些Java的场景和问题。

公众号上发布的消息都存放在http://hellojava.info上。

cpu iowait高排查的case

在之前的常见的Java问题排查方法一文中,没有写cpu iowait时的排查方法,主要的原因是自己之前也没碰到过什么cpu iowait高的case,很不幸的是在最近一周连续碰到了两起cpu iowait的case,通过这两起case让自己学习到了很多系统层面的知识,也许这些知识对于熟悉系统的人来说没什么,不过对于写Java的同学我觉得还是值得分享下(由于Java基本不用于存储类型的场景,所以通常来说碰到iowait高的机会会比其他几类问题更低很多)。

当出现iowait高时,最重要的是要先找出到底哪个进程在消耗io,以最快的速度解决问题,但linux默认的一些工具例如像top、iostat等都只能看到io的消耗状况,但对应不到是哪个进程在消耗,比较好用的用来定位的工具是iotop,不过有些环境要装上可能不太容易,装上了后直接执行iotop,就可以看到到底是哪个进程消耗了比较多的io,对应解决问题而言,通常在找到进程后杀掉基本就算解决了(还有一种方法是通过打开syslog以及blk_dump来看一段时间内消耗io的进程,但在我的两个case里试过效果不太理想)。

但通常而言,上面的方法只能算勉强解决了问题,但还是没有定位到程序里哪个地方有问题,甚至有可能重启仍然iowait很高,于是需要借助其他工具来进一步排查,所幸系统层面是有这样的工具,主要可通过blktrace/debugfs来定位到到底是读或写哪个(或哪些)文件造成了iowait高(这个方法主要学习自阿里集团内核组的伯瑜的一篇blog)。

在装上了blktrace后,先mount -t debugfs none /sys/kernel/debug下,然后可通过iostat查看到底是哪个设备在消耗io,例如假设看到是sda在消耗,那么即可执行blktrace /dev/sda,在执行时将会自动在执行的目录下生成一些sda.blktrace.*的文件,当觉得采集的差不多后,即可ctrl+c停掉。

之后执行blkparse sda.blktrace.* > result.log,再生成了result.log后执行grep ‘A’ result.log | head -n 5看看在采集的这段过程中,消耗io比较多的地方在哪,例如在我碰到的case中执行后看到的为:
8,0 11 1 0.000071219 248366 A R 218990140 + 16 <- (8,9) 148994872 8,0 11 1 0.000071219 248366 A R 218990140 + 16 <- (8,9) 148994872 8,0 11 1 0.000071219 248366 A R 218990140 + 16 <- (8,9) 148994872 8,0 11 1 0.000071219 248366 A R 218990140 + 16 <- (8,9) 148994872 8,0 11 1 0.000071219 248366 A R 218990140 + 16 <- (8,9) 148994872 这里A后面的R到底的意思是读(如果是写则为WS),和之前iostat看到的是一样的,io主要是大量的读造成的。 通过上面的信息8,0和(8,9)可以看到主要的消耗是在sda9(这个通过iostat也可以看到),(8,9)后的148994872代表读的扇区号。 再通过debugfs -R 'stats' /dev/sda9 | grep 'Block size'可以找到sda9的block size,在我碰到的case中block size是4096,也是通常ext2/ext3文件系统默认的。 每个扇区的大小为512,148994872/(4096/512) = 18624359即可找到文件系统上对应的block号,之后通过debugfs -R 'icheck 18624359' /dev/sda9可找到对应的inode,在我碰到的case中,执行后的结果为: Block Inode number 18624359 18284971 而debugfs还提供了通过inode number找到具体的文件的功能,于是就好办了,继续执行debugfs -R 'ncheck 18284971',执行后看到类似如下的信息: Inode Pathname 18284971 [相应的文件名] 在找到了文件名后就好办了,可结合之前的iotop或直接lsof找出对应的进程id,然后就可以看看从代码上层面怎么避免对此文件的大量读。 除了上面的这种case外,还有些情况的iowait其实是比较简单的,例如读写了巨大的文件(通常在大量出现异常时可能会出现)... 在解决上周碰到的两个cpu iowait高的case中,其中一个是如上面的业务代码造成,但另一个则是和raid卡配置相关,因为从iostat来看,当时写的量也不是很大,但iowait却比较高,请系统的人帮忙看了后,告诉我是因为raid卡写策略配置的问题,我之前对raid卡的这些配置完全不懂。 通过服务器上会带有raid卡,而现在的raid卡基本是带有cache的,为了保障cache里的数据的安全性,通常raid卡会带有电池或电容,相对而言电容的故障率比较低,raid卡会提供写策略的配置,写策略通常是Write Back和Write Through两种,Wirte Back是指写到cache后即认为写成功,Write Through是指写到磁盘上才算成功,通常Raid卡的写策略会分为正常时,以及电池或电容出问题时两种来配置,而在碰到的case中是因为配置了当电池/电容出问题时采用Write Through,当时机器的Raid卡的电池出故障了,所以导致策略切换为了Write Through,能够支撑的iops自然是大幅度下降了,而在我们的场景中,本地数据丢掉是无所谓的,所以Raid卡的策略可以配置为即使电池出故障了也仍然采用Write Back。 通常各种Raid卡都会提供工具来配置写策略,例如HP卡的hpacucli,可通过cat /proc/scsi/scsi查看硬盘和Raid卡的信息(可以先用cat /proc/mdstat来查看raid信息),有助于确认raid卡的cache/cache容量/电池以及硬盘本身能支撑的iops等。 因为建议在碰到iowait高的场景时,可以先看看raid卡的写策略,如果没问题的话再通过iotop、blktrace、debugfs、lsof等来定位到具体的根源。 可见即使是对于Java的同学,在排查问题时对于各种系统工具、硬件层面的一些知识还是要有些了解,不一定要很深,但至少要知道,对于工具要会用。 ------------微信互动------------ 1. 在设置LANG即可改变Java的默认编码的原因分析的那篇文章发出后,有同事给我反馈其中的这段话:“顺带说一句,其实即使在启动时带上-Dfile.encoding也不会改变Charset.defaultCharset(这个默认编码还是以LC_CTYPE为准),加上这个参数只会改变系统属性中的file.encoding值而已。” 和他测试时的表现不一致,后来我确认才发现这段话是我过于武断的判断了,在这里要纠正为“,-Dfile.encoding是可以生效的,只是在指定的file.encoding没有对应的Charset实现时,会默认为UTF-8,这里要稍微注意的一点是,file.encoding的指定方法和LANG有些不同,LANG通常的语言大集.小集,例如zh_CN.gbk,而file.encoding就直接是语言的小集,例如gbk。“。 问: String.intern那篇文章里关于s1 == s2的那个case,为什么要保证s1.intern是在String s2赋值的那行之前,因为如果String s2赋值是在s1.intern之后,那么即使是在jdk 7中s1 == s2也会是false。 答: 这里的原因是在JDK 7中,当执行String.intern时,如String Pool中没有相同内容的,则会将此内容作为key,而value即为此String对象,在对String进行赋值时,会先在String pool中找是否会相同内容的,有的话则直接就指向改对象,没有就新创建一个对象,因此在那篇文章的case里,如果在String s2赋值之前执行s1.intern,那么String pool里就已经有了”GoodMorning“对应的String对象实例了,也就是s1,因此之后在执行String s2 = "GoodMorning"时,就已经是直接将s1赋给了s2,但调转这行代码后就不一样了。 问:你说之前那个cpu us诡异的case的根本原因是BeanUtils里频繁调用Class.getMethod造成的,那不是很容易出现这个现象? 答:其实出现这个现象不会太容易,原因是如果传入的methodName基本是固定的,那么其实执行String.intern真正放进String pool的并不会多,而在我之前碰到的case里为什么有问题,原因是类似struts之类的结构,通常是会把一个form里的所有域直接填充到对应的对象里去,而如果form里有一些隐藏域,是和每次请求有关的话,那么BeanUtils在执行class.getMethod时传入的methodName可能就是经常变化的,如果这个页面访问量又比较大的话,那就很容易导致String pool性能疯狂下降,建议最好是在用BeanUtils填充对象时,先检查下是否有必要。 ============================= 欢迎关注微信公众号:hellojavacases 关于此微信号: 用于分享Java的一些小知识点,Java问题排查的Cases,Java业界的动态,以及和大家一起互动讨论一些Java的场景和问题。 公众号上发布的消息都存放在http://hellojava.info上。