首页 文章 精选 留言 我的

精选列表

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

三步轻松理解Kerberos协议

Kerberos是一种身份验证协议,它作为一种可信任的第三方认证服务,通过使用对称加密技术为客户端/服务器应用程序提供强身份验证。在域环境下,AD域使用Kerberos协议进行验证,熟悉和掌握Kerberos协议是学习域渗透的基础。 Kerberos协议中主要的三个角色: 1.访问服务的Client2.提供服务的Server3.KDC:密钥分发中心,默认安装在域控上 AS:身份验证服务 TGS:票证授予服务 Kerberos协议认证过程: 协议可以分为三个步骤:一是获得票据许可票据,二是获取服务许可票据,三是获得服务。 第一步:获得票据许可票据 KRB_AS_REQ:用于向KDC请求TGT 当用户在客户端输入域用户和密码时,客户端将用户密码转换为hash作为加密密钥,对时间戳进行加密作为请求凭据。 cipher:加密的时间戳,即客户端的当前时间与用户的hash加密后的结果 KRB_AS_REQ 数据包如下: KRB_AS_REP:用于通过KDC传递TGT 收到请求后,KDC从AD域数据库中找到对用用户的hash解密时间戳来验证用户身份。如果时间戳在允许的时间范围内,那么它就会生成一个会话密钥(Session key),以AS_REP 数据包进行响应。 AS_REP 包含信息: ticket:使用krbtgt hash加密,包含用户名/会话密钥和到期时间等信息.enc-part:使用用户hash加密,包含会话密钥/TGT到期时间和随机数(防重放) KRB_AS_REP数据包如下: 第二步:获得服务许可票据 KRB_TGS_REQ:使用TGT向KDC请求TGS 客户端获得TGT和用户密钥加密的enc-part,使用用户hash解密enc-part获得会话密钥(Session key),然后使用会话密钥将用户名/时间戳进行加密,生成authenticator和TGT发送给TGS。 ticket:实质上就是一张TGT,客户端没有 krbtgt hash,故无法解密TGT. KRB_TGS_REQ 数据包如下: KRB_TGS_REP:通过KDC传递TGS TGS 收到KRB_TGS_REQ请求后,使用 krbtgt hash解密ticket 获取会话密钥(Session key),然后使用会话密钥解密 authenticator 获取用户名和时间戳进行身份验证。确认信息后,创建一个服务会话密钥(Service Session key)。 ticket:使用对应的服务密钥进行加密,包含服务会话密钥/用户名/到期时间等信息,本质上就是一张ST(Service Ticket)。enc-part:包含使用会话密钥加密的服务会话密钥(Service Session key) KRB_TGS_REP数据包如下: 第三步:获得服务 KRB_AP_REQ:使用TGS,服务对用户身份验证 客户端已经拥有了有效的TGS可以与服务进行交互,使用会话密钥解密 enc-part,得到 服务会话密钥(Service Session key),将用户名/时间戳等信息使用服务会话密钥(Service Session key)进行加密,得到新的Authentication。 KRB_AP_REP:由服务用来针对用户标识自身 服务端接收到请求,使用自己的hash解密TGS获得服务会话密钥(Service Session key)和授权用户信息,然后使用服务会话密钥解密Authentication,比对用户名和时间戳等信息,如果有相互验证标记,服务端使用服务会话密钥加密时间戳发给客户端,客户端解密时间戳验证服务端,然后开始请求服务。 本文分享自微信公众号 - Bypass(Bypass--)。 如有侵权,请联系 support@oschina.cn 删除。 本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

如何理解MySQL事务的隔离级别

关注我,更多精彩文章第一时间推送给你 读未提交(READ UNCOMMITTED) 读已提交(READ COMMITTED) 可重复读(REPEATABLE READ) 可串行化(SERIALIZABLE) MySQL的默认事务的隔离级别是**可重复读** <!--more--> -- 登录mysql的root账户,-p待输入密码,-h mysql服务器地址 -P 端口号(注意大写) ➜ mysql -u root -p -h localhost -P 3307 Enter password: **** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 284 Server version: 5.7.28 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> -- 查看当前事务的隔离级别 mysql> show variables like 'transaction_isolation'; +-----------------------+-----------------+ | Variable_name | Value | +-----------------------+-----------------+ | transaction_isolation | REPEATABLE-READ | +-----------------------+-----------------+ -- 或者如此查看事务的隔离级别 mysql> SELECT @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+ -- 可以看到MySQL的默认事务隔离级别是可重复读 读未提交 会产生所有的问题(脏读、不可重复读、幻读) 读已提交 在此事务隔离级别下,事务B只能在事务A修改并且已提交后才能读取到事务A修改的数据。 避免了脏读 但还是会产生(不可重复读、幻读) 可重复读 在此事务隔离级别下,事务B只能在事务A修改数据并提交后,自己也提交事务后,才能读取到事务A修改的数据。 避免了脏读和不可重复读 但还是会产生幻读问题 可串行化 此种事务隔离级别别最高,不会发生任何以下问题(脏读、可重复读、幻读),通过加锁实现 读锁和写锁,只在读读不阻塞,读写、写读、写写都会阻塞。 解读一下并发造成的问题(脏读、不可重复读、幻读) 数据库表t_user我的表字段 mysql> show columns from t_user; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(50) | NO | | NULL | | +-------+-------------+------+-----+---------+----------------+ **注意:**MySQL 默认开启事务自动提交模式,即除非显式的开启事务(BEGIN 或 START TRANSACTION),否则每条 SOL 语句都会被当做一个单独的事务自动执行。 -- 可以看到mysql默认开启自动提交模式 mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ -- 可以设置是否自动提交 -- 值为 0 和值为 OFF:关闭事务自动提交。 -- 值为 1 和值为 ON:开启事务自动提交。 -- 如下可设置的4种值 -- SET autocommit = 0|1|ON|OFF; 脏读 事务A 读取到了 事务B 修改但是未提交的脏数据 开始之前一定调整事务的隔离级别为读未提交,不然不可能产生脏读现象 mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+ 首先查看一下t_user表的数据 mysql> select * from t_user; +----+--------+ | id | name | +----+--------+ | 1 | 达摩 | +----+--------+ 然后进行如下测试 -- session1 事务A 开始 mysql> begin; -- session2 事务B 开始 mysql> begin; -- session2 事务B 修改name = 吕布 (注意这里并未提交) mysql> update t_user set name = '吕布' where id = 1; -- session1 事务A 读取id = 1的数据 (注意,读到了未提交的脏数据 name = 吕布) mysql> select * from t_user where id = 1; +----+--------+ | id | name | +----+--------+ | 1 | 吕布 | +----+--------+ -- session1 事务A 对读到的脏数据进行提交(事务A结束) mysql> commit; -- session2 事务B 对刚才的修改进行回滚(事务B结束) mysql> rollback; -- 最终数据库中的name并没有改变,但是事务A读到了脏数据,这就是脏读 -- 此事务隔离级别(读未提交)也会造成不可重复读和幻读,这里不做演示,看下面的隔离级别中演示 不可重复读 事务A 读取表的名字为达摩(并未提交),之后事务B修改了名字为曹操(修改完提交了),此时事务A在未提交的情况下又读取了name却变成了曹操,这就是不可重复读 设置事务的隔离级别为读已提交,也可以为读未提交,因为读未提交也会产生不可重复读现象 -- 设置事务的隔离级别为读已提交 mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; Query OK, 0 rows affected (0.00 sec) -- 查看修改结果,已经为读已提交 mysql> SELECT @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-COMMITTED | +-------------------------+ 首先查看一下t_user表的数据 mysql> select * from t_user; +----+--------+ | id | name | +----+--------+ | 1 | 达摩 | +----+--------+ 然后进行如下测试 -- session1 事务A开始事务 mysql> begin; Query OK, 0 rows affected (0.00 sec) -- session1 事务A 读取表中数据(注意此时未提交事务) mysql> select * from t_user where id = 1; +----+--------+ | id | name | +----+--------+ | 1 | 达摩 | +----+--------+ -- session2 事务B直接修改并提交事务 mysql> update t_user set name = '曹操' where id = 1; Query OK, 1 row affected (0.03 sec) -- session1 事务A 再次读取表中数据,发现同一个事务中得到了不一样的结果,这就是不可重复读 mysql> select * from t_user where id = 1; +----+--------+ | id | name | +----+--------+ | 1 | 曹操 | +----+--------+ -- 只要是事务A一直不提交,其他事务修改提交之后,事务A再次查询都能查到最新修改的结果 -- 此事务隔离级别也会造成幻读,但是避免了脏读 -- 测试是否避免脏读,事务A此时未提交 -- session2 事务B开始 mysql> begin; Query OK, 0 rows affected (0.00 sec) -- session2 事务B修改name = 伽罗 (注意此时未提交) mysql> update t_user set name = '伽罗' where id = 1; Query OK, 1 row affected (0.00 sec) -- session1 事务A进行读取,看到并没有读取到脏数据 mysql> select * from t_user where id = 1; +----+--------+ | id | name | +----+--------+ | 1 | 曹操 | +----+--------+ -- session2 事务B回滚 mysql> rollback; Query OK, 0 rows affected (0.05 sec) -- session1 事务A提交 mysql> commit; Query OK, 0 rows affected (0.00 sec) 知识点:为什么加了写锁,别的事务还可以进行读操作? 因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。 幻读 事务A读取表中数据为一条(这时候未提交), 事务B向表中添加了一条记录(提交),此时事务A再次查询表中数据,结果是两条,好像产生了幻觉,这就是幻读 设置事务的隔离级别为可重复读,也可以为读未提交,也可以为读已提交,因为这三种事务隔离级别都会产生幻读现象 -- 设置事务的隔离级别为可重复读 mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; Query OK, 0 rows affected (0.00 sec) -- 查看修改结果,已经为可重复读 mysql> SELECT @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+ 首先查看一下t_user表的数据 mysql> select * from t_user; +----+--------+ | id | name | +----+--------+ | 1 | 曹操 | +----+--------+ 之后进行的测试 -- session1 事务A开启 mysql> begin; Query OK, 0 rows affected (0.00 sec) -- session1 事务A查询(注意未提交) mysql> select * from t_user; +----+--------+ | id | name | +----+--------+ | 1 | 曹操 | +----+--------+ -- session2 事务B添加数据(提交) mysql> insert into t_user(name) values('马可波罗'); Query OK, 1 row affected (0.04 sec) -- session1 事务A再次查询(一个事务中两次查询多出了数据,幻读) mysql> select * from t_user; +----+--------------+ | id | name | +----+--------------+ | 1 | 曹操 | | 2 | 马可波罗 | +----+--------------+ -- 测试此事务隔离级别能避免不可重复读现象(此时事务A还是未提交) -- session2 事务B修改id= 1的数据名字为狄仁杰(提交) mysql> update t_user set name = '狄仁杰' where id = 1; Query OK, 1 row affected (0.04 sec) -- 此时session1 事务A再次读取数据(可以看到跟上一次一样,证明避免了不可重复读现象) mysql> select * from t_user; +----+--------------+ | id | name | +----+--------------+ | 1 | 曹操 | | 2 | 马可波罗 | +----+--------------+ 以上讲的都是并发操作可能造成的读的问题 关于乐观锁和悲观锁解决并发写操作可能造成的丢失更新的问题请访问我的这篇文章

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

我对独立思考的理解

随着互联网的普及,社会评论者的门槛越来越低,各路声音充斥各大平台;同时,思考这件事情似乎该质疑了,值得反思。 大脑本质上并不喜欢思考,更多的是记忆在驱动行为。 独立思考应如何定义,我思前想后不知如何解释,不过老子《道德经》中的一句话很有独立思考的意思:“有物混成,先天地生,寂兮寥兮,独立而不改,周行而不殆,可以为天地母。” 关于独立思考,我分为五个部分来讨论何为独立思考——群体与思考、价值观与思考、信息与思考、辩证与思考、读书与思考。 我只是写出我的想法,希望对阅读的您有启发作用。 群体与思考 说到群体与思考的关系,绕不开《乌合之众》一书。 在《乌合之众》这本中,作者古斯塔夫·勒庞提出一种观点: 在群体之中,人的个性会被湮没,独立思考和推理的能力会丧失,智力也随之下降,并表现出冲动、情绪夸张、易怒、易受暗示、轻信等主要特征。 我认为讨论“群体与思考”这个话题本身似乎就是错误的,因为群体只有寥寥无几的“领导者”在思考与鼓动。甚至连“领导者”也没有思考,只是在鼓动,而这种鼓动的行为翻译过来就是“拉人站队”。 人在思维层面分为两类,“有意识”与“无意识”。其中“有意识”的一面更多受认知支配,需要靠逻辑思维判断,推理,思考等复杂的智力参与;“无意识”的一面更多受行动支配,是不需要理智和智商参与的下意识。然而群体并不具备“有意识”的特征,它仅仅处在“无意识”的状态和简单的逻辑判断和推理。 人在群体里很容易失去自我,很容易向着群体的行为靠拢,即不自知的“站队”。因为群体具有匿名性,个体在其中会有依靠感,这种依靠感会使群体的思维降低为零,当群体里最开始有一个暗示,其他人往往就会一窝蜂地往一边倒。 不难发现,个体在群体中,很难用到思维、智力和认知,无法获得深入的交流和深度思考,获得的只是群体的力量、极端的情绪和泛泛而谈。有一个现象可以充分说明这个观点——群体的“领导者”大都表现的爱断言,爱重复观点,爱说一些形象化能够激发无限想象的东西;反而表现的总是全面理智分析问题,对事对物持有辩证的理智中立态度的人很难成为“领导者”,因为这种理智很难煽动群众的情绪。 当然群体特征也有其优点,比如超强的行动力、超强的服从力等等。此文讨论的是群体与思考之关系,暂不讨论群体的优点。 这种群体现象在网络上随处可见,人们失去独立思考,根据情绪与偏见选择一方站入队伍,接受群体的暗示后开始语言攻击,随意把事实捏造成符合群体口味的样子,如此恶性循环。 价值观与思考 仔细分析两者的关系,价值观与思考似乎是相互寄生的关系,此供于彼的同时此源于彼。 价值观左右着思考角度,思考角度潜移默化着价值观。 面对同样一件事情,不同的处理方式来自于价值观的影响,即底层思维逻辑的指导,比如行为习惯与处世风格。在特定的时间、地点、条件下,人们的价值观总是相对稳定和持久的。通常对某种人或事物的好坏总有一个看法和评价,对某种人或事物的思考角度也是单一的。 价值观对思考具有导向作用,思考所获得的结论对价值观具有潜移默化的作用。改变思考方式需要不断试图跳出这个循环,同时也影响着价值观的形成。 通常,价值观禁锢着独立思考——对异于自己价值观的思考角度的排斥。 大部分人对他人的思考角度是否脱离事实并不在意,他们欣赏符合自己原有价值观的声音。因为人的本性就是贪于舒适,符合自己价值观的声音可以让人产生欣慰的感觉。遇到源于不同价值观的声音时,要么有“优越感”,对其不屑一顾,甚至想要说服对方接受自己的声音;要么有“挫败感”,高抬对方的想法,但此时如果有符合自己原有价值观的声音与对方辩论,往往会获得“安慰”,而后加入说服对方的阵营。 由于人性这一特点,大部人止步于对不同思考角度的接受,以至于思考角度极为狭隘,宛如“井底之蛙”,却自以为“独立思考”。 真正的独立思考是尽可能突破思考者本身价值观的存在,当一种思考夹杂着诸多的价值观,从某种程度上是曲解事实的。但圣贤如凤毛麟角,凡人难以做到不参杂个人价值观去思考,那么,试着拥抱各种声音吧。 信息与思考 关于信息与思考的关系,我想从两个方面讨论这个话题——信息同质化、信息真实性。 大部分信息的本质,都是反复围绕着某个思维模型重复述说。 大多数人每天摄入的信息都是同质化信息,固定的信息样式配合着新的事情,看似每天得到的信息不同,实则是一套样式。在信息爆炸时代,尤其是各种APP通过算法推荐的信息,让人的思维愈加封闭与固执。 信息的同质化使人变得懒惰,思维是有惯性的,当人吸取同一套信息样式,即使信息内容不同,也不会感到疲倦;一旦跳出已有的思维模型,就会极度不适应,大脑会感到劳累。如果不跳出已有的思维模型,久而久之,便不再愿意尝试其他的思维模型,大脑的信息都是一个样式,思考的方式与角度自然单一。 事物的生成是多面的,固有的思维模式对其进行思考不过是盲人摸象。只有跳出已有的思维模型,吸取各式信息样式,才能拥有相对宽阔的眼界,达成对事物多方位的思考,进而尽可能的了解事物真正的实质。 大多数人对信息真实性的判断源于主观期望。 在不掌握信息和事实的情况下进行思考,翻译为大白话就是“知道的太少,想的太多”,简称“杠精”。“杠精”的思维就是如此,对信息的真实性毫不关心,信息真实与否的判断来自如何能反驳他人,通过反驳他人获取存在感。 一味的强调思考,忽略信息与事实的意义,那么这样的思考是毫无意义的。真正的思考是追溯信息渠道、辨识信息真假、分析信息内容;脱离任何一环,“思考”将失去其价值。有价值的思考所得出的结论是经得起事实的验证的,而非空中楼阁,经不起一丝微风。 辩证与思考 对一件事物的思考,本质上就是试图完整的认识该事物。 这里的辩证正是唯物、客观的思维方式。 辩证思维就是通过多角度、客观的看待事物,发现该事物在不同角度看待之下的矛盾之处,再从其各个方面的相互联系中进行考察,从整体上、本质上完整的认识该事物。 对辩证最好的诠释就是法院审判——为什么一个大多数人将其视为十恶不赦的“凶犯”也有辩护律师: 之所以我认为这是对辩证最好的诠释,就是因为大多数人对这样的行为具有偏见性,认为十恶不赦的“凶犯”直接死刑就好了,却还有人为其辩护。为“凶犯“提供辩护律师,就是为了尽可能的揭露真相,不能以人的主观性对案件敲锤定音。 法审本身就类似一个辩论赛,区别在于法审需要以《刑法》和事实为依据进行辩论。正是有辩护律师的介入,甚至为“凶犯”极力辩护,才能使得反方收集各种证据和资料证明“凶犯”就是凶犯。所以辩护律师的各种挑刺恰恰就是为了保证判决的客观公正性。 辩证使得我们的思考能够尽可能的多角度思考,从不同角度探索事物,从而挖掘事物的本质。在辩论赛中,双方都会因为棋逢对手的对方而更加深刻思考辩论的对象,发现更多未知的区域。而辩证思维就好像我们在大脑开一场辩论赛。 读书与思考 书不是权威,书不是神写的。 读书不过是自己思考的代用物而已。 最好的学习永远是书,相对于其他信息而言,书是一种沉淀——一旦出版基本不会修改,所以作者落笔更加谨慎,反复修改,内容也更有系统性,是脉络可寻的总体,可通过它向某种体系开展思考。 读书最重要的是因书而思考,而非一味的读。我认为一本书对一个人的价值,在于这本书引发了一个人怎样的思考,而不是从书中认识了哪个字。我们只可以把书本当做途径,阅读时依赖他人把自己的思想导向某方面。要切记书不是神写的,因为有很多书籍非但无益,而且还会使人走向邪路,读书一定要思考和质疑。 本位首发于我的博客一杯茶

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

深入理解JVM - JVM内存模型

各版本的差异 JDK1.6 在JDK1.6 的时候运行时常量池在方法区中 JDK1.7 在JDK1.7 的时候运行时常量池在堆中 JDK1.8 在JDK1.8 的时候,JVM内存模型直接将方法区移到了本地内存中,叫元数据空间。该区域的内存大小就只受本机总内存的限制,但是当申请不到足够内存时也会报出 程序计数器 主要作用是:存储当前线程运行时的字节码行号,占用空间小且线程私有。 字节码解释器会通过改变程序计数器的值来选取下一条需要执行的字节码指令,并且分支(if)、循环、跳转、异常处理、线程恢复等基础功能都是基于程序计数器来实现的。 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。 JAVA 虚拟机栈 线程私有,描述的是Java方法执行的内存模型,主要作用是:存储运行当前线程需要执行的所有方法所对应的栈帧。 一个线程栈的默认大小是1M,可用参数 –Xss调整大小,例如-Xss256k; 栈帧 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是Java 虚拟机栈(Virtual Machine Stack)的栈元素。 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法的执行过程,就是一个栈帧在虚拟机栈中从入栈到出栈的过程。 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。 栈桢结构 下面是栈帧的一个概念结构图: 局部变量表 局部变量表(Local Variable Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。 每个栈对应局部变量表的内存空间,在编译时期就已经确认了,不会随着程序的运行而发生改变。 局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,一个long或double类型数据会占用2个槽,一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据会占用一个槽。 reference:表示对象引用,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置,如:对象所属数据类型在方 法区中的存储的类型信息。 returnAddress:返回值类型,指向了一条字节码指令的地址。 虚拟机通过索引定位的方式来使用局部变量表,如果访问的是32位数据类型的变量,那么索引n就代表使用第n个槽,如果访问的是64位的数据类型变量,则会同时使用n和n+1两个槽。对于共同存放一个64位数据的两个槽是不允许单独访问其中任何一个的,如果单独访问会直接抛出异常。 为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量, 其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。并且Solt的复用会直接影响垃圾收集器的行为。我们通过下面一段代码来演示一下是如何影响的,我先向内存申请64M的空间,然后手动触发GC。通过设置虚拟机运行参数-verbose:gc可以使我们看到垃圾收集器的过程。 示例1: public static void main(String[] args) throws InterruptedException { { byte[] placeholder = new byte[64 * 1024 * 1024]; } System.gc(); } 运行结果: Connected to the target VM, address: '127.0.0.1:53448', transport: 'socket' [GC (System.gc()) 73284K->66775K(247296K), 0.0016168 secs] [Full GC (System.gc()) 66775K->66658K(247296K), 0.0059011 secs] Disconnected from the target VM, address: '127.0.0.1:53448', transport: 'socket' 我们可以发现placeholder变量虽然已经超出作用域,但是它对应的64M空间并没有被回收。 示例1: public static void main(String[] args) throws InterruptedException { { byte[] placeholder = new byte[64 * 1024 * 1024]; } int i = 0; System.gc(); } 运行结果: Connected to the target VM, address: '127.0.0.1:63864', transport: 'socket' [GC (System.gc()) 73284K->66775K(247296K), 0.0012476 secs] [Full GC (System.gc()) 66775K->1122K(247296K), 0.0060915 secs] Disconnected from the target VM, address: '127.0.0.1:63864', transport: 'socket' 加上int i = 0;后,我们执行垃圾回收时,我们可以看出placeholder变量在超出作用域后,对应的64M空间被回收了。placeholder能否被回收的根本原因是:局部变量表中的Slot是否还存有关于placeholder数组对象的引用。在示例1中,placeholder原本所占用的Slot还没有被其他变量所复用,placeholder变量对应的局部变量表还仍然保持着对它的关联,所以GC无法回收它。如果这条语句的后续操作执行很耗时,就会造成大量空间浪费。int i = 0;的作用是用来打断placeholder变量和它对应的局部变量表之间的联系,在实际开发过程中我们可以通过placeholder = null来更加优雅的实现这一点。 操作数栈 操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First Out, LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中,和局部变量表不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。 举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。 Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。 动态链接 Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。 方法返回地址 当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。 另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的。 方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。 在实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部归为一类,称为栈帧信息。 异常情况 StackOverflowError:当请求的栈深度大于虚拟机所允许的最大深度时会抛出此异常,方法的循环调用,递归调用等情况; OutOfMemoryError:当虚拟机栈在动态扩容的过程中无法申请到足够的内存时会报此异常,不建议虚拟机栈无线扩容; 相关参数 –Xss:设置一个线程栈的大小,默认大小是1M,例如-Xss256k; 本地方法栈 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,线程私有,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。 Native方法称为本地方法。在java源程序中以关键字“native”声明,不提供函数体。其实现使用非Java语言在另外的文件中编写,编写的规则遵循Java本地接口的规范(简称JNI)。 Java堆 Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块;所有线程共享;它唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术出现,导致一些对象的分配并没有在堆上 Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。Java堆无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。 老年代:2/3 的堆空间 年轻代:1/3 的堆空间 eden区:8/10 的年轻代 survivor0: 1/10 的年轻代 survivor1: 1/10 的年轻代 异常情况 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。 相关参数 -Xms:堆的最小值(初始值); -Xmx:堆的最大值; -Xmn:新生代的大小; -XX:NewSize:新生代最小值(初始值); -XX:MaxNewSize:新生代最大值; 方法区/元数据区域 线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 JDK1.7及以前叫方法区或者永久代,这时该区域在JVM运行时数据区域中。在JDK1.8过后,废除了原来的方法区,在本地内存中直接开辟了一个空间来存储原来方法区中的数据,叫元数据区域,详见上图。 异常情况 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常 相关参数 jdk1.7及以前: -XX:PermSize:设置方法区最小值(初始值); -XX:MaxPermSize:设置方法区最大值; jdk1.8以后: -XX:MetaspaceSize:表示metaspace首次使用不够而触发FGC的阈值,只对触发起作用; -XX:MaxMetaspaceSize:用于设置metaspace区域的最大值。 运行时常量池 用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。在JDK1.7之前该区域在方法区中,在JDK1.7及以后该区域放到了堆内存中。 字面量 文本字符串:String a = "abc",这个abc就是字面量; 八种基本类型:int a = 1; 这个1就是字面量; 声明为final的常量 符号引用 以一组符号来描述所引用的目标,比如:一个java类(假设为People类)被编译成一个class文件时,如果People类引用了Tool类,但是在编译时People类并不知道引用类的实际内存地址,因此只能使用符号引用来代替。 异常情况 当常量池无法再申请到内存时会抛出OutOfMemoryError异常。在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 直接内存 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。 异常情况 服务器管理员在配置虚拟机参数时,会根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。 相关参数 -XX:MaxDirectMemorySize:设置直接内存大小(默认与堆内存最大值一样)。 JVM 内存参数说明 -Xss:设置一个线程栈的大小; -Xms:堆的最小值(初始值); -Xmx:堆的最大值; -Xmn:年轻代的大小; -XX:NewSize:年轻代最小值(初始值); -XX:MaxNewSize:年轻代最大值; -XX:NewRatio:设置年轻代和年老代的比值。如:为3,表示年轻代:年老代=1:3,年轻代占整个年轻代年老代和的1/4; -XX:SurvivorRatio:年轻代中Eden区与两个Survivor区的比值。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5; -XX:PermSize: 方法区初始大小(JDK1.7及以前); -XX:MaxPermSize: 方法区最大大小(JDK1.7及以前); -XX:MetaspaceSize: 元数据区初始值(JDK1.8及以后); -XX:MaxMetaspaceSize: 元数据区最大值(JDK1.8及以后); -XX:MaxDirectMemorySize:直接内存大小,默认与堆内存最大值一样(-Xmx); -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/heap/dump:当JVM发生OOM时,自动生成DUMP文件,/path/heap/dump表示文件路径; -、-X 和 -XX的区别 -:标准参数,所有的JVM实现都必须实现这些参数的功能,而且向后兼容; -X:非标准参数,默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容; -XX:非稳定参数,此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用; 示例 JDK 1.7: set JAVA_OPTS=-Xms1024m -Xmx1024m -Xss512k -XX:PermSize=128m -XX:MaxPermSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m JDK1.8 : set JAVA_OPTS=-Xms1024m -Xmx1024m -Xss512k -XX:MetaspaceSize=128m -XX:MAXMetaspaceSize=256m -XX:NewSize=256m -XX:MaxNewSize=256m 查看JVM运行时参数 -XX:+PrintFlagsInitial 查看初始值 -XX:+PrintFlagsFinal 查看最终值(初始值可能被修改掉) -XX:+UnlockExperimentalVMOptions 解锁实验性参数 -XX:+UnlockDiagnosticVMOptions 解锁诊断参数 -XX:+PrintCommandLineFlags 打印命令行参数 示例 java -XX:+PrintFlagsInitial -version 输出结果: D:\workspace\etc\etc-credit-card>java -XX:+PrintFlagsInitial -version [Global flags] uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} uintx AdaptiveSizePolicyOutputInterval = 0 {product} uintx AdaptiveSizePolicyWeight = 10 {product} uintx AdaptiveSizeThroughPutPolicy = 0 {product} uintx AdaptiveTimeWeight = 25 {product} bool AdjustConcurrency = false {product} bool AggressiveOpts = false {product} intx AliasLevel = 3 {C2 product} bool AlignVector = true {C2 product} bool UseLargePagesIndividualAllocation := false {pd product} ... 将结果输出到文本: java -XX:+PrintFlagsInitial -version > flag.txt =表示默认值; :=表示被用户或JVM修改后的值

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

深入理解 Java 中 SPI 机制

本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/vpy5DJ-hhn0iOyp747oL5A 作者:姜柱 SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,本文由浅入深地介绍了Java SPI机制。 一、简介 SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。 SPI与API区别: API是调用并用于实现目标的类、接口、方法等的描述; SPI是扩展和实现以实现目标的类、接口、方法等的描述; 换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。 参考:https://stackoverflow.com/questions/2954372/difference-between-spi-and-api?answertab=votes#tab-top SPI整体机制图如下: 当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。 二、应用场景 SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。 SPI流程: 有关组织和公式定义接口标准 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件 开发者使用 比如JDBC场景下: 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。 在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。 同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。 三、使用demo 1.定义一个接口HelloSPI。 package com.vivo.study.spidemo.spi; public interface HelloSPI { void sayHello(); } 2.完成接口的多个实现。 package com.vivo.study.spidemo.spi.impl; import com.vivo.study.spidemo.spi.HelloSPI; public class ImageHello implements HelloSPI { public void sayHello() { System.out.println("Image Hello"); } } package com.vivo.study.spidemo.spi.impl; import com.vivo.study.spidemo.spi.HelloSPI; public class TextHello implements HelloSPI { public void sayHello() { System.out.println("Text Hello"); } } 在META-INF/services/目录里创建一个以com.vivo.study.spidemo.spi.HelloSPI的文件,这个文件里的内容就是这个接口的具体的实现类。 具体内容如下: com.vivo.study.spidemo.spi.impl.ImageHello com.vivo.study.spidemo.spi.impl.TextHello 3.使用 ServiceLoader 来加载配置文件中指定的实现 package com.vivo.study.spidemo.test import java.util.ServiceLoader; import com.vivo.study.spidemo.spi.HelloSPI; public class SPIDemo { public static void main(String[] args) { ServiceLoader<HelloSPI> serviceLoader = ServiceLoader.load(HelloSPI.class); // 执行不同厂商的业务实现,具体根据业务需求配置 for (HelloSPI helloSPI : serviceLoader) { helloSPI.sayHello(); } } } 输出结果如下: Image Hello Text Hello 四、源码分析 // ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者 public final class ServiceLoader<S> implements Iterable<S> { // 查找配置文件的目录 private static final String PREFIX = "META-INF/services/"; // 表示要被加载的服务的类或接口 private final Class<S> service; // 这个ClassLoader用来定位,加载,实例化服务提供者 private final ClassLoader loader; // 访问控制上下文 private final AccessControlContext acc; // 缓存已经被实例化的服务提供者,按照实例化的顺序存储 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 迭代器 private LazyIterator lookupIterator; } // 服务提供者查找的迭代器 public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); // hasNext方法 public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } // next方法 public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } }; } // 服务提供者查找的迭代器 private class LazyIterator implements Iterator<S> { // 服务提供者接口 Class<S> service; // 类加载器 ClassLoader loader; // 保存实现类的url Enumeration<URL> configs = null; // 保存实现类的全名 Iterator<String> pending = null; // 迭代器中下一个实现类的全名 String nextName = null; public boolean hasNext() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } public S next() { if (!hasNext()) { throw new NoSuchElementException(); } String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service,"Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated: " + x, x); } throw new Error(); // This cannot happen } } 首先,ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。 其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。 最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap类型) 然后返回实例对象。 五、不足 1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。 2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。 3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。 六、规避 针对以上的不足点,我们在SPI机制选择时,可以考虑使用dubbo实现的SPI机制。 具体参考:http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html

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

回归架构本质,重新理解微服务

第一部分:微服务的诞生、演变以及应用策略 记者:近几年来,微服务架构设计方式被提出并在越来越多的企业中得以实践和落地,但对于刚开始接触微服务的人来说,还是不知道要从哪些方面开始了解。您能否结合软件架构的发展历史,聊聊微服务的发展与特征。 梁鑫:微服务本质上是一种架构的风格,如果要了解微服务,我认为需要先了解整个架构的发展脉络。 软件架构,总是在不断的演进中。如果把时间退回到二十年前,当时企业级领域研发主要推崇的还是C/S模式,PB、Delphi这样的开发软件是企业应用开发的主流。 随着时间的推移,我们发现标准化的客户端存在一些弊病,比如我有一千个终端,升级版本需要每一台终端都升级,这是非常麻烦的。然后,企业应用研发开始向互联网学习,把浏览器作为客户端来使用,就可以避免这个问题。因此,基于浏览器的B/S架构开始渐渐流行起来。 刚开始的时候是A

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

QLExpress代码解读,运行原理解析

本文针对上图的功能详细图,进行逐个的简单介绍:代码入口、代码的主要逻辑和算法。 调用代码实例 //本文以helloworld案例,开启了两个打印日志的参数,实际使用通常不建议打开。 boolean printParseLog = true;//语法分析日志开关 boolean printExecuteLog = true;//每一条指令执行开关 ExpressRunner runner = new ExpressRunner(false, printParseLog); IExpressContext<String, Object> context = new DefaultContext<String, Object>(); String script = "sum=0;for(i=0;i<10;i=i

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

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文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册