Java知识梳理七

一、Java多线程二

1.Java内存模型

       首先,程序计数器 (PC,Program CounterRegister)。在JVM规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行本地方法,则是未指定值(undefined)。
       第二, Java虚拟机栈(Java Virtual MachineStack) 。早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。前面谈程序计数器时,提到了当前方法;同理,在一个时间点,对应的只会有一个活动的栈帧,通常叫作当前帧,方法所在的类叫作当前类。如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,成为新的当前帧,一直到它返回结果或者执行结束。JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈。栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等。
       第三,堆(Heap)。它是Java内存管理的核心区域,用来放置Java对象实例,几乎所有创建的Java对象实例都是被直接分配在堆上。堆被所有的线程共享,在虚拟机启动时,我们指定的“Xmx”之类参数就是用来指定最大堆空间等指标。理所当然,堆也是垃圾收集器重点照顾的区域,所以堆内空间还会被不同的垃圾收集器进行进一步的细分,最有名的就是新生代、老年代的划分。
       第四,方法区 (Method Area)。这也是所有线程共享的一块内存区域,用于存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等。由于早期的Hotspot JVM实现,很多人习惯于将方法区称为永久代(Permanent Generation)。Oracle JDK8中将永久代移除,同时增加了元数据区(Metaspace)。
       第五,运行时常量池(Run-Time Constant Pool)。这是方法区的一部分。如果仔细分析过反编译的类文件结构,你能看到版本号、字段、方法、超类、接口等各种信息,还有一项信息就是常量池。Java的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛。
       第六, 本地方法栈(Native Method Stack)。它和Java虚拟机栈是非常相似的,支持对本地方法的调用,也是每个线程都会创建一个。在Oracle Hotspot JVM中,本地方法栈和Java虚拟机栈是在同一块儿区域,这完全取决于技术实现的决定,并未在规范中强制。
       JMM总结,1 从JVM运行时视角来看,JVM内存可分为JVM栈、本地方法栈、PC计数器、方法区、堆;其中前三区是线程所私有的,后两者则是所有线程共有的;2 从JVM内存功能视角来看,JVM可分为堆内存、非堆内存与其他。其中堆内存对应于上述的堆区;非堆内存对应于上述的JVM栈、本地方法栈、PC计数器、方法区;其他则对应于直接内存;3 从线程运行视角来看,JVM可分为主内存与线程工作内存。Java内存模型规定了所有的变量都存储在主内存中;每个线程的工作内存保存了被该线程使用到的变量,这些变量是主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量;4 从垃圾回收视角来看,JVM中的堆区=新生代+老年代。新生代主要用于存放新创建的对象与存活时长小的对象,新生代=E+S1+S2;老年代则用于存放存活时间长的对象。

2.如何监控和诊断JVM内存
  • 可以使用综合性的图形化工具,如JConsole、VisualVM(注意,从Oracle JDK 9开始,VisualVM已经不再包含在JDK安装包中)等。这些工具具体使用起来相对比较直观,直接连接到Java进程,然后就可以在图形化界面里掌握内存使用情况。以JConsole为例,其内存页面可以显示常见的 堆内存各种堆外部分 使用状态。
  • 也可以使用命令行工具进行运行时查询,如jstat和jmap等工具都提供了一些选项,可以查看堆、方法区等使用数据。
  • 或者,也可以使用jmap等提供的命令,生成堆转储(Heap Dump)文件,然后利用jhat或Eclipse MAT等堆转储分析工具进行详细分析。
  • 如果你使用的是Tomcat、Weblogic等Java EE服务器,这些服务器同样提供了内存管理相关的功能。
  • 另外,从某种程度上来说,GC日志等输出,同样包含着丰富的信息。
    这里有一个相对特殊的部分,就是是堆外内存中的直接内存,前面的工具基本不适用,可以使用JDK自带的Native Memory Tracking(NMT)特性,它会从JVM本地内存分配的角度进行解读。
3.Java常见的垃圾收集器
  • Serial GC,它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的GC实现,无需维护复杂的数据结构,初始化也简单,所以一直是Client模式下JVM的默认选项。 从年代的角度,通常将其老年代实现单独称作Serial Old,它采用了标记-整理(Mark-Compact)算法,区别于新生代的复制算法。
    Serial GC的对应JVM参数是:
    -XX:+UseSerialGC
  • ParNew GC,很明显是个新生代GC实现,它实际是Serial GC的多线程版本,最常见的应用场景是配合老年代的CMS GC工作,下面是对应参数
    -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
  • CMS(Concurrent Mark Sweep) GC,基于标记-清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于Web等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用CMS GC。但是,CMS采用的标记-清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS会占用更多CPU资源,并和用户线程争抢。
  • Parrallel GC,在早期JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,尽管实现要复杂的多,其特点是新生代和老年代GC都是并行进行的,在常见的服务器环境中更加高效。
    开启选项是:
    -XX:+UseParallelGC

另外,Parallel GC引入了开发者友好的配置项,我们可以直接设置暂停时间或吞吐量等目标,JVM会自动进行适应性调整,例如下面参数:

    -XX:MaxGCPauseMillis=value
    -XX:GCTimeRatio=N // GC时间和用户时间比例 = 1 / (N+1)
  • G1 GC这是一种兼顾吞吐量和停顿时间的GC实现,是Oracle JDK 9以后的默认GC选项。G1可以直观的设定停顿时间的目标,相比于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。 G1 GC仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个region。Region之间是复制算法,但整体上实际可看作是标记-整理(Mark-Compact)算法,可以有效地避免内存碎片,尤其是当Java堆非常大的时候,G1的优势更加明显。 G1吞吐量和停顿表现都非常不错,并且仍然在不断地完善,与此同时CMS已经在JDK 9中被标记为废弃(deprecated),所以G1 GC值得你深入掌握。各种垃圾收集器特点总结如下:
           Serial收集器:串行运行;作用于新生代;复制算法;响应速度优先;适用于单CPU环境下的client模式。
           ParNew收集器:并行运行;作用于新生代;复制算法;响应速度优先;多CPU环境Server模式下与CMS配合使用。
           Parallel Scavenge收集器:并行运行;作用于新生代;复制算法;吞吐量优先;适用于后台运算而不需要太多交互的场景。
           Serial Old收集器:串行运行;作用于老年代;标记-整理算法;响应速度优先;单CPU环境下的Client模式。
           Parallel Old收集器:并行运行;作用于老年代;标记-整理算法;吞吐量优先;适用于后台运算而不需要太多交互的场景。
           CMS收集器:并发运行;作用于老年代;标记-清除算法;响应速度优先;适用于互联网或B/S业务。
           G1收集器:并发运行;可作用于新生代或老年代;标记-整理算法+复制算法;响应速度优先;面向服务端应用。
4.GC调优的思路

       对于GC调优来说,首先就需要清楚调优的目标是什么?从性能的角度看,通常关注三个方面,内存占用(footprint)、延时(latency)和吞吐量(throughput),大多数情况下调优会侧重于其中一个或者两个方面的目标,很少有情况可以兼顾三个不同的角度。当然,除了上面通常的三个方面,也可能需要考虑其他GC相关的场景,例如,OOM也可能与不合理的GC相关参数有关;或者,应用启动速度方面的需求,GC也会是个考虑的方面。基本的调优思路可以总结为:

  • 理解应用需求和问题,确定调优目标。假设,我们开发了一个应用服务,但发现偶尔会出现性能抖动,出现较长的服务停顿。评估用户可接受的响应时间和业务量,将目标简化为,希望GC暂停尽量控制在200ms以内,并且保证一定标准的吞吐量。
  • 掌握JVM和GC的状态,定位具体的问题,确定真的有GC调优的必要。具体有很多方法,比如,通过jstat等工具查看GC等相关状态,可以开启GC日志,或者是利用操作系统提供的诊断工具等。例如,通过追踪GC日志,就可以查找是不是GC在特定时间发生了长时间的暂停,进而导致了应用响应不及时。
  • 这里需要思考,选择的GC类型是否符合我们的应用特征,如果是,具体问题表现在哪里,是Minor GC过长,还是Mixed GC等出现异常停顿情况;如果不是,考虑切换到什么类型,如CMS和G1都是更侧重于低延迟的GC选项。
  • 通过分析确定具体调整的参数或者软硬件配置。
  • 验证是否达到调优目标,如果达到目标,即可以考虑结束调优;否则,重复完成分析、调整、验证这个过程。
5.happens-before原则

       程序顺序原则——一个线程内保证语义的串行性;
       volatile规则——volatile变量的写先发生于读,这保证了volatile变量的可见性;
       锁规则——解锁必然发生在随后的加锁前;
       传递性——a先于b,b先于c,那么a必然先于c;
       线程的start()方法先于它的每一个动作;
       线程的所有操作先于线程的终结;
       线程的中断先于被中断线程的代码;
       对象的构造函数执行、结束先于finalize()方法。

6.Java程序在Docker上运行有哪些问题?
  • 如果未配置合适的JVM堆和元数据区、直接内存等参数,Java就有可能试图使用超过容器限制的内存,最终被容器OOM kill,或者自身发生OOM。
  • 错误判断了可获取的CPU资源,例如,Docker限制了CPU的核数,JVM就可能设置不合适的GC并行线程数等。
  • 从应用打包、发布等角度出发,JDK自身就比较大,生成的镜像就更为臃肿,当我们的镜像非常多的时候,镜像的存储等开销就比较明显了。如果考虑到微服务、Serverless等新的架构和场景,Java自身的大小、内存占用、启动速度,都存在一定局限性,因为Java早期的优化大多是针对长时间运行的大型服务器端应用。
7.Java注入攻击

       注入式(Inject)攻击是一类非常常见的攻击方式,其基本特征是程序允许攻击者将不可信的动态内容注入到程序中,并将其执行,这就可能完全改变最初预计的执行过程,产生恶意效果。下面是几种主要的注入式攻击途径,原则上提供动态执行能力的语言特性,都需要提防发生注入攻击的可能。
       首先,就是最常见的SQL注入攻击。一个典型的场景就是Web系统的用户登录功能,根据用户输入的用户名和密码,我们需要去后端数据库核实信息。假设应用逻辑是,后端程序利用界面输入动态生成类似下面的SQL,然后让JDBC执行。

    Select * from use_info where username = “input_usr_name” and password = “input_pwd”

       但是,如果我输入的input_pwd是类似下面的文本,

    “ or “”=”

       那么,拼接出的SQL字符串就变成了下面的条件,OR的存在导致输入什么名字都是复合条件的。

    Select * from use_info where username = “input_usr_name” and password = “” or “” = “”

       这里只是举个简单的例子,它是利用了期望输入和可能输入之间的偏差。上面例子中,期望用户输入一个数值,但实际输入的则是SQL语句片段。类似场景可以利用注入的不同SQL语句,进行各种不同目的的攻击,甚至还可以加上“;delete
xxx”之类语句,如果数据库权限控制不合理,攻击效果就可能是灾难性的。
       第二,操作系统命令注入。Java语言提供了类似Runtime.exec(…)的API,可以用来执行特定命令,假设我们构建了一个应用,以输入文本作为参数,执行下面的命令:

    ls –la input_file_name

       但是如果用户输入是 “input_file_name;rm –rf/”,这就有可能出现问题了。当然,这只是个举例,Java标准类库本身进行了非常多的改进,所以类似这种编程错误,未必可以真的完成攻击,但其反映的一类场景是真实存在的。
       
第三,XML注入攻击。Java核心类库提供了全面的XML处理、转换等各种API,而XML自身是可以包含动态内容的,例如XPATH,如果使用不当,可能导致访问恶意内容。还有类似LDAP等允许动态内容的协议,都是可能利用特定命令,构造注入式攻击的,包括XSS(Cross-site Scripting)攻击,虽然并不和Java直接相关,但也可能在JSP等动态页面中发生。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,528评论 3 83
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,061评论 1 34
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,946评论 2 31
  • 第二部分 自动内存管理机制 第二章 java内存异常与内存溢出异常 运行数据区域 程序计数器:当前线程所执行的字节...
    小明oh阅读 1,129评论 0 2
  • 作者:一字马胡 转载标志 【2017-11-12】 更新日志 日期更新内容备注 2017-11-12新建文章初版 ...
    beneke阅读 2,184评论 0 7