标签归档:java

Javassist 字节码操作库

研究 Javassist 的起因是维护的项目是从外部采购的一个系统,有几个核心的类在构造函数里从数据库加载一些配置信息,而这些配置信息基本是不会改变的,应当缓存起来。这些类没有源码,class 文件还是混淆过的。

想来想去,觉得用字节码操作工具来改写是比较合适的,把改写后生成的 class 文件替换原来的,这样使用这些类的地方也不用做任何修改。

1. Javassist 简介

Javassist 是一个字节码操作库,通过它,可以在运行时改写类:添加新的字段、方法和构造函数,改变类、父类和接口的方法。

Javassist 定义了 CtField, CtMethod, CtConstructor, CtClass 来表示 字段、方法、构造函数、类。

继续阅读

Java finalize 方法与垃圾回收

JVM 对于覆写了 Object.finalize() 方法的类是实例,在垃圾回收时,先放进一个队列里,
然后用一个线程执行这些实例的 finalize 方法,最后才做内存回收。

这个机制的问题是:它会影响垃圾回收,如果实现了 finalize 的实例很多,那么回收队列就会很长,而只有一个线程
在执行这些方法,这些对象就会回收得很慢。

最近碰到的一个问题跟 finalize 方法有关。
厂商提供的代码把 javax.sql.Connection 做了层封装,覆写了 finalize 方法,在里面把 Connection 关闭了。

一个同事调用的方法定义是用封装类作为入参,他调用的时候是直接 new 了一个封装类,把 connection 传进去,后续数据库操作就不定位置出现连接关闭的异常。原因就是这个临时 new 出来的封装类被垃圾回收,把 connection 关闭了。由于垃圾回收的时间是不确定的,所以报异常的时间点也不确定。

计算 一个点 附近的地方

问题

随着地理位置的应用普及,越来越多类似计算 一个点 附近的酒店的需求。比如,显示当前位置2000米范围内的 7 天酒店。

一般来说,在系统里都有一张表,存储了每个 7 天酒店的位置信息,表结构可以简化为三个字段 tb_location(ID, lng, lat),其中 lng:表示经度;lat:表示纬度,这两个字段都是数值类型的,且建了索引。

现在要把 2 公里范围内的 7 天酒店找出来,有的人写的 SQL 语句大概是这样的:
select id from tb_location where calc_distance(lng, lat, curLng, curLat) < 2000

这个语句的问题是没法利用索引,需要全表扫描,计算每一条记录与当前位置的距离,效率很低。

优化

用一张图来说明优化策略:
附近地点计算优化

继续阅读

Java 对象内存布局

本文来自:http://www.ibm.com/developerworks/cn/java/j-codetoheap/

寻址能力与用户空间

进程能够处理的位数取决于处理器能寻址的内存范围,处理器的寻址能力取决于处理器的位数,比如 32 位能寻址 2^32,也就是 4G。

处理器提供的部分可寻址范围由 OS 本身用,供操作系统内核以及 C 运行时。OS 和 C 运行时占用的内存数量取决于所用的 OS,比如 Windows 默认占用 2GB。剩余的可寻址空间是供运行的实际进程使用的内存(用户空间)。

对于 Java 应用程序,用户空间是 Java 进程占用的内存,实际上包含两个池:Java 堆和本机(非 Java)堆。Java 堆的大小由 JVM 的 Java 堆设置控制:-Xms 和 -Xmx 分别设置最小和最大 Java 堆。在按照最大的大小设置分配了 Java 堆之后,剩下的用户空间就是本机堆。
32bit JVM

可寻址范围总共有 4GB,OS 和 C 运行时大约占用了其中的 1GB,Java 堆占用了将近 2GB,本机堆占用了其他部分。请注意,JVM 本身也要占用内存,就像 OS 内核和 C 运行时一样,而 JVM 占用的内存是本机堆的子集。

继续阅读

Java SE 6 故障排除指南 – 4、系统崩溃故障排除

崩溃或致命错误导致进程异常终止。有各种可能的理由导致崩溃。例如,崩溃可能是由于HotSpot VM、系统库、Java SE 库或API、程序本地代码、甚至操作系统里的 bug。极端因素如操作系统资源耗尽也可以导致崩溃。

因 HotSpot VM 或 Java SE库代码导致的崩溃是罕见的。本章提供如何检查崩溃的建议。有时候可以变通崩溃直到导致崩溃的源被诊断和修复(也就是可以避开崩溃)。

通常,崩溃的第一步是定位致命错误日志。这是HotSpot VM生成的文本文件。附录C-致命错误日志 解释了如何定位文件和文件的详细描述。

4.1 崩溃样本

本节展示一些样本来说明错误日志是如何用于启发崩溃原因的。

4.1 测定哪里发生崩溃

错误日志头显示了有问题的帧。见 C.3 格式头。

如果顶层帧是本地帧且不是操作系统本地帧,这表明问题可能发生在本地库,而不是在JVM里。解决崩溃的第一步是研究本地库发生崩溃的源。有三个选择,取决于本地库的源。

如果本地库是由你的程序提供,研究你的本地库的源代码。选项 -Xcheck:jni 可以帮助查找本地 bug。见 B.2.1 -Xcheck:jni 选项。

如果你的程序使用的本地库是由其他供应商提供,报告bug,提供致命错误日志信息。

继续阅读

Java SE 6 故障排除指南 – 5、挂起或循环进程故障排除

本章为挂起或循环进程的故障排除在特定程序上提供了信息和指导。

问题在涉及挂起或循环进程时发生。挂起可能因为多种原因发生,但经常是源于程序代码、API代码或库代码里的死锁。挂起甚至是因为 HotSpot VM的bug。

有时候,一个表面上是挂起的可能是个循环。例如,VM进程里的bug导致一个或多个线程进入死循环,会消耗掉所有可得CPU周期。

诊断挂起的最初步骤是找出VM进程是空闲还是消耗了所有可得CPU周期,为做这个要求使用操作系统工具。如果进程表现为繁忙且消耗了所有可得CPU周期,那么问题很可能是循环线程而不是死锁。

继续阅读

Java SE 6 故障排除指南 – 3、内存泄露

内存泄露故障排除

如果你的应用程序执行的时间越来越长,或如果操作系统执行越来越慢,这可能是内存泄露的指示。换句话说,虚拟内存被分配但在不需要时没有归还。最终应用程序或系统没有可用内存,应用程序非正常终止。

这篇文章提供了一些涉及内存泄露的问题诊断的建议。

3.1 OutOfMemoryError 的含义

一个最常见的内存泄露的指示是 java.lang.OutOfMemoryError 错误。这个错误在Java堆或堆的特定区域没有足够空间用于分配对象时抛出。垃圾收集器不能创造更多可用空间来容纳一个新的对象,堆也不能扩展。

当 java.lang.OutOfMemoryError 错误抛出时,栈轨迹也会被打印。

java.lang.OutOfMemoryError 也可以被本地库代码抛出,当本地分配不能满足时,例如,交换空间很低。

诊断 java.lang.OutOfMemoryError 的一个早期步骤是确定错误的含义。它是否意味着Java堆满了,或意味着本地堆满了?为帮助你回答这个问题,下面的子章节解释了一些可能的错误信息,还有连接到更详细信息的链接:

继续阅读

Java SE 6 故障排除指南 – 1、诊断工具和选项

英文完整文档:http://www.oracle.com/technetwork/java/javase/tsg-vm-149989.pdf

诊断工具和选项

这章介绍了JDK 6 和 Java SE6 里不同的诊断和监视工具。这些工具在第二章详细介绍。

附录 D 列出了这个发布里的可用工具,还有上次发布以来的改变。

注意:本章描述的一些命令行工具是实验性的。例如 jstack, jinfo, jmap 就是实验性的。这些工具可能在将来的JDK发布里改变,或者不包括在将来的发布里。

继续阅读

Java 性能调优指南 – 高性能Java

本文主要基于 Java performance tuning tips or everything you want to know about Java performance in 15 minutes的翻译。

这篇指南主要调优 java 代码而不是 JVM 设置。

一、JDK 类

Java 1.7.0_06 String 内部表示的改变

  • 从 Java 1.7.0_06 开始,String.substring 总是为它创建的新字符串创建一个新的底层 char[] 值。这意味着这个方法现在有线性的复杂度,之前是常量的复杂度。这个改变的好处是字符串需要更少的内存 footprint(比以前少8字节),也是避免 String.substring 导致的内存泄露的一个保证。

Java 里二进制序列化的不同方法

  • 写单个字节到直接字节缓存是非常慢的。对写记录 — 大多数情况是单个字节的字段,你应该避免使用直接字节缓存。
  • 如果你有原始数组字段,总是使用bulk方法来处理它们(一次处理一批的)。ByteBuffer 的bulk 方法的性能接近于Unsafe的这些方法。如果你需要存储/加载任何其它原始数组 – 除字节型的,用 ByteBuffer.to[YouType]Buffer.put(array) 方法调用,字节缓存的位置会自动更新。不要在循环里调用 ByteBuffer.put[YouType] 方法。
  • 总是尝试用带本地字节顺序的直接缓存序列化原始数组。直接字节缓存的性能接近于Unsafe且是可移植的。

Java 集合概览

单线程 并发
Lists ArrayList : 一般基于数组
LinkedList :不要使用
Vector:废弃
CopyOnWriteArrayList:很少更新,经常遍历。
Queues/deques ArrayDeque:一般基于数组。
Stack:废弃。
PriorityQueue:有序的检索操作。
ArrayBlockQueue:有界阻塞queue。
ConcurrentLinkedDeque/ConcurrentLinkedQueue:无界链接queue(CAS)。
DelayQueue:在每个元素上带延迟的queue。
LinkedBlockingDeque/LinkedBlockingQueue:可选的有界链接queue(锁)。
LinkedTransferQueue:may transfer elements w/o storing。
PriorityBlockingQueue:并发的PriorityQueue。
SynchronousQueue:实现了Queue接口的Exchanger。
Maps HashMap:一般的map。
EnumMap:enum作为key的。Hashtable:废弃。
IdentityHashMap:用 == 比较键。
LinkedHashMap:保留了插入顺序。
TreeMap:有序的键。
WeakHashMap:可用于缓存。
ConcurrentHashMap:一般的并发map。
ConcurrentSkipListMap:有序的并发map。
Sets HashSet:一般的set。
EnumSet:enum的集合。
BitSet:比特位或稀疏整数的集合。
LinkedHashSet:保留了插入顺序。
TreeSet:有序集合。
ConcurrentSkipListSet:有序并发集合。
CopyOnWriteArraySet:很少更新,经常遍历。

继续阅读

JUC 源码分析 三 AbstractQueuedSynchronizer 共享模式 与 CountDownLatch

共享模式

共享模式允许一组线程获取同一个许可。为实现共享模式子类需要实现两个方法:

  • tryAcquireShared:返回int类型的值,小于0表示获取失败,等于0表示获取成功但不允许后续更多的获取,大于0表示获取成功且允许更多的后续获取。
  • tryReleaseShared:返回true表示释放许可成功,可以唤醒等待线程;false表示失败,不唤醒等待线程。

共享获取 acquireShared

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
       // 添加到等待队列,不管是共享模式还是独占模式,都共享同一个等待队列。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg); // 尝试获取,返回值表示是否允许获取
                if (r >= 0) {
                   // 获取成功
                   // 把自己设为头结点并传递可以获取的信号
                   // node 把自己设为头结点后,它的后继发现它的前驱是头结点了,就会尝试获取。
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * 尝试通知队列里的下一个结点,如果:
     *       调用者指示或者之前操作记录显示需要传递
     *       (注意:这里对waitStatus使用单一检查,因为PROPAGATE可能被转换到SIGNAL)
     *   并且
     *       下一个结点以共享模式等待或者我们根本就不知道,因为它是空的。
     *
     * 在这些检查有点保守,可能导致不必要的唤醒,但只是在多重竞争acquires/releases时,
     * 因此,大多数都是现在或不久就需要通知的。
     */
    if (propagate > 0 || h == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

private void setHead(Node node) {
    head = node;
    node.thread = null; // for GC
    node.prev = null;
}

继续阅读