首页 文章 精选 留言 我的

精选列表

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

国会提案:美国公民可以就网络攻击向外国政府问责索赔

3月8日,德克萨斯州的民主党联邦众议员Colin Allred, D-TX与其两党同事提出《国土和网络威胁法》(The Homeland and Cyber Threat Act,简称HACT Act)。该法案指出,允许美国人在联邦或州法院对向美国发动或参与网络攻击的外国政府提出索赔。 简单来说,对于归因外国政府的网络攻击,美国公民可以追究责任。并且,该立法将取消外国人(包括外国官员、雇员或代理人)在在美国法院涉及网络攻击造成的人身伤害、名誉损害,或财产损失被寻求赔偿时的豁免权。 这一法案看似合理,但是跨国索赔的难度是可预见的。对此,法案强调,美国国会必须通过引入法律来封锁位于美国的这些州的资产,从而制止破坏美国国家安全的外国。 封锁资产或许成为美国对外国政府网络攻击的一种“索赔方式”。但且不说这种做法的合理性,仅仅是攻击归因本来就存在很多难点,尤其是国家级攻击大多都存在归因不确定性,存疑性。因此,美国这一法律提案很有可能演变成借公民申诉的理由将攻击事态上升至国家之间。 综合上述原因,在笔者看来成功索赔依然是不理想的,且很有可能成为又一个美国攻击归因的法律依赖,并且有效对其他国家进行污名化指责。

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

面试被问分布式事务(2PC、3PC、TCC),这样解释没毛病!

说了一些没用的,脑子里总想起这个事,不说不痛快,大家姑且就当笑话听吧,下边我们进入正题 引言 前两天有个学弟公众号留言,说让讲讲分布式事务,面试就挂在这个问题上。时下随着微服务架构体系的流行,面试的题目也都慢慢开始升级,不再是早些年单纯的问点SSH框架知识、数据结构了。高并发、高可用、分布式服务治理、分布式文件系统、分布式xxx,反正和分布式沾边的都会问点, 项目实际用不用不要紧,关键你得了解,是不是总有一种学不动了的感觉? 什么是分布式事务? 我们看看百度上对于分布式事务的定义:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 额~ 看了反而更懵逼了,简单的画个图好让大家理解一下,拿下单减库存来说举例:当系统的业务量很小时,“一站式”的系统完全可以满足现有业务需求,所有的业务都共用一个数据库,整个下单流程或许只用在一个方法里同一个事务下操作数据库即可。 此时所有操作都在一个事务里,要么全部提交,要么全部回滚 。 但随着业务量不断增长,“一站式”系统渐渐扛不住巨大的流量,就需要对数据库进行分库分表,将业务服务化拆分(SOA),就会分离出了订单中心、用户中心、库存中心。而这样就造成业务间相互隔离,每个业务都维护着自己的数据库,数据的交换只能进行RPC调用。 用户再下单时,创建订单和扣减库存,需要同时对订单DB和库存DB进行操作。两步操作必须同时成功,否则就会造成业务混乱,可此时我们只能保证自己服务的数据一致性,无法保证调用其他服务的操作是否成功,所以为了保证整个下单流程的数据一致性,就需要分布式事务介入。 在说分布式事务之前,先回忆一下事务的基本概念:事务是一个程序执行单元,里面的所有操作要么全部执行成功,要么全部执行失败。 一个事务有四个基本特性,也就是我们常说的(ACID)。 Atomicity(原子性) :事务是一个不可分割的整体,事务内所有操作要么全做成功,要么全失败。 Consistency(一致性) :务执行前后,数据从一个状态到另一个状态必须是一致的(A向B转账,不能出现A扣了钱,B却没收到)。 Isolation(隔离性): 多个并发事务之间相互隔离,不能互相干扰。 Durablity(持久性) :事务完成后,对数据库的更改是永久保存的,不能回滚。 上面这些知识点都是反反复复念叨的概念,面试必背的东西。 分布式事务解决方案 有困难就一定会有解决问题的办法,什么都难不倒聪明的程序员。 XA协议是一个基于数据库的分布式事务协议,其分为两部分:事务管理器和本地资源管理器。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚。二阶提交协议(2PC)和三阶提交协议(3PC)就是根据此协议衍生出来而来。如今Oracle、Mysql等数据库均已实现了XA接口。 1、两段提交(2PC) 两段提交顾名思义就是要进行两个阶段的提交:第一阶段,准备阶段(投票阶段) ; 第二阶段,提交阶段(执行阶段)。 上边图片源自网络,如有侵权联系删除 下面还拿下单扣库存举例子,简单描述一下两段提交(2PC)的原理: 之前说过业务服务化(SOA)以后,一个下单流程就会用到多个服务,各个服务都无法保证调用的其他服务的成功与否,这个时候就需要一个全局的角色(协调者)对各个服务(参与者)进行协调。 一个下单请求过来通过协调者,给每一个参与者发送Prepare消息,执行本地数据脚本但不提交事务。 如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中被占用的资源,显然2PC做到了所有操作要么全部成功、要么全部失败。 两段提交(2PC)的缺点 二阶段提交看似能够提供原子性的操作,但它存在着严重的缺陷 网络抖动导致的数据不一致: 第二阶段中协调者向参与者发送commit命令之后,一旦此时发生网络抖动,导致一部分参与者接收到了commit请求并执行,可其他未接到commit请求的参与者无法执行事务提交。进而导致整个分布式系统出现了数据不一致。 超时导致的同步阻塞问题: 2PC中的所有的参与者节点都为事务阻塞型,当某一个参与者节点出现通信超时,其余参与者都会被动阻塞占用资源不能释放。 单点故障的风险: 由于严重的依赖协调者,一旦协调者发生故障,而此时参与者还都处于锁定资源的状态,无法完成事务commit操作。虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个协调者宕机导致的参与者处于阻塞状态的问题。 2、三段提交(3PC) 三段提交(3PC)是对两段提交(2PC)的一种升级优化,3PC在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制,当参与者各种原因未收到协调者的commit请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,但3PC 还是没能从根本上解决数据一致性的问题。 上边图片源自网络,如有侵权联系删除 3PC 的三个阶段分别是CanCommit、PreCommit、DoCommit CanCommit:协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。 PreCommit:协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦参与者中有向协调者发送了No响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort请求,参与者接受abort命令执行事务的中断。 DoCommit: 在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort请求命令,执行事务的中断。 3、补偿事务(TCC) 很多初学者总是被TCC、2PC、3PC这几个概念搞混淆,傻傻分不清,实际上 TCC与 2PC、3PC一样,都只是实现分布式事务的一种方案而已。 TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC 是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。 TCC它的核心思想是:"针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)"。 还拿下单扣库存解释下它的三个操作: Try阶段: 下单时通过Try操作去扣除库存预留资源。 Confirm阶段: 确认执行业务操作,在只预留的资源基础上,发起购买请求。 Cancel阶段: 只要涉及到的相关业务中,有一个业务方预留资源未成功,则取消所有业务资源的预留请求。 上边图片源自网络,如有侵权联系删除 TCC的缺点: 应用侵入性强:TCC由于基于在业务层面,至使每个操作都需要有 try、confirm、cancel三个接口。 开发难度大:代码开发量很大,要保证数据一致性 confirm 和 cancel 接口还必须实现幂等性。 总结 很浅显的介绍了一下2PC、3PC、TCC的概念,如有错误还望温柔指正,分布式事务一直都是面试中比较热点的问题,也是进阶高级Java工程师必备的知识点。 整理了几百本各类技术电子书,有需要的同学可以,在我同名公众号回复[ 666 ]自取。技术群快满了,想进的同学可以加我好友,和大佬们一起吹吹技术,期待你的加入。 无论你是刚入行、还是已经有几年经验的程序员,相信这份面试提纲都会给你不少助力,长按二维码关注 『 程序员内点事 』 ,回复 『 offer 』 自行领取,祝大家 offer 拿到手软

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

多线程问的太深入不知道怎么回答,从volatile开始给你讲清楚

volatile的用途 1.线程可见性 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。 package com.msb.testvolatile; ​ public class T01_ThreadVisibility { private static volatile boolean flag = true; ​ public static void main(String[] args) throws InterruptedException { new Thread(()-> { while (flag) { //do sth } System.out.println("end"); }, "server").start(); ​ ​ Thread.sleep(1000); ​ flag = false; } } 2.防止指令重排序 问题:DCL单例需不需要加volatile? CPU的基础知识 缓存行对齐缓存行64个字节是CPU同步的基本单位,缓存行隔离会比伪共享效率要高Disruptor package com.msb.juc.c_028_FalseSharing; ​ public class T02_CacheLinePadding { private static class Padding { public volatile long p1, p2, p3, p4, p5, p6, p7; // } ​ private static class T extends Padding { public volatile long x = 0L; } ​ public static T[] arr = new T[2]; ​ static { arr[0] = new T(); arr[1] = new T(); } ​ public static void main(String[] args) throws Exception { Thread t1 = new Thread(()->{ for (long i = 0; i < 1000_0000L; i++) { arr[0].x = i; } }); ​ Thread t2 = new Thread(()->{ for (long i = 0; i < 1000_0000L; i++) { arr[1].x = i; } }); ​ final long start = System.nanoTime(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println((System.nanoTime() - start)/100_0000); } } MESI 伪共享 合并写CPU内部的4个字节的Buffer package com.msb.juc.c_029_WriteCombining; ​ public final class WriteCombining { ​ private static final int ITERATIONS = Integer.MAX_VALUE; private static final int ITEMS = 1 << 24; private static final int MASK = ITEMS - 1; ​ private static final byte[] arrayA = new byte[ITEMS]; private static final byte[] arrayB = new byte[ITEMS]; private static final byte[] arrayC = new byte[ITEMS]; private static final byte[] arrayD = new byte[ITEMS]; private static final byte[] arrayE = new byte[ITEMS]; private static final byte[] arrayF = new byte[ITEMS]; ​ public static void main(final String[] args) { ​ for (int i = 1; i <= 3; i++) { System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne()); System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo()); } } ​ public static long runCaseOne() { long start = System.nanoTime(); int i = ITERATIONS; ​ while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayA[slot] = b; arrayB[slot] = b; arrayC[slot] = b; arrayD[slot] = b; arrayE[slot] = b; arrayF[slot] = b; } return System.nanoTime() - start; } ​ public static long runCaseTwo() { long start = System.nanoTime(); int i = ITERATIONS; while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayA[slot] = b; arrayB[slot] = b; arrayC[slot] = b; } i = ITERATIONS; while (--i != 0) { int slot = i & MASK; byte b = (byte) i; arrayD[slot] = b; arrayE[slot] = b; arrayF[slot] = b; } return System.nanoTime() - start; } } 指令重排序 什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理 package com.msb.jvm.c3_jmm; ​ public class T04_Disorder { private static int x = 0, y = 0; private static int a = 0, b =0; ​ public static void main(String[] args) throws InterruptedException { int i = 0; for(;;) { i++; x = 0; y = 0; a = 0; b = 0; Thread one = new Thread(new Runnable() { public void run() { //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间. //shortWait(100000); a = 1; x = b; } }); ​ Thread other = new Thread(new Runnable() { public void run() { b = 1; y = a; } }); one.start();other.start(); one.join();other.join(); String result = "第" + i + "次 (" + x + "," + y + ")"; if(x == 0 && y == 0) { System.err.println(result); break; } else { //System.out.println(result); } } } ​ ​ public static void shortWait(long interval){ long start = System.nanoTime(); long end; do{ end = System.nanoTime(); }while(start + interval >= end); } } volatile如何解决指令重排序 1: volatile i 2: ACC_VOLATILE 3: JVM的内存屏障 4:hotspot实现 bytecodeinterpreter.cpp int field_offset = cache->f2_as_index(); if (cache->is_volatile()) { if (support_IRIW_for_not_multiple_copy_atomic_cpu) { OrderAccess::fence(); } orderaccess_linux_x86.inline.hpp inline void OrderAccess::fence() { if (os::is_MP()) { // always use locked addl since mfence is sometimes expensive #ifdef AMD64 __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); #else __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory"); #endif } } 第一次写这种底层的东西,整理了好几次话术,实在是没整明白该怎么发,因为确实有点难以整理话术,所以这里附上了关键地方的源码以及实例,运行一下可能会更好理解这些技术,后面我会去逐步改进 个人公众号:Java架构师联盟,每日更新技术好文

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

Java虚拟机堆和栈详细解析,以后面试再也不怕问jvm了!

堆 Java堆是和Java应用程序关系最密切的内存空间,几乎所有的对象都放在其中,并且Java堆完全是自动化管理,通过垃圾收集机制,垃圾对象会自动清理,不需自己去释放。根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代(Young Gen)和老年代(Tenured Gen)。其中新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象。新生代分为edn区,s0区,s1区,s0和s1也被称为from和to区域,他们是两块大小相等,并且可以互相转换的空间。绝大多数情况下,对象首先分配在edn区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则他的年龄就加1,对象达到一定的年龄后,则进入老年代。举例:A a=new A();首先把对象存放到堆内存当中,发现这个A只用了一次,首先会进入新生代里边去,存放到edn区域里面,因为Java堆自动化管理,垃圾收集机制,发现这个a只用了一次,如果a又被一段代码引用,垃圾回收机制有算法,每引用一次记录一次,就会马上进入到s0或者是s1区域,一旦a这个对象引用次数非常多了,就会存放到老年代里去。 栈 Java栈是一块线程私有的空间,一个栈,一般由三部分组成:局部变量表、操作数据栈和帧数据区。局部变量表:用于报错函数的参数及局部变量操作数栈:主要保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间。帧数据区:除了局部变量表和操作数据栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便计程序访问常量池,另外当函数返回或出现异常时卖虚拟机子必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。 Java内存分配原理 栈、堆、常量池虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同。一般Java在内存分配时会涉及到以下区域:◆寄存器:我们在程序中无法控制◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中◆堆:存放用new产生的数据◆静态域:存放在对象中用static定义的静态成员◆常量池:存放常量◆非RAM存储:硬盘等永久存储空间 Java内存分配中的栈 在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 Java内存分配中的堆 堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以 在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。 引用变量就相当于是 为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序 运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍 然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因。实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针! 常量池 (constant pool) 常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如:◆类和接口的全限定名;◆字段的名称和描述符;◆方法和名称和描述符。虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。欢迎大家关注我的公种浩【程序员追风】,整理了2019年多家公司java面试题资料100多页pdf文档,文章都会在里面更新,整理的资料也会放在里面。对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引 用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池 会储存在Method Area,而不是堆中。 堆与栈 Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、 anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存 大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态 分配内存,存取速度较慢。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是 确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int, short, long, byte, float, double, boolean, char)和对象句柄(引用)。栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义: int a = 3; 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令 a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响 到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。String是一个特殊的包装类数据。可以用: String str = new String("abc"); String str = "abc"; 两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池 里找有没有"abc",如果没有,则将"abc"存放进字符串常量池 ,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。 String str1 = "abc"; String str2 = "abc"; System.out.println(str1==str2); //true 可以看出str1和str2是指向同一个对象的。 String str1 =new String ("abc"); String str2 =new String ("abc"); System.out.println(str1==str2); // false 用new的方式是生成不同的对象。每一次生成一个。因此用第二种方式创建多个”abc”字符串,在内存中 其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。另 一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换 其值时,应该考虑使用StringBuffer类,以提高程序效率。 首先String不属于8种基本数据类型,String是一个对象。因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。 new String()和new String(”")都是申明一个新的空字符串,是空串不是null; String str=”kvill”;String str=new String (”kvill”)的区别示例: String s0="kvill"; String s1="kvill"; String s2="kv" + "ill"; System.out.println( s0==s1 ); System.out.println( s0==s2 ); 结果为: true true 首先,我们要知结果为道Java 会确保一个字符串常量只有一个拷贝。因为例子中的 s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字 符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。示例: String s0="kvill"; String s1=new String("kvill"); String s2="kv" + new String("ill"); System.out.println( s0==s1 ); System.out.println( s0==s2 ); System.out.println( s1==s2 ); 结果为: false false false 例2中s0还是常量池 中"kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。 String.intern():再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;看示例就清楚了 示例: String s0= "kvill"; String s1=new String("kvill"); String s2=new String("kvill"); System.out.println( s0==s1 ); System.out.println( "**********" ); s1.intern(); s2=s2.intern(); //把常量池中"kvill"的引用赋给s2 System.out.println( s0==s1); System.out.println( s0==s1.intern() ); System.out.println( s0==s2 ); 结果为: false false //虽然执行了s1.intern(),但它的返回值没有赋给s1 true //说明s1.intern()返回的是常量池中"kvill"的引用 true 最后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中 ,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:示例: String s1=new String("kvill"); String s2=s1.intern(); System.out.println( s1==s1.intern() ); System.out.println( s1+" "+s2 ); System.out.println( s2==s1.intern() ); 结果: false kvill kvill true 在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一 个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。s1==s1.intern() 为false说明原来的”kvill”仍然存在;s2现在为常量池中”kvill”的地址,所以有s2==s1.intern()为true。 关于equals()和==:这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是 比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。 关于String是不可变的这一说又要说很多,大家只 要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原 因了,因为StringBuffer是可改变的。 下面是一些String相关的常见问题:String中的final用法和理解 final StringBuffer a = new StringBuffer("111"); final StringBuffer b = new StringBuffer("222"); a=b;//此句编译不通过 final StringBuffer a = new StringBuffer("111"); a.append("222");// 编译通过 可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象 的变化,final是不负责的。String常量池问题的几个例子下面是几个常见例子的比较分析和理解:String a = "a1"; String b = "a" + 1; System.out.println((a == b)); //result = true String a = "atrue"; String b = "a" + "true"; System.out.println((a == b)); //result = true String a = "a3.4"; String b = "a" + 3.4; System.out.println((a == b)); //result = true 分析:JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。 String a = "ab"; String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = false 分析:JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。 String a = "ab"; final String bb = "b"; String b = "a" + bb; System.out.println((a == b)); //result = true 分析:和[3]中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量 池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。 String a = "ab"; final String bb = getBB(); String b = "a" + bb; System.out.println((a == b)); //result = false private static String getBB() { return "b"; } 分析:JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面 程序的结果为false。通过上面4个例子可以得出得知:String s = "a" + "b" + "c"; 就等价于String s = "abc"; String a = "a"; String b = "b"; String c = "c"; String s = a + b + c; 这个就不一样了,最终结果等于: StringBuffer temp = new StringBuffer(); temp.append(a).append(b).append(c); String s = temp.toString(); 由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码: public class Test { public static void main(String args[]) { String s = null; for(int i = 0; i < 100; i++) { s += "a"; } } } 每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行 append操作。String对象的intern方法理解和分析: public class Test4 { private static String a = "ab"; public static void main(String[] args){ String s1 = "a"; String s2 = "b"; String s = s1 + s2; System.out.println(s == a);//false System.out.println(s.intern() == a);//true } } 这里用到Java里面是一个常量池的问题。对于s1+s2操作,其实是在堆里面重新创建了一个新的对象,s保存的是这个新对象在堆空间的的内容,所 以s与a的值是不相等的。而当调用s.intern()方法,却可以返回s在常量池中的地址值,因为a的值存储在常量池中,故s.intern和a的值相等。 总结 栈中用来存放一些原始数据类型的局部变量数据和对象的引用(String,数组.对象等等)但不存放对象内容堆中存放使用new关键字创建的对象.字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。 最后 ​欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!

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

一线大厂面试必问的!

什么是自动垃圾回收? 自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用对象),则没有被任何指针给指向,因此占用的内存也可以被回收掉。在用 C 之类的编程语言时,程序员需要自己手动分配和释放内存。而 Java 不一样,它有垃圾回收器,释放内存由回收器负责。本文接下来将介绍垃圾回收机制的基本过程。 标记 垃圾回收的第一步是标记。垃圾回收器此时会找出哪些内存在使用中,还有哪些不是。 上图中,蓝色表示已引用对象,橙色表示未引用对象。垃圾回收器要检查完所有的对象,才能知道哪些有被引用,哪些没。如果系统里所有的对象都要检查,那这一步可能会相当耗时间。 清除 这一步会删掉标记出的未引用对象。 内存分配器会保留指向可用内存的引用,以供分配新对象。 压缩 为了提升性能,删除了未引用对象后,还可以将剩下的已引用对象放在一起(压缩),这样就能更简单快捷地分配新对象了。 为什么需要分代垃圾收集? 之前说过,逐一标记和压缩 Java 虚拟机里的所有对象非常低效:分配的对象越多,垃圾回收需时就越久。不过,根据统计,大部分的对象,其实用没多久就不用了。来看个例子吧。(下图中,竖轴代表已分配的字节,而横轴代表程序运行时间) 上图可见,存活(没被释放)的对象随运行时间越来越少。而图中左侧的那些峰值,也表明了大部分对象其实都挺短命的。欢迎大家关注我的公种浩【程序员追风】,文章都会在里面更新,整理的资料也会放在里面。 JVM 分代 根据之前的规律,就可以用来提升 JVM 的效率了。方法是,把堆分成几个部分(就是所谓的分代),分别是新生代、老年代,以及永生代。 新对象会被分配在新生代内存。一旦新生代内存满了,就会开始对死掉的对象,进行所谓的小型垃圾回收过程。一片新生代内存里,死掉的越多,回收过程就越快;至于那些还活着的对象,此时就会老化,并最终老到进入老年代内存。Stop the World 事件 —— 小型垃圾回收属于一种叫 "Stop the World" 的事件。在这种事件发生时,所有的程序线程都要暂停,直到事件完成(比如这里就是完成了所有回收工作)为止。老年代用来保存长时间存活的对象。通常,设置一个阈值,当达到该年龄时,年轻代对象会被移动到老年代。最终老年代也会被回收。这个事件成为 Major GC。Major GC 也会触发STW(Stop the World)。通常,Major GC会慢很多,因为它涉及到所有存活对象。所以,对于响应性的应用程序,应该尽量避免Major GC。还要注意,Major GC的STW的时长受年老代垃圾回收器类型的影响。永久代包含JVM用于描述应用程序中类和方法的元数据。永久代是由JVM在运行时根据应用程序使用的类来填充的。此外,Java SE类库和方法也存储在这里。如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。 世代垃圾收集过程 现在你已经理解了为什么堆被分成不同的代,现在是时候看看这些空间是如何相互作用的。后面的图片将介绍JVM中的对象分配和老化过程。首先,将任何新对象分配给 eden 空间。两个 survivor 空间都是空的。 当 eden 空间填满时,会触发轻微的垃圾收集。 引用的对象被移动到第一个 survivor 空间。清除 eden 空间时,将删除未引用的对象。 在下一次Minor GC中,Eden区也会做同样的操作。删除未被引用的对象,并将被引用的对象移动到Survivor区。然而,这里,他们被移动到了第二个Survivor区(S1)。此外,第一个Survivor区(S0)中,在上一次Minor GC幸存的对象,会增加年龄,并被移动到S1中。待所有幸存对象都被移动到S1后,S0和Eden区都会被清空。注意,Survivor区中有了不同年龄的对象。 在下一次Minor GC中,会重复同样的操作。不过,这一次Survivor区会交换。被引用的对象移动到S0,。幸存的对象增加年龄。Eden区和S1被清空。 此幻灯片演示了 promotion。在较小的GC之后,当老化的物体达到一定的年龄阈值(在该示例中为8)时,它们从年轻一代晋升到老一代。 随着较小的GC持续发生,物体将继续被推广到老一代空间。 所以这几乎涵盖了年轻一代的整个过程。最终,将主要对老一代进行GC,清理并最终压缩该空间。 最后 欢迎大家一起交流,喜欢文章记得关注我点个赞哟,感谢支持!

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

10道面试官喜欢问的微服务面试题Spring Cloud+Spring Boot

前言为什么要使用微服务?随着互联网的快速发展,各行各业都在用互联网。互联网已经离不开人们的形形色色。随着越来越多的用户,业务场景也愈来愈复杂。传统的单体架构已经很难满足互联网技术发展的要求,代码可维护性扩展性和可读性降低,维护成本的提高都是驱动微服务的发展趋势。 微服务哪些框架 Dubbo,是阿里巴巴服务化治理的核心框架,并被广泛应用于阿里巴巴集团的各成员站点。阿里巴巴近几年对开源社区的贡献不论在国内还是国外都是引人注目的,比如:JStorm 捐赠给 Apache 并加入 Apache 基金会等,为中国互联网人争足了面子,使得阿里巴巴在国人眼里已经从电商升级为一家科技公司了。Spring Cloud,从命名我们就可以知道,它是 Spring Source 的产物,Spring 社区的强大背书可以说是 Java 企业界最有影响力的组织了,除了 Spring Source 之外,还有Pivotal 和 Netfix 是其强大的后盾与技术输出。其中 Netflix 开源的整套微服务架构套件是Spring Cloud 的核心。 说说 RPC 的实现原理 首先需要有处理网络连接通讯的模块,负责连接建立、管理和消息的传输。其次需要有编解码的模块,因为网络通讯都是传输的字节码,需要将我们使用的对象序列化和反序列化。剩下的就是客户端和服务器端的部分,服务器端暴露要开放的服务接口,客户调用服务接口的一个代理实现,这个代理实现负责收集数据、编码并传输给服务器然后等待结果返回。 说说 Dubbo 的实现原理 dubbo 作为 rpc 框架,实现的效果就是调用远程的方法就像在本地调用一样。如何做到呢?就是本地有对远程方法的描述,包括方法名、参数、返回值,在 dubbo 中是远程和本地使用同样的接口;然后呢,要有对网络通信的封装,要对调用方来说通信细节是完全不可见的,网络通信要做的就是将调用方法的属性通过一定的协议(简单来说就是消息格式)传递到服务端;服务端按照协议解析出调用的信息;执行相应的方法;在将方法的返回值通过协议传递给客户端;客户端再解析;在调用方式上又可以分为同步调用和异步调用;简单来说基本就这个过程 Spring Boot 有哪些优点? Spring Boot 的优点有:减少开发,测试时间和努力。使用 JavaConfig 有助于避免使用 XML。避免大量的 Maven 导入和各种版本冲突。提供意见发展方法。通过提供默认值快速开始开发。没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish 或其他任何东西。需要更少的配置 因为没有 web.xml 文件。只需添加用@ Configuration 注释的类,然后添加用@Bean 注释的方法,Spring 将自动加载对象并像以前一样对其进行管理。您甚至可以将@Autowired 添加到 bean 方法中,以使 Spring 自动装入需要的依赖关系中。基于环境的配置 使用这些属性,您可以将您正在使用的环境传递到应用程序:-Dspring.profiles.active = {enviornment}。在加载主应用程序属性文件后,Spring 将在(application{environment} .properties)中加载后续的应用程序属性文件。如何实现 Spring Boot 应用程序的安全性?为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展 WebSecurityConfigurerAdapter 并覆盖其方法。 如何集成 Spring Boot 和 ActiveMQ? 对于集成 Spring Boot 和 ActiveMQ,我们使用spring-boot-starter-activemq依赖关系。 它只需要很少的配置,并且不需要样板代码。 什么是 Swagger?你用 Spring Boot 实现了它吗? Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger 消除了调用服务时的猜测。 使用 Spring Cloud 有什么优势? 使用 Spring Boot 开发分布式微服务时,我们面临以下问题与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。冗余-分布式系统中的冗余问题。负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。性能-问题 由于各种运营开销导致的性能问题。部署复杂性-Devops 技能的要求。 负载平衡的意义什么? 在计算中,负载平衡可以改善跨计算机,计算机集群,网络链接,中央处理单元或磁盘驱动器等多种计算资源的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性。负载平衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务器进程。 什么是 Netflix Feign?它的优点是什么? Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序。Feign 的第一个目标是将约束分母的复杂性统一到 http apis,而不考虑其稳定性。在 employee-consumer 的例子中,我们使用了 employee-producer 使用 REST 模板公开的 REST 服务。但是我们必须编写大量代码才能执行以下步骤使用功能区进行负载平衡。获取服务实例,然后获取基本 URL。利用 REST 模板来使用服务。 前面的代码如下 @Controller public class ConsumerControllerClient { @Autowired private LoadBalancerClient loadBalancer; public void getEmployee() throws RestClientException, IOException { ServiceInstance serviceInstance=loadBalancer.choose("employee-producer"); System.out.println(serviceInstance.getUri()); String baseUrl=serviceInstance.getUri().toString(); baseUrl=baseUrl+"/employee"; RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> response=null; try{ response=restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(),String.class); }catch (Exception ex) { System.out.println(ex); } System.out.println(response.getBody()); } 之前的代码,有像 NullPointer 这样的例外的机会,并不是最优的。我们将看到如何使用 Netflix Feign使呼叫变得更加轻松和清洁。如果 Netflix Ribbon 依赖关系也在类路径中,那么 Feign 默认也会负责负载平衡。 喜欢文章记得点个赞,感谢支持!

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

八问:通过微信小程序,黑客有可能盗走你的红包吗? | 宅客频道

微信小程序和街边大保健有不少共同点。 用完就走, 需要再来。 新面孔常有, 来去无痕迹。 总之,服务还是那些服务,只不过更加轻量,自由。这些细微而坚定的进步,也许能让你更加欲罢不能。 今天早晨,微信小程序正式刷爆了朋友圈。作为中国吃瓜群众的老朋友,雷锋网(公众号:雷锋网)宅客频道本着看热闹不怕事大的原则,决定探寻一个重要的问题: 黑客有没有可能通过微信小程序的漏洞,偷偷地用你的微信给他发一个大红包? 为了搞清这个问题,雷锋网宅客频道咨询了几位黑客大牛,整理回答如下: 1、从 App 到小程序,有一些漏洞会一直存在吧? 小程序改变了业务前端实现的形式,但是基本的业务没有变化。所以对于小程序服务商而言,有两方面风险依然存在: Web接口的漏洞。例如 xss、csrf、各类越权等等。这类是服务构架本身的漏洞。 业务功能的逻辑漏洞。例如:订单额任意修

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Rocky Linux

Rocky Linux

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

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

用户登录
用户注册