本文目录一览:
weblogichost头攻击配置
1. HTTP Host头攻击
从HTTP / 1.1开始,HTTP Host标头是必需的请求标头。它指定客户端要访问的域名。例如,当用户访问时,其浏览器将组成一个包含Host标头的请求,如下所示:
GET /web-security HTTP/1.1
Host: example.net
HTTP Host头的目的是帮助识别客户端要与之通信的后端组件。如果请求不包含Host头或者格式不正确,则在将传入请求的应用程序时可能会导致问题。
从历史上看,这种漏洞并不存在太大问题,因为每个IP地址只会被用于单个域的内容。如今,很大程度上是由于同一个IP上存在多个Web应用程序(不同端口,不同域名解析等),通常可以在同一IP地址访问多个网站和应用程序。这种方法的普及也部分是由于IPv4地址耗尽所致。
当可以通过同一IP地址访问多个应用程序时,最常见的原因是以下情况之一:
虚拟主机
单个Web服务器托管多个网站或应用程序。这可能是具有单个所有者的多个网站,但是也可能是不同所有者的网站托管在同一个共享平台上。它们都与服务器共享一个公共IP地址。
通过代理路由流量
网站托管在不同的后端服务器上,但是客户端和服务器之间的所有流量都通过代理系统进行路由。这可能是一个简单的负载平衡设备或某种反向代理服务器。在客户通过内容分发网络(CDN)访问网站的情况下,这种设置尤其普遍。
在上面两种种情况下,即使网站托管在单独的后端服务器上,它们的所有域名也都解析为中间组件的单个IP地址。这带来了与虚拟主机相同的问题,因为反向代理或负载平衡需要知道每个请求到的哪个后端上。
HTTP Host头的作用就在于,指定请求应该发送到那个应用程序的后端服务器上。打个比方,一封信需要送到居住在公寓楼中的某人手中,整个公寓有许多房间,每个房间都可以接受信件,通过指定房间号和收件人(也就是HTTP Host头)来将信封送到指定的人手中。
3. 什么是HTTP Host头攻击
一些网站以不安全的方式处理Host头的值。如果服务器直接信任Host头,未校验它的合法性,则攻击者可能能够使用此可控变量来注入Host,以操纵服务器端的行为。
现成的Web应用程序通常不知道将它们部署在哪个域上,除非在安装过程中在配置文件中手动指定了该域。例如,当他们需要知道当前域以生成电子邮件中包含的绝对URL时,他们可能依赖于Host头中的值:
a href="['HOST']/support"联系支持/a
Host头值还可以用于不同网站系统之间的各种交互。
由于Host头实际上是用户可控制的,因此这种做法可能导致许多问题。如果未校验或者直接使用Host头,则Host头可以与一系列其他漏洞“组合拳”攻击,比如:
缓存投毒
特殊业务功能的逻辑漏洞
基于路由的SSRF
经典服务端漏洞,如SQL注入(当Host被用于SQL语句时)等
4. 如何发掘HTTP Host头攻击
首先要判断服务端是否检测Host头?检测完了是否还使用Host头的值?
通过修改Host的值,如果服务端返回错误信息:
30b04453d11b44a56c903cc387f17296.png
则说明服务端检测了Host的内容。至于有没有使用Host头的值,有以下几种方法去发掘:
修改Host值
简单的来说,可以修改HTTP头中的Host值,如果观察到响应包中含有修改后的值,说明存在漏洞。
但有时候篡改Host头的值会导致无法访问Web应用程序,从而导致“无效主机头”的错误信息,特别是通过CDN访问目标时会发生这种情况。
添加重复的Host头
添加重复的Host头,通常两个Host头之中有一个是有效的,可以理解为一个是确保请求正确地发送到目标服务器上;另一个则是传递payload到后端服务器中。
GET /example HTTP/1.1
Host: vulnerable-website.com
Host: attackd-stuff
使用绝对路径的URL
尽管许多请求通常在请求域上使用相对路径,但是也同时配置了绝对URL的请求。
GET HTTP/1.1
Host: attack-stuff
有时候也可以尝试不同的协议,如HTTP或HTTPS。
添加缩进或换行
当一些站点block带有多个Host头的请求时,可以通过添加缩进字符的HTTP头来绕过:
GET /example HTTP/1.1
Host: attack-stuff
Host: vulnerable-website.com
注入覆盖Host头的字段
与Host头功能相近的字段,如X-Forwarded-Host、X-Forwarded-For等,这些有时候是默认开启的。
GET /example HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: attack-stuff
诸如此类,还有其他的字段:
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
忽略端口仅校验域名
当修改、添加重复Host头被拦截的时候,可以尝试了解Web应用程序是怎样解析Host头的。
比如,一些解析算法会忽略Host头中的端口值,仅仅校验域名。这时候可以将Host修改为如下形式:
GET /example HTTP/1.1
Host: vulnerable-website.com:attack-stuff
保持域名不变,修改端口值为非端口号的其他值(非数字), 将Host头攻击的payload放在端口值处,同样能进行Host头攻击。
5. HTTP Host头攻击漏洞示例
5.1 密码重置中毒
根据HTTP Host头攻击的攻击特点,它被广泛应用于密码重置中毒:攻击者可以操纵网站在重置密码情况下生成的密码重置链接,使其发送攻击者指定的域下,利用此来窃取重置任意用户密码的令牌。
1c2aa7d6f7e77ca7c2bd09feee527087.png
一个重设密码(忘记密码)功能的大致流程如下:
用户输入其用户名或电子邮件地址,然后提交密码重置请求。
该网站检查该用户是否存在,然后生成一个临时的、唯一的、复杂的令牌,该令牌与后端的用户帐户相关联。
该网站向用户发送一封电子邮件,其中包含用于重置其密码的链接。重置令牌的参数包含在相应的URL中:
4. 当用户访问此URL时,网站将检查提供的令牌是否有效,并使用它来确定要重置哪个帐户。如果一切都符合,则可以进入用户重置密码步骤。最后,令牌被销毁。
以上步骤的安全性依赖于:只有目标用户才能访问其电子邮件,从而可以访问其唯一的令牌。
密码重置中毒是窃取此令牌以更改另一个用户密码的一种漏洞。
如果网站重置密码的流程完全依赖用户的可控输入(如HTTP Host头),这可能导致密码重置中毒:
1. 攻击者获取受害者的用户名或者电子邮件,作为提交重置密码的请求,攻击者会拦截请求并修改HTTP Host头为其指定的域,如evil-user.net
2. 受害者会收到一封重置密码的邮件,但由于攻击者修改了Host头,而web程序生成重置链接又完全依赖于Host头,导致生成以下URL:
3. 如果受害者点击了该链接,重置密码的令牌就会发送到攻击者的服务器 evil-user.net 上
4. 当攻击者获取到虫子密码的令牌之后,就会进行相应的构造访问真实重置密码的URL进行密码重置。
5.1.1 密码重置中毒—基础
详细了解了上面的密码重置中毒的流程和原理之后,这里通过HTTP Host头攻击导致的基础的密码重置中毒来演示。
首先输入用户名或者用户的电子邮箱来重置指定用户的密码:
534a1d062ccc3813b6596fdec0bb475b.png
提交之后,会发送一封重置密码的电子邮件到wiener用户的邮箱中(数据包如右图):
7c47a2e653a4fcbbaeae1687f914d093.png
注意重置密码的链接,可能是受Host头的值的影响?
我们来验证一下是否存在HTTP Host头攻击,修改Host头的值为 baidu.com:
8901c053cc8c3e6f0383f5f61b14d549.png
发现请求是可以被后端服务器接收的,所以是存在HTTP Host头攻击的。
这里就输入受害用户carlos进行重置密码,然后抓包将Host头的值改为我们自己的服务器:
ec1f439fbc6013f1b4f71011a2885947.png
然后在我们自己的服务器上就可以通过访问日志看到被窃取的重置密码Token:
c3b81283ee2a440dcf205a1ab344ab32.png
然后根据已知链接规律,构造重置密码的链接:
1a4ba4a58416c68b3f513ba1239f76d9.png
随即进入输入新密码的界面,密码重置中毒成功。
5.1.2 密码重置中毒—注入覆盖Host头的字段
有时候直接修改Host头、添加重复Host头的值以及混淆Host头都不行:
3ca0c53bfef0d9b47a9f788eb484f40f.png
可以尝试使用与Host头功能相同的HTTP字段,如X-Forwarded-Host、X-Forwarded-For等,可以进行Fuzz:
cbb645bac4d5b28e8b91c39b174a0767.png
实际上他能够被 X-Forwarded-Host 字段影响,导致Host头攻击,当同时添加多个字段使请求被拦截时,可以尝试类似排除法、二分法来排查哪个字段有效。
对受害用户carlos进行密码重置投毒:
53fd95a8a051dd84951094b4e3e79502.png
然后构造链接即可:
efb5ab58ec4a711faa6f4dac736e069a.png
5.1.3 重置密码中毒—Dangling Markup技术
首先简单介绍一下 Dangling Markup技术:
Dangling markup技术
Dangling markup技术, 是一种无需脚本即可窃取页面内容的技术,它使用图像等资源(结合CSP运行的策略)将数据发送到攻击者控制的远程位置。当反射型XSS不工作或被内容安全策略(CSP)阻止时,它非常有用。其思想是插入一些未完成状态的部分HTML,例如图像标记的src属性,页面上的其余标记关闭该属性,但同时将两者之间的数据(包含窃取页面的内容)发送到远程服务器。
例如,我们在反射型XSS注入点上注入这样一个img标签:
img src="?
则注入点和下一个双引号的代码将会发送到攻击者的 服务器, 其中被发送的代码或者内容可能包含一些敏感信息, 例如CSRF Token等, 配合反射型XSS以完成CSRF的利用。
关于 Dangling Markup技术 的实战意义可以参考博主之前的文章:绕过CSP之Dangling markup技术
什么时候可以使用 Dangling Markup技术 呢?与我们这篇文章的主题有什么关系呢?
我们直接进入主题,当输入需要重置密码的用户名后,该用户的邮箱内会收到如下邮箱:
67a4c2bc755ebbb44847d23189d30422.png
有一个跳转到登录界面的链接,后面紧接着重置之后的随机密码。
此时考虑一下,该链接是否是从Host头取值而来?只要这个值可控,那么就可以利用Host头攻击实施 Dangling Markup攻击,包含住链接后面紧跟着的密码,再结合Host头攻击将请求指定到攻击者服务器上。一个漫天过海的窃取行为就完成了。
第一步,寻找Host头攻击点:
通过Fuzz,可发现Host头攻击类型为 忽略端口仅校验域名。即服务端在校验Host域的时候,仅校验了域名,忽略了后面的端口号,造成端口值可控(可以是数字或字符):
904608f5a5bb3404d1ef62f6c92481b9.png a59bf77cc52ad88158c7fdc06202e54b.png
通过在Host头的端口中注入payload,依旧可以实现Host头攻击。
第二步,借助可控变量 Host:ip:port 来实施 Dangling Markup技术,从而将后面的密码外带到攻击者服务器上:
注意,需要闭合此处的双引号出去,经过尝试,输入单引号时,服务端会自动转为双引号,故这里通过单引号将双引号闭合,然后添加自定的a href=xxx.attack-domain标签将密码外带:
184e31cb9b0426e30db33340e2e16c91.png
原本的正常HTML是这样的:
ed4e92688d41cd5e90434d73f12aa811.png
通过Dangling Markup技术 在a标签的链接中注入? 符,使得后面的值在双引号闭合之前全部被当做URL参数请求到攻击者服务器上:
b90e561f45ba0a4e1df4f0363069921e.png
这也是 Dangling Markup技术 的精髓所在,该技术的核心点在于:
可控变量后面是否接着需要窃取的关键数据(包括Token、密码等)
在攻击者服务器上可以看到被Host头攻击转发上来的请求,里面成功窃取了受害者重置后的密码:
d1f9d32d9c0a753a3088e81d382ce9e6.png
5.2 Host头攻击+缓存投毒
当存在Host头攻击的web站点不存在密码重置的功能的时候,此时该漏洞就显得没有影响,因为不可能驱使用户去抓包修改Host头,辅助攻击者完成一系列的攻击。
但是,如果目标站点使用Web缓存,则可以通过缓存投毒给其他用户提供带有病毒的缓存响应。此时的Host头攻击漏洞转变为类似XSS存储型类的漏洞。要构造Web缓存投毒攻击:
1. 需要寻找映射到其他用户请求的缓存键;
2. 下一步则是缓存此恶意响应;
3. 然后,此恶意缓存将提供给尝试访问受影响页面的所有用户。
第一步,寻找Host头攻击点:
通过对站点的主页添加重复的Host值,可以达到覆盖的效果,并验证存在Host头攻击:
b09b4eeacd00d364633ccda68d1de500.png
第二步,寻找是否使用了Web缓存?缓存键是什么?
从上图中也可以发现,站点使用了Wen缓存功能,并且配合Host头攻击,可以缓存/resources/js/tracking.js资源文件。
第三步,在攻击者服务器上创建一个同名的 /resources/js/tracking.js资源文件,内容为:
alert(document.cookie);
然后通过Host头注入攻击者服务器域名,可以看到在响应中正确地对应了我们的 /resources/js/tracking.js资源文件:
8458208389ca34f2736aea5e82afc11e.png
发送多次请求,使该请求的响应变为缓存:
200f4558b179f6aa8cd8bda18f763b66.png
当其他用户请求站点主页时,服务端就会提供该恶意缓存给用户,造成缓存投毒。
5.3 Host头攻击绕过访问控制
出于安全考虑,通常网站对某些功能的访问限制为内部用户使用。但是通过Host头攻击一定可能上可以绕过这些限制。
对于一个站点,从发现Host头攻击到利用,下面来展示一个完整的流程:
第一步,访问主页,随意修改Host的值:
9649967593356ece9722484a91980ff3.png
注意,这里的Host的值不会出现响应包中,但是依然可能存在Host头攻击,因为响应依然成功,说明服务端没有对Host头做验证。
第二步,寻找敏感页面,通过 /robots.txt 知道 /admin 为做了访问控制的页面:
d3fd91199b6c914ec44a0b7659f56e1d.png
可以错误信息提示,/admin 页面只允许本地用户访问。
第三步,将Host改为服务端内部地址,从而绕过IP访问控制:
c66736849f5149d01997ea9edbf9f1ce.png
5.4 Host头攻击+SSRF
Host头攻击可能会导致基于路由的SSRF攻击,称为:Host SSRF Attack。
经典的SSRF攻击通常基于XXE或可利用的业务逻辑,将用户可控的URL作为HTTP请求发送;而基于路由的SSRF依赖于云部署的体系结构中,包括负载均衡和反向代理,这些中间件将请求分配发送到对应的后端服务器处理,如果服务端未校验Host头转发的请求,则攻击者可能会将请求发送(重定向)到体系中的任意系统。
这可能需要知道内部系统的IP地址(私有地址),一般可以通过信息收集或者Fuzz来判断有效的私有IP地址(如枚举192.168.1.1/16)。
5.4.1 基础Host头攻击+SSRF
比如,普通方式访问不到 /admin 页面(404):
774952d6bc5c1f9c84078f832d213f06.png
猜测 /admin 存在于内网中,需要内网机器才能访问,但是配合Host头攻击+SSRF可以绕过并访问。
第一步,判断Host是否被使用,可用DNSLog外带
这里我使用Burp自带的 “Burp Collaborator client” 来实现外带:
fe89ddd297619cc7283a92ba551a877e.png
说明服务端是根据Host头的域名来请求资源的。
第二步,基于Host头的SSRF探测内网主机
假如一些敏感的页面(比如管理页面),深处于内网,外网无法访问,但是通过Host头攻击+SSRF可达到绕过访问控制,从而访问内网资产,这里Fuzz内网的IP的C段为192.168.0.0/24,直接利用Intruder枚举:
a450a6e1f580ed2201f1e7a20d8c7395.png 8f19202c9c4fc0a81696f084f2066d1d.png
得到内网IP为192.168.0.240
第三步,访问内网资源
构造 /admin 页面,在Host处换位内网IP:
4522a7e735f469b8221d677c8b6a1966.png
5.4.2 Host头攻击+SSRF—使用绝对路径的URL
有时候服务端会校验Host头的值,如果Host被修改,服务端会拒绝一切修改过后的请求:
591a625e712178e117db63b2d5e217fc.png
普通请求通常在请求域上使用相对路径,但是,服务端也同时可能配置了绝对URL的请求,采用如下形式可绕过对Host的验证:
GET HTTP/1.1
4030f968be73cbd6bf90f8e61dde4d2a.png
接着用 “Burp Collaborator client” 进行外带:
6b68718a8514a4b9803f40b8d227d959.png
外带成功,说明Host头被服务端使用来向指定域名请求资源,直接SSRF爆破内网:
675515a890884884ce25786619575ca2.png
访问内网页面:
4e7c3841b5c7ee2aaa0e6f3bf75e4bad.png
6 HTTP Host头攻击防护
最简单的方法是避免在服务器端代码中完全使用Host头,可以只使用相对URL。
其他方法包括:
6.1 正确配置绝对域名URL
当必须使用绝对域名URL时,应在配置文件中手动指定当前域的URL,并引用配置的值,而不是从HTTP的Host头中获取。这种方法可防止密码重置的缓存投毒。
6.2 白名单校验Host头的域
如果必须使用Host头,需要正确校验它的合法性。这包括允许的域,并使用白名单校验它,以及拒绝或重定向对无法识别的主机请求。这包括但不仅限于单个web应用程序、负载均衡以及反向代理设备上。
6.3 不支持主机头覆盖
确保不适用与Host头功能相近的字段,如X-Forwarded-Host、X-Forwarded-For等,这些有时候是默认开启的。
值得一提的是,不应该将内网使用的Host主机(不出网)与公网的应用程序托管在同一个服务器上,否则攻击者可能会操纵Host头来访问内部域。
XSS攻击和DDOS攻击的区别
XSS攻击:跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。
分布式拒绝服务(DDoS:Distributed Denial of Service)攻击指借助于客户/服务器技术,将多个计算机联合起来作为攻击平台,对一个或多个目标发动DDoS攻击,从而成倍地提高拒绝服务攻击的威力。通常,攻击者使用一个偷窃帐号将DDoS主控程序安装在一个计算机上,在一个设定的时间主控程序将与大量代理程序通讯,代理程序已经被安装在网络上的许多计算机上。代理程序收到指令时就发动攻击。利用客户/服务器技术,主控程序能在几秒钟内激活成百上千次代理程序的运行。
47 jvm性能优化之GC日志参数分析
回收算法
标记清除算法
根据gcRoot对象的引用链,发现如果该对象没有被引用的情况下,
则标记为垃圾,在清除。
优点:算法简单
缺点:容易产生内存碎片
标记整理算法
根据gcRoot对象的引用链,发现如果该对象没有被引用的情况下,
则标记为垃圾
标记整理与标记清除区别在于:避免标记清除算法产生的碎片问题,
清除垃圾过程中,会将可用的对象实现移动,内存空间更加具有连续性。
优点:没有内存的碎片问题
缺点:整理过程中会产生内存地址移动,效率可能偏低。
标记复制算法
根据gcRoot对象的引用链,发现如果该对象没有被引用的情况下,
将正在被引用的对象拷贝到to区中,让后再直接清理整个from区,在交换位置。
优点:不会产生内存碎片
缺点:比较占内存空间
分代算法
分代算法中主要分成三代: 新生代/from或者 to/老年代
默认 新生代(Young)与老年代(Old)的比例的值为 1:2 (该值可以通过参数–XX:NewRatio 来指定)。
默认的 Eden:from:to=8:1:1 (可以通过参数 –XX:SurvivorRatio 来设定)。
新生代:刚创建的对象都存放在新生代中eden区,当eden区空间内存满之后,则根据GC可达分析算法,将幸存的对象拷贝到to区中,并且寿命+1. 如果该对象的寿命15的情况下
则将该对象放入到老年代中。
老年代:Minor GC新生代GC ,回收多次如果该对象还一直被引用的情况下,则放入到老年代中,如果新生代和老年代内存都满的情况下,则会触发FullGC
总结:
1.对象首先分配到eden区(伊田园区) 新生代GC (Minor GC)
2.新生代空间不足时,触发Minor GC,将eden区(伊田园区)存活的对象采用标记复制算法放入到to区中,并且该对象的寿命+1
注意:Minor GC因为标记复制算法,会触发stop the world 暂停其他用户的线程,等待垃圾回收结束之后,其他的用户线程才会继续执行。
3.如果该对象的寿命15的情况下,则将该对象放入到老年代中,对象寿命放入在对象头中。
4.当老年代空间不足的时候,会触发FullGC 采用标记清理/整理算法
5.新生代GC非常频繁,速度比老年代GC要高。
GC相关参数
Stop-The-World
在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿。Stop-The-World对系统性能存在影响,因此垃圾回收的一个原则是尽量减少“Stop-The-World”的时间。
回收算法:
误区:没有引用计数算法
1.标记清除算法
2.标记整理算法
3.标记复制算法
回收算法是针对不同的场景使用
按照分代使用
新生代:标记复制算法
老年代:标记清除/标记整理
为什么新生代使用 标记复制算法?而老年代使用标记清除/标记整理
?
为什么新生代使用 标记复制算法?而老年代使用标记清除/标记整理
?
因为新生代gc非常频繁,所以选择效率比较高的垃圾回收算法。、
标记清除算法:
优点:算法非常简单。
缺点:空间地址不连续
为什么标记清除算法:空间地址不连续 没有整理
标记整理算法:
优点:空间地址保持连续
缺点:速度慢、会产生移动内存地址 在移动过程中其他线程无法访问堆内存。
标记复制算法:
优点:空间地址保持连续、效率比标记整理算法要高。
缺点: 存在两个空间,占用空间。
分为两个区域:from 和to区 相等。
From和to切换
原理:
当我们堆内存触发gc的时候,在from区中将可用对象拷贝到to中,在直接将整个from
区清空,依次循环切换。
垃圾收集器 并行、串行、cms g1
分代算法:
首先,对我们堆内存空间实现分代,核心分为新生代、老年代
新生代:刚创建的对象一般的情况下都是放在新生代中
分为:eden区() from区 to区
刚创建的对象会放在eden区,当我们新生代eden区空间满的
情况下,会将引用的对象拷贝到to区中,如果gc回收15次的时候
发现该对象还一直被使用的情况下,该对象就会晋升为老年代中。
老生代:
存储空间比例:
1.默认的情况下新生代与老年代存储空间比例是1:2
2.新生代中eden区与from/to区
为什么新生代中from/to区 比例是1:1
因为新生代中使用的标记复制算法
老年代在什么的情况下会发生fullgc
1.在新生代和老年代有堆内存空间都快满的时候。
新生代GCMinorGC
老年代fullgc
当我们老年代gc开始回收垃圾的时候,也会触发新生代gc回收。
老年代在什么时候触发:
当我们老年代空间装不下的时候。
依次内推如何演示新生代gc
Java垃圾收集算法 cms、g1收集器
1.标记清除、标记整理、标记复制算法(引用计数法)
1.标记清除算法:
优点:算法实现简单
缺点: 内存空间不连续、空间利用率不高 容易产生碎片化
2.标记整理算法
优点:空间具有连续性 没有产生碎片化的问题 效率比较低
缺点:对象的应用内存地址有可能会发生变化
3.标记复制算法
优点:效率比较高,保证空间连续性的问题
缺点:以空间的方式换时间
分代算法:
分为新生代和老年代
新生代:刚创建的对象都存放到新生代中 ,在新生代中有分成三个区域。
新生代中分为 eden区、from和to区
刚创建的对象存放到eden区,当eden区如果满的时候,幸存的对象晋升为存放到
From或者是to区
新生代触发的GCMinorGC
老年代触发的GCFullGC
那些对象会晋升到老年代中?
1.年限达到当gc多次在回收的时候,如果能够一直引用到一个阈值的情况下。直接晋升到老年代中。
2.直接是大对象
在默认的情况下 新生代与老年代存储空间比例 是为1:2
在默认的情况下,新生代中 eden区域与from或者是to占比是为8:1:1
程序发生内存溢出:存放的对象空间大小大于我们老年代的空间大小。
因为在整合堆内存中,新生代触发gc回收次数一定比老年代多。
为什么在分代算法中 新生代中有from和to区大小一样。
因为新生代中gc触发非常频繁,为了能够更加高效清理垃圾,所以采用标记复制算法。
垃圾收集器在什么开始触发收集垃圾?
当新生代或者是老年代内存满的情况下,开始触发垃圾
垃圾收集器之前频繁推出垃圾收集器
Stop-The-World 问题
Stop-The-World:当我们垃圾收集器在回收垃圾的时候,
当垃圾回收线程在清理垃圾的时候,会暂停我们的所以用户线程。
导致当前的用户线程短暂卡
在生产环境中调优jvm: 不要频繁触发垃圾回收或者是降低用户线程阻塞的时间
所有的收集器:在回收垃圾的时候都会暂停我们用户的线程
怎样避免触发垃圾收集器频繁回收?
1.堆内存空间设置比较大
2.堆内存初始化 与最大值一定保持一致。
3.生产环境中不要去调用System.gc();
垃圾收集器 与垃圾收集算法区别:
垃圾收集器:串行、并行收集、CMS、G1 Java11 ZGC收集器,能够降低对我们用户线程暂停的时间或者用户线程和GC线程同时运行
垃圾收集算法:回收算法:标记清除、标记整理、标记复制、分代算法
Stop-The-World
当我们在触发gc回收的时候,有可能会暂停用户的线程。
JVM参数
如果存放的对象空间大于新生代空间,该对象会直接放到老年代中。
GC核心参数
-Xms100 -Xmx
1、-Xms
初始大小内存,默认为物理内存 1/64,等价于 -XX:InitialHeapSize
2、-Xmx
最大分配内存,默认为物理内存的 1/4,等价于 -XX:MaxHeapSize
3、-Xss
设置单个线程栈的大小,一般默认为 512-1024k,等价于 -XX:ThreadStackSize
4、-Xmn
设置年轻代的大小
整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小
持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
5、-XX:MetaspaceSize
设置元空间大小
元空间的本质和永久代类似,都是对 JVM 规范中的方法区的实现。
元空间与永久代之间最大区别:元空间并不在虚拟机中,而是使用本地内存
因此默认情况下,元空间的大小仅受本地内存限制,元空间默认比较小,我们可以调大一点
6、-XX:+PrintGCDetails
输出详细GC收集日志信息
7、-XX:SurvivorRatio
设置新生代中 eden 和 S0/S1 空间比例,默认 -XX:SurvivorRatio=8,Eden : S0 : S1 = 8 : 1 :
8、-XX:NewRatio
配置年轻代和老年代在堆结构的占比,默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
9、-XX:MaxTenuringThreshold
设置垃圾最大年龄
新生代与老年代参数比例设置
-XX:+PrintGCDetails -verbose:gc -XX:SurvivorRatio=2 -XX:NewRatio=1
GC日志参数分析
[GC (Allocation Failure) [PSYoungGen: 1626K-488K(6144K)] 1626K-688K(19968K), 0.0039387 secs] [Times: user=0.14 sys=0.00, real=0.00 secs]
新生代发生GC ,从堆内存回收前占用:1626k 回收后变为占用488K ,后面表示GC的用时。
[Full GC (Allocation Failure) [PSYoungGen: 488K-0K(6144K)] [ParOldGen: 296K-593K(13824K)] 784K-593K(19968K), [Metaspace: 3099K-3099K(1056768K)], 0.0043821 secs]
当发生老年代GC时,也会触发新生代GC,新生代堆回收前占用488K,回收后占用0K
,ParOldGen 老年代回收前占用784K,回收后占用593K
GC分析大对象
当我们直接存一个大的对象的时候,比新生代占用空间还有大时,会直接晋升为老年代。
6个重要的JVM性能参数
围绕垃圾收集和内存,您可以将600多个参数传递给 JVM 。如果包括其他方面,则JVM参数总数将很容易超过1000+。任何人都无法消化和理解太多的论据。在本文中,重点介绍了七个重要的 JVM 参数,在 Java性能测试 中起着非常重要的作用。
-Xmx 可能是最重要的 JVM 参数。 -Xmx 定义要分配给应用程序的最大堆大小。。您可以这样定义应用程序的堆大小: -Xmx2g 。
堆大小在影响应用性能和所需物理硬件需求。这带来了一个问题,我的应用程序正确的堆大小是多少?我应该为应用程序分配大堆大小还是小堆大小?答案是:取决于需求和预算。
将 -Xms 和 -Xmx 设置为相同值的会提高JVM性能
元空间是将存储 JVM 的元数据定义(例如类定义,方法定义)的区域。默认情况下,可用于存储此元数据信息的内存量是无限的(即受您的容器或计算机的RAM大小的限制)。您需要使用 -XX:MaxMetaspaceSize 参数来指定可用于存储元数据信息的内存量的上限。
-XX:MaxMetaspaceSize=256m
OpenJDK中有7种不同的GC算法:
如果您未明确指定GC算法,那么JVM将选择默认算法。在Java 8之前, Parallel GC 是默认的GC算法。从Java 9开始, G1 GC 是默认的GC算法。
GC算法的选择对于确定应用程序的性能起着至关重要的作用。根据我们的研究,我们正在使用Z GC算法观察到出色的性能结果。如果使用 JVM 11+ ,则可以考虑使用 Z GC 算法(即 -XX:+ UseZGC )。
下表总结了激活每种垃圾收集算法所需传递的JVM参数。
垃圾收集日志包含有关垃圾收集事件,回收的内存,暂停时间段等信息,可以通过传递以下JVM参数来启用垃圾收集日志:
从JDK 1到JDK 8:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}
从JDK 9及更高版本开始:
-Xlog:gc*:file={file-path}
Demo:
通常,GC日志用于调整垃圾回收性能。但是,GC日志包含重要的微观指标。这些指标可用于预测应用程序的可用性和性能特征。在本文中将重点介绍一种这样的标尺:GC吞吐量。GC吞吐量是您的应用程序在处理客户交易中花费的时间与它在处理GC活动中花费的时间之比。假设您的应用程序的GC吞吐量为98%,则意味着应用程序将其98%的时间用于处理客户活动,其余2%用于GC活动。
现在,让我们看一个健康的JVM的堆使用情况图:
您会看到一个完美的锯齿图案。您会注意到,当运行Full GC(红色三角形)时,内存利用率将一直下降到最低。
现在,让我们看一下有问题的JVM的堆使用情况图:
您可以注意到,在图表的右端,即使GC反复运行,内存利用率也没有下降。这是一个典型的内存泄漏迹象,表明该应用程序正在存在某种内存问题。
如果您仔细观察一下该图,您会发现重复的完整GC开始在上午8点左右开始。但是,该应用程序仅在上午8:45左右开始获取OutOfMemoryError。到上午8点,该应用程序的GC吞吐量约为99%。但是,在上午8点之后,GC吞吐量开始下降到60%。因为当重复的GC运行时,该应用程序将不会处理任何客户交易,而只会进行GC活动。
OutOfMemoryError 是一个严重的问题,它将影响您的应用程序的可用性和性能。要诊断 OutOfMemoryError 或任何与内存相关的问题,必须在应用程序开始遇到 OutOfMemoryError 的那一刻或一瞬间捕获堆转储。由于我们不知道何时会抛出 OutOfMemoryError ,因此很难在抛出时左右的正确时间手动捕获堆转储。但是,可以通过传递以下JVM参数来自动化捕获堆转储:
-XX:+ HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath = {HEAP-DUMP-FILE-PATH}
在 -XX:HeapDumpPath 中,需要指定堆转储所在的文件路径。传递这两个JVM参数时,将在抛出 OutOfMemoryError 时自动捕获堆转储并将其写入定义的文件路径。例:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/crashes/my-heap-dump.hprof
一旦捕获了堆转储,就可以使用 HeapHero 和 EclipseMAT 之类的工具来分析堆转储。
每个应用程序将具有数十,数百,数千个线程。每个线程都有自己的堆栈。在每个线程的堆栈中,存储以下信息:
他们每个都消耗内存。如果它们的使用量超出某个限制,则会引发 StackOverflowError 。可以通过传递-Xss参数来增加线程的堆栈大小限制。例:
-Xss256k
如果将此 -Xss 值设置为一个很大的数字,则内存将被阻塞并浪费。假设您将 -Xss 值指定为 2mb ,而只需要 256kb ,那么您将浪费大量的内存。
假设您的应用程序有500个进程,然后 -Xss 值为 2Mb ,则您的线程将消耗 1000Mb 的内存。另一方面,如果您仅将 -Xss 分配为 256kb ,那么您的线程将仅消耗 125Mb 的内存。每个JVM将节省 875Mb 内存。
注意:线程是在堆(即 -Xmx )之外创建的,因此这 1000Mb 将是您已经分配的-Xmx值的补充。
现代应用程序使用多种协议(即SOAP,REST,HTTP,HTTPS,JDBC,RMI)与远程应用程序连接。有时远程应用程序可能需要很长时间才能做出响应,有时它可能根本不响应。
如果没有正确的超时设置,并且远程应用程序的响应速度不够快,则您的应用程序线程/资源将被卡住。远程应用程序无响应可能会影响您的应用程序的可用性。它可以使您的应用程序停止磨削。为了保护应用程序的高可用性,应配置适当的超时设置。
您可以在JVM级别传递这两个强大的超时网络属性,这些属性可以全局适用于所有使用 java.net.URLConnection 的协议处理程序:
sun.net.client.defaultConnectTimeout :指定建立到主机的连接的超时(以毫秒为单位)。例如,对于HTTP连接,它是与HTTP服务器建立连接时的超时。当建立与资源的连接时, sun.net.client.defaultReadTimeout 指定从输入流读取时的超时(以毫秒为单位)。
例如,如果您要将这些属性设置为2秒:
注意,默认情况下,这两个属性的值为-1,这表示未设置超时。
jvm - 常用调优启动参数配置
可以看到堆内存为2G,新生代为768M老年代为1280M,新生代采用ParNew收集器
-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS
线程栈为512k(默认1024k调小可以增加创建线程数,增加并发量)
同时打印 GC 详细信息和打印 GC 发生时间,当发生OOM时,Dump文件到指定路径
栈空间参数设置
-Xss: 设置线程的最大栈空间,栈空间越大,方法的递归深度越大
方法区参数设置(方法区大小的参数设置跟jdk版本相关)
jdk1.6,jdk1.7设置方法区永久代的大小
-XX:PermSize=5M
-XX:MaxPermSize=5M 最大永久代的大小默认是64M
jdk1.8及以上,永久代被移除,取而代之的是元数据区,元数据区是一块堆外的直接内存
如果不指定大小,那么会耗尽所有可用的系统内存
-XX:MaxMetaspaceSize=60M 设置最大元数据的大小
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:设置新生代大小,设置较大的新生代大小会减小老年代大小,新生代的大小一般为堆空间的1/3
-XX:NewSize=n: 设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值(老年代/新生代)。如:为3,表示年轻代与年老代比值为1:3
年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。
-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3组参数都可以影响年轻代的大小,混合使用的情况下,优先级是什么?
1.高优先级:-XX:NewSize/-XX:MaxNewSize
2.中优先级:-Xmn(默认等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
3.低优先级:-XX:NewRatio
推荐使用-Xmn参数,原因是这个参数简洁,相当于一次设定 NewSize/MaxNewSIze而且两者相等,适用于生产环境。
-Xmn 配合 -Xms/-Xmx,即可将堆内存布局完成 -Xmn参数是在JDK 1.4 开始支持
下面两个参数配合使用,当系统发生堆空间不足时,会导出整个堆的信息,且导出到指定的文件中去,后面用MAT工具分析
-XX:HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=d:/a.dump
直接内存配置
-XX:MaxDirectMemorySize
设置直接内存大小,如果不设置,默认值为最大堆空间,即-Xmx指定的大小,当达到指定值时,
会触发垃圾回收,如果回收后也无法释放空间,那么将会抛出OOM
-XX:+UseSerialGC:新生代,老年代都使用串行收集器
-XX:+UseParNewGC:新生代使用ParNew收集器,老年代使用串行收集器Serial
(jdk9,10已经废除,因为ParNew只能和CMS收集器配合使用,而jdk9,10使用的默认收集器是G1)
-XX:+UseParallelGC:新生代使用ParallelGC,老年代使用串行收集器Serial
-XX:+UseParallelGC:新生代使用ParallelGC回收器,老年代使用串行回收器Serial
-XX:+UseParallelOldGC:新生代使用ParallelGC回收器,老年代使用ParallelOldGc回收器
两个重要参数
-XX:MaxGCPasuseMillis:设置最大垃圾回收停顿时间,设置的过小,可能导致垃圾回收频率加大
-XX:GCTimeRatio:设置吞吐量大小,取值范围为0-100,系统回收垃圾的停顿时间花费不超过1/(1+n)%
设置线程数量
-XX:ParallelGCThreads
-XX:+UseConcMarkSweepGC:新生代使用ParNew回收器,老年代使用CMS
CMS默认启动的并发线程数量为(parallelGCTheads+3)/4
设置并发线程数
-XX:ConcGCThreads=n
-XX:ParallelGCThreads=n
设置老年代空间使用率达到多少时执行CMS垃圾回收
-XX:CMSInitiatingOccupancyFraction 默认值为68
碎片整理参数,如果碎片不整理,可能造成没达到阈值就会触发老年代垃圾回收
-XX:+UseCMSCompactAtFullCollection :在CMS垃圾收集完成之后,进行一次内存碎片整理
-XX:+CMSFullGCsBeforeCompaction=n :在n次CMS回收后进行一次内存碎片整理
使用CMS回收方法去的perm区,默认情况下,还需要触发一次FullGC
-XX:+CMSClassUnloadingEnabled
XX:UseG1GC 开启G1垃圾收集器
两个重要的参数
-XX:MaxGcPasuseMillis :指定目标最大停顿时间,如果停顿的时间过小,一次收集的区域数量也会变小
就会增加FullGC的可能
-XX:parallelGCThreads :设置并行回收的GC线程数量
-XX:InitiatingHeapOccupancyPercent :设置整个堆使用率达到多少时,触发并发标记的执行,默认是45
-XX:+PrintGC 在程序运行期间,只要遇到GC,就会打印GC情况
占用大小-gc后大小 GC消耗时间
jdk9,jdk10默认使用G1收集器,所以打印GC参数不同
-Xlog:gc
-XX:+PrintGCDetails 打印GC详细信息(JDK8,9,10建议使用-Xlog:gc*)
-XX:+PrintGCTimeStamps 打印分析GC发生的时间
-XX:+PrintGCApplicationConcurrentTime 打印应用程序的执行时间
-XX:+PrintGCApplicationStoppedTime 打印GC产生停顿的时间
-Xloggc:/usr/local/log/gc.log 让gc日志打印在log文件夹下的gc文件中,因为默认情况下gc日志在控制台输出
栈上分配,逃逸分析(方法内的变量被外部引用)
允许对象直接在栈上进行分配,随线程停止而销毁,这样做可以加快分配速度,减少GC次数
栈空间较小,所以不适合大对象的栈上分配
-XX:+DoEscapeAnalysis 启用逃逸分析
-XX:+EliminateAllocations 开启标量替换(默认打开),允许对象打散分配在栈上
比如对象拥有id和name两个字段,那么这两个字段将会被视为两个独立的变量进行分配。
对象晋升
-MaxTenuringThreshold=n ,当对象经历了多少次GC次数后进入老年代
注意:大对象直接晋升到老年代
-PretenureSizeThreshold=n 这里的单位是字节,新生对象大于这个值的时候,会直接分配到老年代
1、对存活对象标注时间过长:比如重载了Object类的Finalize方法,导致标注Final Reference耗时过长;或者String.intern方法使用不当,导致YGC扫描StringTable时间过长。
2、长周期对象积累过多:比如本地缓存使用不当,积累了太多存活对象;或者锁竞争严重导致线程阻塞,局部变量的生命周期变长
当Eden区空间不足时,就会触发YGC。结合新生代对象的内存分配看下详细过程:
1、新对象会先尝试在栈上分配,如果不行则尝试在TLAB分配,否则再看是否满足大对象条件要在老年代分配,最后才考虑在Eden区申请空间。
2、如果Eden区没有合适的空间,则触发YGC。
3、YGC时,对Eden区和From Survivor区的存活对象进行处理,如果满足动态年龄判断的条件或者To Survivor区空间不够则直接进入老年代,如果老年代空间也不够了,则会发生promotion failed,触发老年代的回收。否则将存活对象复制到To Survivor区。
4、此时Eden区和From Survivor区的剩余对象均为垃圾对象,可直接抹掉回收。
此外,老年代如果采用的是CMS回收器,为了减少CMS Remark阶段的耗时,也有可能会触发一次YGC,这里不作展开。
大多数情况下,对象直接在年轻代中的Eden区进行分配,如果Eden区域没有足够的空间,那么就会触发YGC(Minor GC),YGC处理的区域只有新生代。因为大部分对象在短时间内都是可收回掉的,因此YGC后只有极少数的对象能存活下来,而被移动到S0区(采用的是复制算法)。当触发下一次YGC时,会将Eden区和S0区的存活对象移动到S1区,同时清空Eden区和S0区。当再次触发YGC时,这时候处理的区域就变成了Eden区和S1区(即S0和S1进行角色交换)。每经过一次YGC,存活对象的年龄就会加1。
下面4种情况,对象会进入到老年代中:
当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括新生代和老年代。除此之外,还有以下4种情况也会触发FGC: