一次由于Java内存管理机制导致的线程异常阻塞之旅

引言

一转眼已经两年多没写多博客了;一转眼也要快工作两三年了;一转眼我又开始写Java代码了。希望自己可以坚持写写博客,总结总结的习惯!加油。

今天在调试代码的时候,发现两个毫不相关的thread用jstack看竟然其中一个在等待另一个的线程持有的锁,很是奇怪。经过研究,是因为Integer类的实现机制导致的。

一、异常阻塞代码

 1 package xxx;
 2
 3 public class TestDeadLock {
 4     public static void main(String[] argvs) {
 5
 6         new A("threadA").start();
 7         new B("threadB").start();
 8         try {
 9             Thread.sleep(15000);
10         } catch (Exception e) {
11
12         }
13     }
14 }
15 class A extends Thread {
16     public A(String name) {super(name);}
17     private final Object updateLock = 0;
18     @Override
19     public void run() {
20         System.out.println(updateLock.getClass());
21         synchronized (updateLock) {
22             System.out.println(System.currentTimeMillis() + " in A");
23             try {
24                 sleep(100000);
25             } catch (InterruptedException  e) {
26
27             }
28         }
29     }
30 }
31 class B extends Thread {
32     public B(String name) {super(name);}
33     private final Object updateLock = 0;
34     @Override
35     public void run() {
36         System.out.println(updateLock.getClass());
37         synchronized (updateLock) {
38             System.out.println(System.currentTimeMillis() + " in B");
39             try {
40                 sleep(100000);
41             } catch (InterruptedException e) {
42
43             }
44         }
45     }
46 }

上面的代码很简单,建立了两个thread,分别打印一句话,每个线程拥有一个本地的变量updateLock用来进行自己线程内部的同步。问题就出在了这个updateLock上。运行上面的代码会发现这两个线程会互相阻塞,updateLock为线程的内部对象,为什么会互相阻塞呢?问题就出现在了17以及33行的0赋值的问题。0这个值本身是一个基本类型int,updateLock是一个Object的类型,因为实际上会把0强转为Integer类型赋值给updateLock对象,那么Java内部是如何完成这个操作的呢,这个赋值是依赖于Integer的ValueOf方法实现的。

二、Integer的ValueOf实现

1 public static Integer valueOf(int i) {
2     if (i >= IntegerCache.low && i <= IntegerCache.high)
3         return IntegerCache.cache[i + (-IntegerCache.low)];
4     return new Integer(i);
5 }

可以看到valueOf的实现依赖于IntegerCache的实现,从名字就能看出来IntegerCache是一个Interger的缓存,IntegerCache缓存这从-128到127(上限127可以通过jvm配置进行修改)。valueOf的逻辑就是如果存在缓存,返回缓存,不存在就返回一个新的Integer对象。

三、IntegerCache的实现

 1     private static class IntegerCache {
 2         static final int low = -128;
 3         static final int high;
 4         static final Integer cache[];
 5
 6         static {
 7             // high value may be configured by property
 8             int h = 127;
 9             String integerCacheHighPropValue =
10                 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
11             if (integerCacheHighPropValue != null) {
12                 try {
13                     int i = parseInt(integerCacheHighPropValue);
14                     i = Math.max(i, 127);
15                     // Maximum array size is Integer.MAX_VALUE
16                     h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17                 } catch( NumberFormatException nfe) {
18                     // If the property cannot be parsed into an int, ignore it.
19                 }
20             }
21             high = h;
22
23             cache = new Integer[(high - low) + 1];
24             int j = low;
25             for(int k = 0; k < cache.length; k++)
26                 cache[k] = new Integer(j++);
27
28             // range [-128, 127] must be interned (JLS7 5.1.7)
29             assert IntegerCache.high >= 127;
30         }
31
32         private IntegerCache() {}
33     }

IntegerCache的实现比较简单,其实就是内部维护了一个cache数组,提前new好对象,需要的时候直接返回,不再进行对象的创建,因为对象的创建成本是很高的,这样做可以提高效率。这里有一个有意思的地方是jvm需要通过java.lang.Integer.IntegerCache.high配置来修改cache缓存的上限,但是没有提供修改下限的配置,不知道这样做的目的是什么,还有待于考察。

四、深入Object updateLock = 0

因为第一节中的额代码产生了线程互相阻塞的问题,所以可以猜测出两个线程中的updateLock其实是一个相同的对象,即两个线程中的updateLock是两个相同的Integer对象,而相同的Integer对象很大可能是产生自Integer的valueOf方法返回的对象。为了验证我们的猜想,我们将断点断到java.lang.Interger的valueOf函数上进行debug,查看调用栈,如下图所示

上面的图验证了我们的猜想。因此如何解决这个问题也就很简单了。可以将一个new Object()赋值给updateLock就可以了。

总结

如果一个对象的目的是用在synchronized关键字后进行同步处理,那么不允许进行int或其他内部类型的赋值,如第一节中的示例代码,标准的做法应该如下

final Object updateLock = new Object();

原文地址:https://www.cnblogs.com/forspecialgirllearning/p/9174528.html

时间: 2024-11-02 10:03:35

一次由于Java内存管理机制导致的线程异常阻塞之旅的相关文章

你必须了解的java内存管理机制(三)-垃圾标记

本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) 1. 你必须了解的java内存管理机制-运行时数据区 2. 你必须了解的java内存管理机制-内存分配 3. 你必须了解的java内存管理机制-垃圾标记 前言 前面花了两篇文章对JVM的内存管理机制做了较多的介绍,通过第一篇文章先了解了JVM的运行时数据区,然后在第二篇文章中通过一个创建对象的实例介

java内存管理机制(一)-运行时数据区

前言 本打算花一篇文章来聊聊JVM内存管理机制,结果发现越扯越多,于是分了三遍文章(文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8),本文为其中第一篇.from java内存管理机制(一)-运行时数据区  1. java内存管理机制-运行时数据区 2. java内存管理机制-内存分配 3. java内存管理机制-垃圾回收 正文 C++与java之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外的人想进去,墙里的人却想出来…… 与C.C++程序员时刻要关注着内存的分配与释放

java内存管理机制

JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间.释放 :对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2. 

Java 内存管理机制:04 Java 内存分配策略

Java 内存分配策略 Java 内存分配策略 优先在 Eden 区分配 大对象直接进入老年代 长期存活的对象将进入老年代 空间分配担保 新生代和老年代的 GC 操作 新生代 GC 操作:Minor GC 发生的非常频繁,速度较块. 老年代 GC 操作:Full GC / Major GC 经常伴随着至少一次的 Minor GC: 速度一般比 Minor GC 慢上 10 倍以上. 优先在 Eden 区分配 Eden 空间不够将会触发一次 Minor GC: 虚拟机参数: -Xmx:Java 堆

Java虚拟机内存管理机制

自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区域则是依赖用户线程的启动和结束而建立和销毁.根据<Java虚拟机规范 第2版>规定,运行时数据区包括: 1.程序计数器 一块较小的内存空间,不在Ram上,而是直接划分在CPU上的,程序员无法直接操作它.当前线程所执行的字节码的行号指示器,通过改变这个计数器的值来选取下一条需要执行的字节码指令.每条

JVM自动内存管理机制——Java内存区域(下)

一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a)下面是一些简单的使用参数 其中最后一个是一个运行时参数设置的简单实例.一般-XX是系统级别的配置(日志信息,或者是配置使用什么样的垃圾回收器等等),后面跟上+表示启用.不是-XX基本上是对于应用层面的配置信息 下面是一个简单的实例:表示设置初始堆大小为5M,最大堆大小为20M,并将虚拟机的参数设置打印出来,后

2.1 自动内存管理机制--Java内存区域与内存溢出异常

自动内存管理机制 第二章.Java内存区域与内存溢出异常 [虚拟机中内存如何划分,以及哪部分区域.什么样代码和操作会导致内存溢出.各区域内存溢出的原因] 一.运行时数据区域 Java虚拟机所管理的内存包括以下几个运行时数据区域[虚拟机内存模型]: 1.程序计数器: 可以看作是当前线程所执行的字节码的行号指示器.在虚拟机中,字节码解释器工作时就是通过程序计数器的值来选择下一条需要执行的字节码指令.Java虚拟机中多线程是通过线程轮流切换并分配处理机执行时间的方式实现的,在任何一个确定的时刻,一个处

【Java深入研究】3、JVM内存管理机制

转自:http://blog.csdn.net/lengyuhong/article/details/5953544 近期看了看Java内存泄露的一些案例,跟原来的几个哥们讨论了一下,深入研究发现JVM里面还是有不少以前不知道的细节,这里稍微剖析一下.先看一看JVM的内部结构-- 如图所示,JVM主要包括两个子系统和两个组件.两个子系统分别是Class loader子系统和Execution engine(执行引擎) 子系统:两个组件分别是Runtime data area (运行时数据区域)组

JVM自动内存管理机制——Java内存区域

一.JVM运行时数据区域概述 Java相比较于C/C++的一个特点就是,在虚拟机自动内存管理机制的帮助下,我们不需要为每一个操作都写像C/C++一样的delete/free代码,所以也不容易出现内存泄漏和内存溢出的问题.显然,这里的不容易只是相对而言的,如果我们想要降低这种代码隐患的发生,就需要对Java虚拟机怎样使用内存有了解,这样的话就算产生错误,排查起来也会相对容易.下面我们来说一说JVM运行时数据区域 1.程序计数器(PC寄存器): 被看作是当前线程所执行的字节码的行号指示器,字节码解析