首页 文章 精选 留言 我的

精选列表

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

HBase优化之路-合理的使用编码压缩

为什么要讨论HBase编码压缩 编码+压缩能够成倍的减少数据的磁盘占用空间,节省可观的存储费用 编码+压缩通常情况下可以提高系统吞吐率,让系统可以做更多的功 默认建表不启用编码或者压缩,对初学者不友好 了解HBase编码 举个栗子,我们有一张物流表叫"express",记录物流订单的流转详情。如下面表格:rowkey包含两个部分,用#号分割,左边是物流订单号,右边是物流信息的更新时间点。表包含两个列,一个物流状态,一个是物流描述信息 rowkey 状态列 描述信息列 10324224#2019-04-21 10:51 已发货 包裹正在等待揽收 10324224#2019-04-21 19:46 已揽件 [嘉兴市]平湖南桥的xxx已揽收 10324224#2019-04-21 19:46 运输中 [嘉兴市]快件已从平湖南桥出发,准备发往嘉兴中转部 10324224#2

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

内存优化篇-String/char[]/byte[]的选择

Java基本数据类型的大小 type size(bits) bytes boolean 8 1 byte 8 1 char 16 2 short 16 2 int 32 4 long 64 8 float 32 4 double 64 8 Java引用的大小 在 32 位的 JVM 上,一个对象引用占用 4 个字节;在 64 位JVM上,占用 8 个字节。 使用 8 个字节是为了能够管理大于 4G 的内存,如果你的程序不需要访问大于 4G 的内存, 可通过-XX:+UseCompressedOops选项,开启指针压缩。从Java 1.6.0_23开始,这个选项默认是开的。 Java对象头的大小 在32位JVM中,对象头的大小为8个字节(4字节的Mark Word+4字节的Klass Pointer). 在64位JVM上,占用16个字节(8字节的Mark Word+8字节的Klass Pointer),因为开启UseCompressedOops,所以实际占用12个字节(8字节的Mark Word+4字节的Klass Pointer) 。参考klass pointer 接下来的内容都基于64位的JVM来展开 Java对象的大小 1、任意Java对象都包含至少12个字节的Object Header。 2、JVM分配内存以8字节为基本单位,如果不满小于8字节,则向8字节的倍数补齐。参考8 byte alignment 思考 Object object = new Object(); 占用多少内存? 数组的大小如何计算? 验证 添加Maven依赖 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency> import org.openjdk.jol.info.ClassLayout; /** * Created by jianpingpan on 2019/1/17. */ public class BasicClass { public static void main(String[] args) throws Exception { System.out.println(ClassLayout.parseClass(Object.class).toPrintable()); System.out.println(ClassLayout.parseClass(String.class).toPrintable()); System.out.println(ClassLayout.parseClass(byte[].class).toPrintable()); System.out.println(ClassLayout.parseClass(char[].class).toPrintable()); } } byte[] 和char[]的 object header为16个字节是因为有4个字节的数组长度。 String / char[] /byte[] 内存大小计算 String a = new String("abc"); String b = new String("abcd"); String c = new String("abc"); 第一行占用JVM内存的大小: 对象大小 = 12字节(object header)+ 4字节 (hash)+ 4字节(数组引用vlaue[]) + 4字节 (padding) 16字节+3*2字节+2字节padding (数组value[]) = 48字节 假设要缓存的字符个数为N。 String的内存大小计算公式 = 40+N*2 +padding char数组的内存大小计算公式=16+N*2+padding 如果用byte数组来存储字符串数据,占用的内存大小X需要分2种情况讨论: 1、如果需要存储的字符全在ASCII码中,一个字符用一个byte就可以存储 (编码方式可选ISO-8859-1/GBK/UTF-8): X = 16+N+padding 2、如果需要存储的字符范围不能被ASCII码覆盖,则需要根据字符范围确定合适的存储方式。 如需要要存储字符集为ASCII+中文字符,则可使用GBK编码: 16+N+padding<X < 16+N*2+padding 如果字符集不能被ASCII码覆盖,并且包含非中文字符,则使用UTF-8编码: 16+N+padding<X<16+6*N+padding 结论: 由此可见,char数组占用的内存大小小于String占用的内存大小。 若存储的字符范围以ASCII码为主,使用byte数组存储优于char数组。 实际使用场景 那么在缓存中可以直接用char[]或byte[]替换String么? 把 Set<String> set = new HashSet<>(); 替换成 Set<byte[]> set = new HashSet<>(); 会怎样呢? 很明显,contains方法、get方法都会失效。因为每个byte[]的hashCode不一样。 我们用下面的这个ByteArray/CharArray封装byte[],再用ByteArray替换String。 /** * Created by jianpingpan on 2019/1/23. */ public class ByteArray { byte[] bytes; public ByteArray(byte[] bytes){ this.bytes = bytes; } @Override public int hashCode() { if(null == bytes){ return 0; } return new String(bytes).hashCode(); } @Override public boolean equals(Object obj) { if(obj == null){ return false; } return hashCode()==obj.hashCode(); } } (CharArray的实现方式同ByteArray,只是把byte[] bytes 替换成 char[] chars即可) ByteArray占用的内存大小= 12字节(object header+ 4字节(数组引用bytes[]) + 16字节+N字节+padding (数组bytes[]) = 32字节+N字节+padding CharArray占用的内存大小= 12字节(object header+ 4字节(数组引用bytes[]) + 16字节+2*N字节+padding (数组bytes[]) = 32字节+2*N字节+padding 其中,N为数组中元素的个数。 例子 以存储100万条长度为32位的MD5字符串为例且内容互不相同字符串为例(假设字符串中的字符均为字母、数字、下划线)。 可以用classmexer来计算内存使用量 。 import com.javamex.classmexer.MemoryUtil; /** * Created by jianpingpan on 2019/1/25. */ public class StringTest { public static void main(String[] args){ String s="cfcd208495d565ef66e7dff9f98764da"; ByteArray b = new ByteArray(s.getBytes()); CharArray c = new CharArray(s.toCharArray()); long stringBytes = MemoryUtil.deepMemoryUsageOf(s); long byteArrayBytes = MemoryUtil.deepMemoryUsageOf(b); long charArrayBytes = MemoryUtil.deepMemoryUsageOf(c); System.out.println("stringBytes:"+stringBytes); System.out.println("byteArrayBytes:"+byteArrayBytes); System.out.println("charArrayBytes:"+charArrayBytes); } } 用String存储,每条记录占用的空间为 40+32*2 = 104字节 用ByteArray存储,每条记录占用的空间为 32+32 = 64字节 用CharArray存储,每条记录占用的空间为 32+32*2 = 96字节 参考文档: http://btoddb-java-sizing.blogspot.com/2012/01/object-sizes.html https://stackoverflow.com/questions/26357186/what-is-in-java-object-header/26416983 http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

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

单元测试优化的实践(Generative Testing)

首先为什么要写单元测试? 因为对于任何一个软件来说,“满足需求”是他存在的必要条件,也是软件的价值体现。单元测试一定是为它服务的。所以很容易知道写单元测试的两个动机:驱动(如:TDD)和验证功能实现。另外,软件需求“易变”的特征决定了修改代码成为必然,在这种情况下,单元测试能保护已有的功能不被破坏。 基于以上两点共识,我们看看传统的单元测试有什么特征? 基于用例的测试(By Example) 单元测试最常见的套路就是Given、When、Then三部曲。 Given:初始状态或前置条件 When:行为发生 Then:断言结果 编写时,我们会精心准备(Given)一组输入数据,然后在调用行为后,断言返回的结果与预期相符。这种基于用例的测试方式在开发(包括TDD)过程中十分好用。因为它清晰地定义了输入输出,而且大部分情况下体量都很小、容易理解。 但这样的测试方式也有坏处。 第一点在于测试的意图。用例太过具体,我们就很容易忽略自己的测试意图。比如我曾经看过有人在写计算器kata程序的时候,将其中的一个测试命名为“return 3 when add 1 and 2”,这样的命名其实掩盖了测试用例背后的真实意图——传入两个整型参数,调用add方法之后得到的结果应该是两者之和。我们常说测试即文档,既然是文档就应该明确描述待测方法的行为,而不是陈述一个例子。 第二点在于测试完备性。因为省事省心并且回报率高,我们更乐于写happy path的代码。尽管出于职业道德,我们也会找一个明显的异常路径进行测试,不过这还远远不够。 为了辅助单元测试改善这两点。我这里介绍另一种测试方式——生成式测试(Generative Testing,也称Property-Based Testing)。这种测试方式会基于输入假设输出,并且生成许多可能的数据来验证假设的正确性。 生成式测试 对于第一个问题,我们换种思路思考一下。假设我们不写具体的测试用例,而是直接描述意图,那么问题也就迎刃而解了。想法很美好,但如何实践Given、When、Then呢?答案是让程序自动生成入参并验证结果。这也就引出“生成式测试”的概念——我们先声明传入数据可能的情况,然后使用生成器生成符合入参情况的数据,调用待测方法,最后进行验证。 Given阶段 Clojure 1.9(Alpha)新内置的Clojure.spec可以很轻松地做到这点: ;; 定义输入参数的可能情况:两个整型参数 (s/def ::add-operators (s/cat :a int? :b int?)) ;; 尝试生成数据 (gen/generate (s/gen ::add-operators)) ;; 生成的数据 -> (1 -122) 首先,我们尝试声明两个参数可能出现的情况或者称为规格(specification),即参数a和b都是整数。然后调用生成器产生一对整数。整个分析和构造的过程中,都没有涉及具体的数据,这样会强制我们揣摩输入数据可能的模样,而且也能避免测试意图被掩盖掉——正如前面所说,return 3 when add 1 and 2并不代表什么,return the sum of two integers才具有普遍意义。 Then阶段 数据是生成了,待测方法也可以调用,但是Then这个断言阶段又让人头疼了,因为我们根本没法预知生成的数据,也就无法知道正确的结果,怎么断言? 拿定义好的加法运算为例: (defn add [a b] (+ a b)) 我们尝试把断言改成一个全称命题: 任取两个整数a、b,a和b加起来的结果总是a、b之和。 借助test.check,我们在Clojure可以这样表达: (def test-add (prop/for-all [a (gen/int) b (gen/int)] (= (add a b) (+ a b)))) 不过,我们把add方法的实现(+ a b)写到了断言里,这几乎丧失了单元测试的基本意义。换一种断言方式,我们使用加法的逆运算进行描述: 任取两个整数,把a和b加起来的结果减去a总会得到b。 (def test-add (prop/for-all [a (gen/int) b (gen/int)] (= (- (add a b) a) b)))) 我们通过程序陈述了一个已知的真命题。变换以后,就可以使用quick-check对多组生成的整数进行测试。 ;; 随机生成100组数据测试add方法 (tc/quick-check 100 test-add) ;; 测试结果 -> {:result true, :num-tests 100, :seed 1477285296502} 测试结果表明,刚才运行了100组测试,并且都通过了。理论上,程序可以生成无数的测试数据来验证add方法的正确性。即便不能穷尽,我们也获得一组统计上的数字,而不仅仅是几个纯手工挑选的用例。 至于第二个问题,首先得明确测试是无法做到完备的。很多指导方法保证使用较少的用例做到有效覆盖,比如:等价类、边界值、判定表、因果图、pairwise等等。但是在实际使用过程当中,依然存在问题。举个例子,假如我们有一个接收自然数并直接返回这个参数的方法identity-nat,那么对于输入参数而言,全体自然数都互为等价类,其中的一个有效等价类可以是自然数1;假定入参被限定在整数范围,我们很容易找到一个无效等价类,比如-1。 用Clojure测试代码表现出来: (deftest test-with-identity-nat (testing "identity of natural integers" (is (= 1 (identity-nat 1)))) (testing "throw exception for non-natural integers" (is (thrown? RuntimeException (identity-nat -1))))) 不过如果有人修改了方法identity-nat的实现,单独处理入参为0的情况,这个测试还是能够照常通过。也就是说,实现发生改变,基于等价类的测试有可能起不到防护作用。当然你完全可以反驳:规则改变导致等价类也需要重新定义。道理确实如此,但是反过来想想,我们写测试的目的不正是构建一张安全网吗?我们信任测试能在代码变动时给予警告,但此处它失信了,这就尴尬了。 如果使用生成式测试,我们规定: 任取一个自然数a,在其上调用identity-nat的结果总是返回a。 (def test-identity-nat (prop/for-all [a (s/gen nat-int?)] (= a (identity-nat a)))) (tc/quick-check 100 test-identity-nat) -> {:result false, :seed 1477362396044, :failing-size 0, :num-tests 1, :fail [0], :shrunk {:total-nodes-visited 0, :depth 0, :result false, :smallest [0]}} 这个测试尝试对100组生成的自然数(nat-int?)进行测试,但首次运行就发现代码发生过变动。失败的数据是0,而且还给出了最小失败集[0]。拿着这个最小失败集,我们就可以快速地重现失败用例,从而修正。 当然也存在这样的可能:在一次运行中,我们的测试无法发现失败的用例。但是,如果100个测试用例都通过了,至少表明我们程序对于100个随机的自然数都是正确的,和基于用例的测试相比,这就如同编织出一道更加紧密的安全网——网孔越小,漏掉的情况也越少。 Clojure语言之父Rich Hickey推崇Simple Made Easy哲学,受其影响生成式测试在Clojure.spec中有更为简约的表达。以上述为例: (s/fdef identity-nat :args (s/cat :a nat-int?) ; 输入参数的规格 :ret nat-int? ; 返回结果的规格 :fn #(= (:ret %) (-> % :args :a))) ; 入参和出参之间的约束 (stest/check `identity-nat) fdef宏定义了方法identity-nat的规格,默认情况下会基于参数的规格生成1000组数据进行生成式测试。除了这一好处,它还提供部分类型检查的功能。 再谈TDD 如果对软件测试、接口测试、自动化测试、性能测试、LR脚本开发、面试经验交流。感兴趣可以175317069,群内会有不定期的发放免费的资料链接,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。 TDD(测试驱动开发)是一种驱动代码实现和设计的过程。我们说要先有测试,再去实现;保证实现功能的前提下,重构代码以达到较好的设计。整个过程就好比演绎推理,测试就是其中的证明步骤,而最终实现的功能则是证明的结果。 对于开发人员而言,基于用例的测试方式是友好的,因为它能简单直接地表达实现的功能并保证其正确性。一旦进入红、绿、重构的节(guai)奏(quan),开发人员根本停不下来,仿佛遁入一种心流状态。只不过问题是,基于用例驱动出来的实现可能并不是恰好通过的。我们常常会发现,在写完上组测试用例的实现之后,无需任何改动,下组测试照常能运行通过。换句话说,实现代码可能做了多余的事情而我们却浑然不知。在这种情况下,我们可以利用生成式测试准备大量符合规格的数据探测程序,以此检查程序的健壮性,让缺陷无处遁形。 凡是想到的情况都能测试,但是想不到情况也需要测试,这才是生成式测试的价值所在。有人把TDD概念化为“展示你的功能”(Show your work),而把生成式测试归纳为“检查你的功能“(Check your work),我深以为然。 小结 回到我们写单元测试的动机上: 1、驱动和验证功能实现; 2、保护已有的功能不被破坏。 基于用例的单元测试和生成式测试在这两点上是相辅相成的。我们可以借助它们尽可能早地发现更多的缺陷,避免它们逃逸到生产环境。 Clojure.spec是Clojure内置的一个新特性,它允许开发人员将数据结构用类型和其他验证条件(例如允许的取值范围)进行封装。这种数据结构一旦建立,Clojure就能利用这种规格来为程序员提供大量的便利:自动生成的测试代码、合法性验证、析构数据结构等等。Clojure.spec提供方法很有前景,它可以让开发者在需要的时候,就能从类型和取值范围中获益。 另外,除了Clojure,其它语言也有相应的生成式测试的框架,你不妨在自己的项目中试一试。

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

RecyclerView实现探探卡片滑动功能及优化

title: RecyclerView实现探探卡片滑动功能 date: 2018-10-07 10:35:56 tags: RecyclerView 代码实现 博客地址:https://blog.csdn.net/qq_39085422/article/details/78612132 我只掌握了RecyclerView基础用法,所以参考别人博客的代码实现。 我使用CardView代替RoundImageView实现圆角效果。 遇到的问题 1.CardView阴影显示不完全 Card_View_border.jpg 原因:将CardView作为最外层布局,可能是两个CardView重叠时,边界会融合到一起。 解决方法:在外层再套一层布局,比如LinearLayout之类的。 解决后效果图: Card_View.jpg 在文末会贴出我的布局代码。 2.左右滑,滑出时卡顿(未解决) 原因:adapter.notifyDataSetChanged(); 卡片滑出后需要删除对应的数据,对数据源进行remove()操作,之后通知adapter,这个时候会发生卡顿 3.向上下滑动时,动画衔接的不好 原因:临界值计算公式不合理,只计算了在X轴的偏移量 解决:CardItemTouchHelperCallback类中的onChildDraw()里, 解决前代码: float ratio = dX / getThreshold(recyclerView, viewHolder); 解决后代码: float distance = (float)Math.sqrt(dX*dX+dY*dY); float ratio = distance / getThreshold(recyclerView, viewHolder); 变化:增加了distance变量,计算位移两点间的直线距离。 4.用Gilde加载网络图片时,会闪烁,使用本地图片时不会 图片闪烁.jpg 原因:Gilde进行加载网络图片时,会保留缓存,使用时加载完整图片,所以分辨率不同,会闪烁。 解决: 思路是类似于做一层缓存,先把图片加载好放在一个List里面,使用时直接从数组里取出来,这样子就模拟了本地图片加载。 具体实现如下: 在adaputer中声明两个数组 //图片的URL private List<String> imgUrlList = new ArrayList<>(); //缓存数组 private List<GlideDrawable> glideDrawableList = new ArrayList<>(); //glideDrawableList的访问方法,imgUrlList通过构造函数传入,不需要get,set public List<GlideDrawable> getGlideDrawableList() { return glideDrawableList; } public void setGlideDrawableList(List<GlideDrawable> glideDrawableList) { this.glideDrawableList = glideDrawableList; } 之后创建一个updateGlideDrawableList()方法,用于请求网络图片,CardConfig.DEFAULT_SHOW_ITEM是显示的卡片数量,CardConfig.DEFAULT_CACHE_ITEM是而外的缓存数量,为了防止用户滑动太快,网络请求速度跟不上。 public void updateGlideDrawableList(){ //每次最多循环的次数i<卡片显示数量+额外缓存数量 for (int i = mCardShowInfoBeanList.size(); i < CardConfig.DEFAULT_SHOW_ITEM+CardConfig.DEFAULT_CACHE_ITEM; i++){ //防止list为空 if(imgUrlList.size() <= 0){ return; } //依次去除所有的url String url = imgUrlList.remove(0); //使用Gilde请求网络图片 Glide.with(mContext) .load(url) //Gilde将图片剪裁成336,326 .into(new SimpleTarget<GlideDrawable>(336, 326) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { //将图片资源放入缓存数组 glideDrawableList.add(resource); //通知adapter数据已经更新,刷新显示。 AdapterCardSwipeLive.this.notifyDataSetChanged(); } }); } } 之后只要在imgUrlList传入之后,onBindViewHolder()之前使用该方法就好了,这里我选择在构造函数里调用: public Adapter(Context context, List imgUrlList){ this.imgUrlList = imgUrlList; this.mContext = context; //我选择今早的调用此方法,减少用户的等待时间 updateGlideDrawableList(); } 到这里,核心代码就已经写完了,接下来只需要将之前使ItemTouchHelperCallback中操作imgUrlList的地方进行一些修改,替换成glideDrawableList就可以了: //替换前 T remove = adaputer.getList().remove(layoutPosition); adapter.notifyDataSetChanged(); //替换后 T remove = ((List<T>) adapterCardSwipeLive.getmCardShowInfoBeanList()).remove(layoutPosition); //调用这个方法以补充被remove掉的GlideDrawable updateGlideDrawableList(); adapter.notifyDataSetChanged(); 这样子就完成了,使用时,传进adaputer来的只有imgUrlList,然后adapter向外只提供一个glideDrawableList,外面也只能对glideDrawableList进行增删改查。同时每删除glideDrawableList中的一个元素,再通过updateGlideDrawableList()添加回来,就能实现尽可能少的缓存。这里只是提供一个思路,代码不一定能毫无错误的运行出来,需要自己进行适配。 拓展 如果你搜索卡片层叠效果,你会发现更多更详细的内容 总结: 折腾了好久也无法像探探那样流畅,只能朝四个方向滑出,而不能360°滑出, 流畅度效果也有不少差距。 应该是实现思路的不同,我是基于recyclerView实现的,比较简单,另一种基于ListView实现的比较复杂,但效果很好。可见自己水平还有待提高。 相关代码: item_card_slide.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="336dp" android:layout_height="426dp"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="match_parent" app:cardCornerRadius="7.5dp" app:cardElevation="1dp" app:cardUseCompatPadding="true"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <ImageView android:id="@+id/iv_avatar" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:background="@color/pink"/> <ImageView android:id="@+id/iv_dislike" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="15dp" android:layout_marginTop="15dp" android:alpha="0" android:background="@color/blue"/> <ImageView android:id="@+id/iv_like" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_marginRight="15dp" android:layout_marginTop="15dp" android:alpha="0" android:background="@color/grey"/> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="100dp" android:paddingLeft="14dp" android:paddingTop="15dp"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="小姐姐" android:textColor="@android:color/black" android:textSize="16sp" /> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_name" android:layout_marginTop="5dp" android:background="@color/pink" android:gravity="center" android:text=" 23" android:textColor="#FFFFFF" android:textSize="14sp" /> <TextView android:id="@+id/tv_constellation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_name" android:layout_marginLeft="4dp" android:layout_marginTop="5dp" android:layout_toRightOf="@id/tv_age" android:background="@color/olivedrab" android:gravity="center" android:text="狮子座" android:textColor="#FFFFFF" android:textSize="14sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_age" android:layout_marginTop="5dp" android:gravity="center" android:text="IT/互联网" android:textColor="#cbcbcb" /> </RelativeLayout> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>

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

Java编程——jvm优化之 图解垃圾回收

多世纪,目前已经十分成熟了。因此本篇主要从这两个方面来了解: 1. 哪些对象需要被回收? 2. 如何回收? 一、谁要被回收 java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同是数据区域,这些区域有各自各自的用途。主要包含以下几个部分组成: 1、程序计数器 程序计数器占用的内存空间我们可以忽略不计,它是每个线程所执行的字节码的行号指示器。 2、虚拟机栈 java的虚拟机栈是线程私有的,生命周期和线程相同。它描述的是方法执行的内存模型。同时用于存储局部变量、操作数栈、动态链接、方法出口等。 3、本地方法栈 本地方法栈,类似虚拟机栈,它调用的是是native方法。 4、堆 堆是jvm中管理内存中最大一块。它是被共享,存放对象实例。也被称为“gc堆”。垃圾回收的主要管理区域 5、方法区 方法区也是共享的内存区域。它主要存储已被虚拟机加载的类信息、常量、静态变量、即时编译器(jit)编译后的代码数据。 以上就是jvm在运行时期主要的内存组成,我们看到常见的内存使用不但存在于堆中,还会存在于其他区域,虽然堆的管理对程序的管理至关重要,但我们不能只局限于这一个区域,特别是当出现内存泄露的时候,我们除了要排查堆内存的情况,还得考虑虚拟机栈的以及方法区域的情况。 知道了要对谁以及那些区域进行内存管理,我还需要知道什么时候对这些区域进行垃圾回收。 二、如何回收 上述的两点讲解之后,我们大概明白了,哪些对象会被回收,以及回收的依据是什么,但回收的这个工作实现起来并不简单,首先它需要扫描所有的对象,鉴别谁能够被回收,其次在扫描期间需要 ”stop the world“ 对象能被冻结,不然你刚扫描,他的引用信息有变化,你就等于白做了。 分代回收 我们从一个object1来说明其在分代垃圾回收算法中的回收轨迹。 1、object1新建,出生于新生代的Eden区域。 2、minor GC,object1 还存活,移动到Fromsuvivor空间,此时还在新生代。 3、minor GC,object1 仍然存活,此时会通过复制算法,将object1移动到ToSuv区域,此时object1的年龄age+1。 4、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象并没有达到survivor的一半,所以此时通过复制算法,将fromSuv和Tosuv 区域进行互换,存活的对象被移动到了Tosuv。 5、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象已经达到survivor的一半以上(toSuv的区域已经满了),object1被移动到了老年代区域。 6、object1存活一段时间后,发现此时object1不可达GcRoots,而且此时老年代空间比率已经超过了阈值,触发了majorGC(也可以认为是fullGC,但具体需要垃圾收集器来联系),此时object1被回收了。fullGC会触发 stop the world。 在以上的新生代中,我们有提到对象的age,对象存活于survivor状态下,不会立即晋升为老生代对象,以避免给老生代造成过大的影响,它们必须要满足以下条件才可以晋升: 1、minor gc 之后,存活于survivor 区域的对象的age会+1,当超过(默认)15的时候,转移到老年代。 2、动态对象,如果survivor空间中相同年龄所有的对象大小的综合和大于survivor空间的一半,年级大于或等于该年级的对象就可以直接进入老年代。 以上采用分代垃圾收集的思想,对一个对象从存活到死亡所经历的历程。期间,在新生代的时刻,会用到复制算法,在老年代时,有可能会用到标记-清楚算法(mark-sweep)算法或者标记-整理算法,这些都是垃圾回收算法基于不同区域的实现,我们看下这几种回收算法的实现原理。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:468947140 点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=5zMN6JB 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 欢迎关注,欢迎留言探讨。

资源下载

更多资源
Mario

Mario

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

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

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等操作系统。

用户登录
用户注册