首页 文章 精选 留言 我的

精选列表

搜索[java],共10000篇文章
优秀的个人博客,低调大师

性能监控之JMX监控docker中的java应用

2 --> 今天在配置docker和JMX监控的时候,看到有一个细节和非容器环境中的JMX配置不太一样。所以在这里写一下,以备其他人查阅。 一般情况下,我们配置JMX只要写上下面这些参数就可以了。 以下是无密码监控时的JMX配置参数(有密码监控的配置和常规监控无异)。 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9998 -Djava.rmi.server.hostname=<serverip> -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false 但是在docker容器中这样配置的时候,会出现这个错误。 这里就要说明一下逻辑了。为什么会这样呢? 先看docker环境的网络结构。 容器使用默认的网络模型,就是bridge模式。在这种模式下是docker run时做的DNAT规则,实现数据转发的能力。所以我们看到的网络信息是这样的: docker中的网卡信息: [root@f627e4cb0dbc /]# ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.18.0.3 netmask 255.255.0.0 broadcast 0.0.0.0 inet6 fe80::42:acff:fe12:3 prefixlen 64 scopeid 0x20<link> ether 02:42:ac:12:00:03 txqueuelen 0 (Ethernet) RX packets 366 bytes 350743 (342.5 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 358 bytes 32370 (31.6 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 docker中的路由信息: [root@a2a7679f8642 /]# netstat -r Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface default gateway 0.0.0.0 UG 0 0 0 eth0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0 [root@a2a7679f8642 /]# 宿主机上的对应网卡信息: docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.18.0.1 netmask 255.255.0.0 broadcast 0.0.0.0 ether 02:42:44:5a:12:8f txqueuelen 0 (Ethernet) RX packets 6691477 bytes 4981306199 (4.6 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 6751310 bytes 3508684363 (3.2 GiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 宿主机上的路由信息: [root@7dgroup ~]# netstat -r Kernel IP routing table Destination Gateway Genmask Flags MSS Window irtt Iface default gateway 0.0.0.0 UG 0 0 0 eth0 link-local 0.0.0.0 255.255.0.0 U 0 0 0 eth0 172.17.208.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.16.0 0.0.0.0 255.255.240.0 U 0 0 0 br-676bae33ff92 所以宿主机和容器是可以直接通信的,即便端口没有映射出来。如下所示: [root@7dgroup ~]# telnet 172.18.0.3 8080 Trying 172.18.0.3... Connected to 172.18.0.3. Escape character is '^]'. 另外,因为是桥接的,宿主机上还有类似veth0b5a080的虚拟网卡设备信息,如: veth0b5a080: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 ether 42:c3:45:be:88:1a txqueuelen 0 (Ethernet) RX packets 2715512 bytes 2462280742 (2.2 GiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2380143 bytes 2437360499 (2.2 GiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 这就是虚拟网卡对veth pair,docker容器里一个,宿主机一个。 在这种模式下,有几个容器,主机上就会有几个veth开头的虚拟网卡设备。 但是如果不是宿主机访问的话,肯定是不通的。如下图所示: 当我们用监控机 访问的时候,会是这样的结果。 Zees-Air-2:~ Zee$ telnet <serverip> 8080 Trying <serverip>... telnet: connect to address <serverip>: Connection refused telnet: Unable to connect to remote host Zees-Air-2:~ Zee$ 因为8080是容器开的端口,并不是宿主机开的端口,其他机器是访问不了的。 这就是为什么要把端口映射出来给远程访问的原因,映射之后的端口,就会有NAT规则来保证数据包可达。 查看下NAT规则,就知道。 [root@7dgroup ~]# iptables -t nat -vnL Chain PREROUTING (policy ACCEPT 171 packets, 9832 bytes) pkts bytes target prot opt in out source destination 553K 33M DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 171 packets, 9832 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 2586 packets, 156K bytes) pkts bytes target prot opt in out source destination 205K 12M DOCKER all -- * * 0.0.0.0/0 !60.205.104.0/22 ADDRTYPE match dst-type LOCAL 0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 2602 packets, 157K bytes) pkts bytes target prot opt in out source destination 265K 16M MASQUERADE all -- * !docker0 172.18.0.0/16 0.0.0.0/0 0 0 MASQUERADE all -- * !br-676bae33ff92 192.168.16.0/20 0.0.0.0/0 0 0 MASQUERADE tcp -- * * 192.168.0.4 192.168.0.4 tcp dpt:7001 0 0 MASQUERADE tcp -- * * 192.168.0.4 192.168.0.4 tcp dpt:4001 0 0 MASQUERADE tcp -- * * 192.168.0.5 192.168.0.5 tcp dpt:2375 0 0 MASQUERADE tcp -- * * 192.168.0.8 192.168.0.8 tcp dpt:8080 0 0 MASQUERADE tcp -- * * 172.18.0.4 172.18.0.4 tcp dpt:3306 0 0 MASQUERADE tcp -- * * 172.18.0.5 172.18.0.5 tcp dpt:6379 0 0 MASQUERADE tcp -- * * 172.18.0.2 172.18.0.2 tcp dpt:80 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:9997 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:9996 0 0 MASQUERADE tcp -- * * 172.18.0.6 172.18.0.6 tcp dpt:8080 0 0 MASQUERADE tcp -- * * 172.18.0.3 172.18.0.3 tcp dpt:9995 0 0 MASQUERADE tcp -- * * 172.18.0.3 172.18.0.3 tcp dpt:8080 Chain DOCKER (3 references) pkts bytes target prot opt in out source destination 159K 9544K RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0 0 0 RETURN all -- br-676bae33ff92 * 0.0.0.0/0 0.0.0.0/0 1 40 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3307 to:172.18.0.4:3306 28 1486 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:6379 to:172.18.0.5:6379 2289 137K DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:91 to:172.18.0.2:80 3 192 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9997 to:172.18.0.6:9997 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9996 to:172.18.0.6:9996 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9002 to:172.18.0.6:8080 12 768 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9995 to:172.18.0.3:9995 4 256 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9004 to:172.18.0.3:8080 [root@7dgroup ~]# 我们看到了宿主机的91端口的数据会传给172.18.0.2的80端口。宿主机的3307会传给172.18.0.4的3306端口。 啰啰嗦嗦说到这里,那和JMX有啥关系。苦就苦在,JMX是这样的。 在注册时使用的是参数jmxremote.port,然后返回一个新的端口jmxremote.rmi.port。 在调用服务时使用是参数jmxremote.rmi.port。 前面提到了,因为docker在bridge模式下端口是要用-p显式指定的,不然没NAT规则,数据包不可达。所以在这种情况下,只能把jmxremote.rmi.port也暴露出去。所以必须显式指定。因为不指定的话,这个端口会随机开。随机开的端口又没NAT规则,所以是不通的了。 所以,这种情况只能指定jmxremote.rmi.port为固定值,并暴露出去。 配置如下: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9995 -Djava.rmi.server.hostname=<serverip> -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.rmi.port=9995 像上面的设置就是两个都是9997,这样是允许的,这种情况下注册和调用的端口就合并了。 再启动docker容器的时候,就需要这样了。 docker run -d -p 9003:8080 -p 9995:9995 --name 7dgroup-tomcat5 -e CATALINA_OPTS="-Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port=9995 \ -Djava.rmi.server.hostname=<serverip> \ -Dcom.sun.management.jmxremote.ssl=false \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.rmi.port=9995" c375edce8dfd 然后就可以连接上JMX的工具了。 在有防火墙和其他的设备的网络环境中,也有可能出同样的问题。明白了JMX的注册调用逻辑之后,就可以解决各种类似的问题了。 网络链路是做性能分析的人必须想明白的技术点,所以前面说了那么多内容。 这里对于JMX工具的选择啰嗦两句。有人喜欢花哨的,有人喜欢简单的,有人喜欢黑窗口的。我觉得工具选择的时候,要看适用情况,在性能分析的时候,一定要选择合适的工具,而不是选择体现技术高超的工具。 最后留个作业: 如果docker run中如果指定-p 19995:9995,也就是换个端口暴露出去,其他配置都不变。JMX工具还能连得上吗? 如果jmxremote.rmi.port和jmxremote.port不合并,并且同时把两个端口都暴露出去,其他配置都不变。JMX工具还能连得上吗? 有兴趣的可以自己尝试下哦。

优秀的个人博客,低调大师

Java容器 | 基于源码分析List集合体系

List集合体系应该是日常开发中最常用的API,而且通常是作为面试压轴问题(JVM、集合、并发),集合这块代码的整体设计也是融合很多编程思想,对于程序员来说具有很高的参考和借鉴价值。 一、容器之List集合 List集合体系应该是日常开发中最常用的API,而且通常是作为面试压轴问题(JVM、集合、并发),集合这块代码的整体设计也是融合很多编程思想,对于程序员来说具有很高的参考和借鉴价值。 基本要点 基础:元素增查删、容器信息; 进阶:存储结构、容量管理; API体系 ArrayList:维护数组实现,查询快; Vector:维护数组实现,线程安全; LinkedList:维护链表实现,增删快; 核心特性包括:初始化与加载,元素管理,自动扩容,数组和链表两种数据结构。Vector底层基于ArrayList实现的线程安全操作,而ArrayList与LinkedList属于非线程安全操作,自然效率相比Vector会高,这个是通过源码阅读可以发现的特点。 二、ArrayList详解 1、数组特点 ArrayList就是集合体系中List接口的具体实现类,底层维护Object数组来进行装载和管理数据: privatestaticfinalObject[]EMPTY_ELEMENTDATA={}; 提到数组结构,潜意识的就是基于元素对应的索引查询,所以速度快,如果删除元素,可能会导致大量元素移动,所以相对于LinkedList效率较低。 数组存储的机制: 数组属于是紧凑连续型存储,通过下标索引可以随机访问并快速找到对应元素,因为有预先开辟内存空间的机制,所以相对节约存储空间,如果数组一旦需要扩容,则重新开辟一块更大的内存空间,再把数据全部复制过去,效率会非常的低下。 2、构造方法 这里主要看两个构造方法: 无参构造器:初始化ArrayList,声明一个空数组。 publicArrayList(){ this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } 有参构造器:传入容量参数大于0,则设置数组的长度。 publicArrayList(intinitialCapacity){ if(initialCapacity>0){ this.elementData=newObject[initialCapacity]; }elseif(initialCapacity==0){ this.elementData=EMPTY_ELEMENTDATA; }else{ thrownewIllegalArgumentException("IllegalCapacity:"+initialCapacity); } } 如果没通过构造方法指定数组长度,则采用默认数组长度,在添加元素的操作中会设置数组容量。 privatestaticfinalintDEFAULT_CAPACITY=10; 3、装载数据 通过上面的分析,可以知道数组是有容量限制的,但是ArrayList却可以一直装载元素,当然也是有边界值的,只是通常不会装载那么多元素: privatestaticfinalintMAX_ARRAY_SIZE=Integer.MAX_VALUE-8; 超过这个限制会抛出内存溢出的错误。 装载元素:会判断容量是否足够; publicbooleanadd(Ee){ ensureCapacityInternal(size+1); elementData[size++]=e; returntrue; } 当容量不够时,会进行扩容操作,这里贴量化关键源码: privatevoidgrow(intminCapacity){ intoldCapacity=elementData.length; intnewCapacity=oldCapacity+(oldCapacity>>1); elementData=Arrays.copyOf(elementData,newCapacity); } 机制:计算新容量(newCapacity=15),拷贝一个新数组,设置容量为newCapacity。 指定位置添加:这个方法很少使用到,同样看两行关键代码; publicvoidadd(intindex,Eelement){ ensureCapacityInternal(size+1); System.arraycopy(elementData,index,elementData,index+1,size-index); elementData[index]=element; size++; } 机制:判断数组容量,然后就是很直接的一个数组拷贝操作,简单来个图解: 如上图,假设在index=1位置放入元素E,按照如下过程运行: 获取数组index到结束位置的长度; 拷贝到index+1的位置; 原来index位置,放入element元素; 这个过程就好比排队,如果在首位插入一位,即后面的全部后退一位,效率自然低下,当然这里也并不是绝对的,如果移动的数组长度够小,或者一直在末尾添加,效率的影响自然就降低很多。 4、移除数据 上面看的数据装载,那与之相反的逻辑再看一下,依旧看几行关键源码: publicEremove(intindex){ EoldValue=elementData(index); intnumMoved=size-index-1; if(numMoved>0){ System.arraycopy(elementData,index+1,elementData,index,numMoved); } elementData[--size]=null; returnoldValue; } 机制:从逻辑上看,与添加元素的机制差不多,即把添加位置之后的元素拷贝到index开始的位置,这个逻辑在排队中好比前面离开一位,后面的队列整体都前进一位。 其效率问题也是一样,如果移除集合的首位元素,后面所有元素都要移动,移除元素的位置越靠后,效率影响就相对降低。 5、容量与数量 在集合的源码中,有两个关键字段需要明确一下: capacity:集合的容量,装载能力; size:容器中装载元素的个数; 通常容器大小获取的是size,即装载元素个数,不断装载元素触发扩容机制,capacity容量才会改变。 三、LinkedList详解 1、链表结构特点 链表结构存储在物理单元上非连续、非顺序,节点元素间的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点组成,节点可以在运行时动态生成,节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 特点描述 物理存储上是无序且不连续的; 链表是由多个节点以链式结构组成; 逻辑层面上看形成一个有序的链路结构; 首节点没有指向上个节点的地址; 尾节点没有指向下个节点的地址; 链表结构解决数组存储需要预先知道元素个数的缺陷,可以充分利用内存空间,实现灵活的内存动态管理。 2、LinkedList结构 LinkedList底层数据存储结构正是基于链表实现,首先看下节点的描述: privatestaticclassNode{ Eitem; Nodenext; Nodeprev; Node(Nodeprev,Eelement,Nodenext){ this.item=element; this.next=next; this.prev=prev; } } 在LinkedList中定义静态类Node描述节点的结构:元素、前后指针。在LinkedList类中定义三个核心变量: transientintsize=0; transientNodefirst; transientNodelast; 即大小,首位节点,关于这个三个变量的描述在源码的注释上已经写的非常清楚了: 首节点上个节点为null,尾节点下个节点为null,并且item不为null。 3、元素管理 LinkedList一大特点即元素增加和删除的效率高,根据链表的结构特点来看源码。 添加元素 通过源码可以看到,添加元素时实际调用的是该方法,把新添加的元素放在原链表最后一位: voidlinkLast(Ee){ finalNodel=last; finalNodenewNode=newNode<>(l,e,null); last=newNode; if(l==null) first=newNode; else l.next=newNode; size++; modCount++; } 结合Node类的构造方法,实际的操作如下图: 核心的逻辑即:新的尾节点和旧的尾节点构建指针关系,并处理首位节点变量。 删除元素 删除元素可以根据元素对象或者元素index删除,最后核心都是执行unlink方法: Eunlink(Nodex){ finalEelement=x.item; finalNodenext=x.next; finalNodeprev=x.prev; if(prev==null){ first=next; }else{ prev.next=next; x.prev=null; } if(next==null){ last=prev; }else{ next.prev=prev; x.next=null; } x.item=null; size--; modCount++; returnelement; } 与添加元素核心逻辑相似,也是一个重新构建节点指针的过程: 两个if判断是否删除的是首位节点; 删除节点的上个节点的next指向删除节点的next节点; 删除节点的下个节点的prev指向删除节点的prev节点; 通过增删方法的源码分析,可以看到LinkedList对元素的增删并不会涉及大规模的元素移动,影响的节点非常少,效率自然相对ArrayList高很多。 4、查询方法 基于链表结构存储而非数组,对元素查询的效率会有很大影响,先看源码: publicEget(intindex){ checkElementIndex(index); returnnode(index).item; } Nodenode(intindex){ if(index<(size>>1)){ Nodex=first; for(inti=0;i<index;i++) x=x.next; returnx; }else{ Nodex=last; for(inti=size-1;i>index;i--) x=x.prev; returnx; } } 这段源码结合LinkedList结构看,真的是极具策略性: 首先是对index的合法性校验; 然后判断index在链表的上半段还是下半段; 如果在链表上半段:从first节点顺序遍历; 如果在链表下半段:从last节点倒序遍历; 通过上面的源码可以看到,查询LinkedList中靠中间位置的元素,需要执行的遍历的次数越多,效率也就越低,所以LinkedList相对更适合查询首位元素。

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册