Java内存模型之happens-before

Java内存模型之happens-before

在上篇博客(【死磕Java并发】—–深入分析volatile的实现原理)LZ提到过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题。那么我们正确使用同步、锁的情况下,线程A修改了变量a何时对线程B可见? 我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性。 在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。 happens-before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,我们解决在并发环境下两操作之间是否可能存在冲突的所有问题。下面我们就一个简单的例子稍微了解下happens-before ; 1 2 i = 1; //线程A执行 j = i ; //线程B执行 j 是否等于1呢?假定线程A的操作(i = 1)happens-before线程B的操作(j = i),那么可以确定线程B执行后j = 1 一定成立,如果他们不存在happens-before原则,那么j = 1 不一定成立。这就是happens-before原则的威力。 happens-before原则定义如下: 1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。 2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。 下面是happens-before原则规则: 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作; 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作; volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行; 对象终结规则:一个对象的初始化完成先行发生于他的f

java CPU 100% 排查

java CPU 100% 排查

一个应用占用CPU很高,除了确实是计算密集型应用之外,通常原因都是出现了死循环。 (友情提示:本博文章欢迎转载,但请注明出处:hankchen,http://www.blogjava.net/hankchen) 以我们最近出现的一个实际故障为例,介绍怎么定位和解决这类问题。 根据top命令,发现PID为28555的Java进程占用CPU高达200%,出现故障。 通过ps aux | grep PID命令,可以进一步确定是tomcat进程出现了问题。但是,怎么定位到具体线程或者代码呢? 首先显示线程列表: ps -mp pid -o THREAD,tid,time 找到了耗时最高的线程28802,占用CPU时间快两个小时了! 其次将需要的线程ID转换为16进制格式: printf "%x\n" tid 最后打印线程的堆栈信息: jstack pid |grep tid -A 30 找到出现问题的代码了! 现在来分析下具体的代码:ShortSocketIO.readBytes(ShortSocketIO.java:106) ShortSocketIO是应用封装的一个用短连接Socket通信的工具类。readBytes函数的代码如下: public byte[] readBytes(int length) throws IOException { if ((this.socket == null) || (!this.socket.isConnected())) { throw new IOException("++++ attempting to read from closed socket"); } byte[] result = null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); if (this.recIndex >= length) { bos.write(this.recBuf, 0, length); byte[] newBuf = new byte[this.recBufSize]; if (this.recIndex > length) { System.arraycopy(this.recBuf, length, newBuf, 0, this.recIndex - length); } this.recBuf = newBuf; this.recIndex -= length; } else { int totalread = length; if (this.recIndex > 0) { totalread -= this.recIndex; bos.write(this.recBuf, 0, this.recIndex); this.recBuf = new byte[this.recBufSize]; this.recIndex = 0; } int readCount = 0; while (totalread > 0) { if ((readCount = this.in.read(this.recBuf)) > 0) { if (totalread > readCount) { bos.write(this.recBuf, 0, readCount); this.recBuf = new byte[this.recBufSize]; this.recIndex = 0; } else { bos.write(this.recBuf, 0, totalread); byte[] newBuf = new byte[this.recBufSize]; System.arraycopy(this.recBuf, totalread, newBuf, 0, readCount - totalread); this.recBuf = newBuf; this.recIndex = (readCount - totalread); } totalread -= readCount; } } } 问题就出在标红

top linux下的任务管理器

top linux下的任务管理器

top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止.比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最“敏感”的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序;而且该命令的很多特性都可以通过交互式命令或者在个人定制文件中进行设定。 $top top - 09:14:56 up 264 days, 20:56, 1 user, load average: 0.02, 0.04, 0.00 Tasks: 87 total, 1 running, 86 sleeping, 0 stopped, 0 zombie Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.2%st Mem: 377672k total, 322332k used, 55340k free, 32592k buffers Swap: 397308k total, 67192k used, 330116k free, 71900k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 2856 656 388 S 0.0 0.2 0:49.40 init 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd 3 root 20 0 0 0 0 S 0.0 0.0 7:15.20 ksoftirqd/0 4 root RT 0 0 0 0 S 0.0 0.0 0:00.00 migration/0 第一行 09:14:56 : 系统当前时间 264 days, 20:56 : 系统开机到现在经过了多少时间 1 users : 当前2用户在线 load average: 0.02, 0.04, 0.00: 系统1分钟、5分钟、15分钟的CPU负载信息 第二行 Tasks:任务; 87 total:很好理解,就是当前有87个任务,也就是87个进程。 1 running:1个进程正在运行 86 sleeping:86个进程睡眠 0 stopped:停止的进程数 0 zombie:僵死的进程数 第三行 Cpu(s):表示这一行显示CPU总体信息 0.0%us:用户态进程占用CPU时间百分比,不包含renice值为负的任务占用的CPU的时间。 0.7%sy:内核占用CPU时间百分比 0.0%ni:改变过优先级的进程占用CPU的百分比 99.3%id:空闲CPU时间百分比 0.0%wa:等待I/O的CPU时间百分比 0.0%hi:CPU硬中断时间百分比 0.0%si:CPU软中断时间百分比 注:这里显示数据是所有cpu的平均值,如果想看每一个cpu的处理情况,按1即可;折叠,再次按1; 第四行 Men:内存的意思 8175320kk total:物理内存总量 8058868k use

通俗理解SSL/TLS协议区别与原理

通俗理解SSL/TLS协议区别与原理

区别(历史) TLS 1.0又被叫做SSL 3.1。 换算关系: 1 2 3 TLS 1.0 = SSL 3.1 TLS 1.1 = SSL 3.2 TLS 1.2 = SSL 3.3 综上,简单说,它们的区别只是版本更迭而已。 展开说的话,历史: 1994年,NetScape公司设计了SSL协议(Secure Sockets Layer)的1.0版,但是未发布。 1995年,NetScape公司发布SSL 2.0版,很快发现有严重漏洞。 1996年,SSL 3.0版问世,得到大规模应用。 1999年,互联网标准化组织ISOC接替NetScape公司,发布了SSL的升级版TLS 1.0版。 2006年和2008年,TLS 1.1版和TLS 1.2版发布。(TLS1.2已经获得主流浏览器支持) 2008年8月,TLS 1.3版发布,性能好。移除了很多东西,速度快了很多,少了一次握手。 综上,可以先用TLS 1.2。 原理 和以前莉姐课上说的PGP协议差不多,都是三板斧:摘要、非对称加密、对称加密。 要解决的问题 1. 窃听。解决方案:加密 2. 篡改。解决方案:摘要 (解决数据完整性) 3. 冒充。解决方案:数字签名 (解决中间人攻击) 架构 ISO七层协议: TCP : 传输层 TLS : 会话层 表示层略 HTTP: 应用层 (TLS+HTTP=>HTTPS) 所以是先进行3次握手建立TCP,然后4次握手建立TLS,然后进行HTTP数据传输。 如果在TCP层抓包的话,里头是TLS加密过的数据。(中间人无法知道内容) 如果在HTTP层(应用层)收取数据的话,是已经解密过的明文。(但是中间人不太可能在应用层,除非已经嵌入到业务层代码了。) 简化版加密通信 假设用三个算法做一下加密通信,可以怎么实现呢? 定义如下: RSA: 一种非对称加密算法 AES: 一种对称加密算法 SHA1: 一种摘要算法 方案1:AES(K,data) 假如客户端是C,服务端是S,C和S要传输的数据data。 直接传明文肯定是不行,可以加密一下。用一个密钥K,加密成AES(K,data)。 这里为啥用AES呢,不用RSA呢,因为非对称加密(RSA)太慢了。 问题: 虽然别人不知道你俩传输了啥,但是可能悄悄得在中间篡改了数据,双方察觉不到。 解决方案: 加上摘要算法。 方案2:AES(K,data+SHA1(data)) 可以在数据后面加上数据的摘要,然后再加密,这样中间人一旦乱改东西马上就会被检测出来,类似于校验位。 问题: 上述方案都有一个前提,就是通信双方使用同一个K进行加解密。 那么一开始的时候怎么约定、协商这个密钥K呢? 解决方案: 先用RSA协商出一个对称密钥K。 协商密钥 最安全的方法当然是线下见面,约定一个密钥K。但是这个通信效率太低了,并发也不高。 为了避免中间人攻击,这个问题的关键点在于确认对方的身份。

JVM相关 - SafePoint 与 Stop The World 全解(基于OpenJDK 11版本)

JVM相关 - SafePoint 与 Stop The World 全解(基于OpenJDK 11版本)

在分析线上 JVM 性能问题的时候,我们可能会碰到下面这些场景: 1.GC 本身没有花多长时间,但是 JVM 暂停了很久,例如下面: 2.JVM 没有 GC,但是程序暂停了很久,而且这种情况时不时就出现。 这些问题一般和 SafePoint 还有 Stop the World 有关。 什么是 SafePoint?什么是 Stop the world?他们之间有何关系? 我们先来设想下如下场景: 当需要 GC 时,需要知道哪些对象还被使用,或者已经不被使用可以回收了,这样就需要每个线程的对象使用情况。 对于偏向锁(Biased Lock),在高并发时想要解除偏置,需要线程状态还有获取锁的线程的精确信息。 对方法进行即时编译优化(OSR栈上替换),或者反优化(bailout栈上反优化),这需要线程究竟运行到方法的哪里的信息。 对于这些操作,都需要线程的各种信息,例如寄存器中到底有啥,堆使用信息以及栈方法代码信息等等等等,并且做这些操作的时候,线程需要暂停,等到这些操作完成,否则会有并发问题。这就需要 SafePoint。 Safepoint 可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,线程可以暂停。在 SafePoint 保存了其他位置没有的一些当前线程的运行信息,供其他线程读取。这些信息包括:线程上下文的任何信息,例如对象或者非对象的内部指针等等。我们一般这么理解 SafePoint,就是线程只有运行到了 SafePoint 的位置,他的一切状态信息,才是确定的,也只有这个时候,才知道这个线程用了哪些内存,没有用哪些;并且,只有线程处于 SafePoint 位置,这时候对 JVM 的堆栈信息进行修改,例如回收某一部分不用的内存,线程才会感知到,之后继续运行,每个线程都有一份自己的内存使用快照,这时候其他线程对于内存使用的修改,线程就不知道了,只有再进行到 SafePoint 的时候,才会感知。 所以,GC 一定需要所有线程同时进入 SafePoint,并停留在那里,等待 GC 处理完内存,再让所有线程继续执。像这种**所有线程进入 SafePoint **等待的情况,就是 Stop the world(此时,突然想起承太郎的:食堂泼辣酱,the world!!!)。 为什么需要 SafePoint 以及 Stop The World? 在 SafePoint 位置保存了线程上下文中的任何东西,包括对象,指向对象或非对象的内部指针,在线程处于 SafePoint 的时候,对这些信息进行修改,线程才能感知到。所以,只有线程处于 SafePoint 的时候,才能针对线程使用的内存进行 GC,以及改变正在执行的代码,例如 OSR (On Stack Replacement,栈

联系我们

联系电话

4000-640-466

联系邮箱

service@f-li.cn

办公地址

上海黄浦区外滩源1号

谢谢,您的信息已成功发送。
请填写信息。