0%

## 聊聊 Java 的类加载机制一

Posted on 2020-11-08 In Java , 类加载 Views: Disqus:

一说到这个主题，想到的应该是双亲委派模型，不过讲的包括但不限于这个，主要内容是参考深入理解 Java 虚拟机书中的介绍，
一个类型的生命周期包含了七个阶段，加载，验证，准备，解析，初始化，使用，卸载。

- ## 加载

1. 通过一个类的全限定名来获取定义此类的二进制字节流
2. 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
3. 在内存中生成了一个代表这个类的 java.lang.Class 对象，作为方法区这个类的各种数据的访问入口

- ## 验证

1. 文件格式验证
2. 元数据验证
3. 字节码验证
4. 符号引用验证

- ## 准备

  准备阶段是正式为类中定义的变量（即静态变量，被static修饰的变量）分配内存并设置类变量初始值的阶段

- ## 解析

  解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程

以上验证、准备、解析 三个阶段又合称为链接阶段，链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。

- ## 初始化

  类的初始化阶段是类加载过程的最后一个步骤，也是除了自定义类加载器之外将主动权交给了应用程序，其实就是执行类构造器()方法的过程，()并不是我们在 Java 代码中直接编写的方法，它是 Javac编译器的自动生成物，()方法是由编译器自动收集类中的所有类变量的复制动作和静态句块(static{}块)中的语句合并产生的，编译器收集的顺序是由语句在原文件中出现的顺序决定的，静态语句块中只能访问定义在静态语句块之前的变量，定义在它之后的变量，在前面的静态语句块可以复制，但是不能访问，同时还要保证父类的执行先于子类，然后保证多线程下的并发问题

最终，方法区会存储当前类类信息，包括类的静态变量、类初始化代码（定义静态变量时的赋值语句 和 静态初始化代码块）、实例变量定义、实例初始化代码（定义实例变量时的赋值语句实例代码块和构造方法）和实例方法，还有父类的类信息引用。

## Apollo 的 value 注解是怎么自动更新的

Posted on 2020-11-01 In Java , Apollo , value Views: Disqus:

在前司和目前公司，用的配置中心都是使用的 Apollo，经过了业界验证，比较强大的配置管理系统，特别是在0.10 后开始支持对使用 value 注解的配置值进行自动更新，今天刚好有个同学问到我，就顺便写篇文章记录下，其实也是借助于 spring 强大的 bean 生命周期管理，可以实现BeanPostProcessor接口，使用postProcessBeforeInitialization方法，来对bean 内部的属性和方法进行判断，是否有 value 注解，如果有就是将它注册到一个 map 中，可以看到这个方法`com.ctrip.framework.apollo.spring.annotation.SpringValueProcessor#processField`

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
      protected void processField(Object bean, String beanName, Field field) {
        // register @Value on field
        Value value = field.getAnnotation(Value.class);
        if (value == null) {
          return;
        }
        Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());

        if (keys.isEmpty()) {
          return;
        }

        for (String key : keys) {
          SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
          springValueRegistry.register(beanFactory, key, springValue);
          logger.debug("Monitoring {}", springValue);
        }
      }

然后我们看下这个`springValueRegistry`是啥玩意

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class SpringValueRegistry {
      private static final long CLEAN_INTERVAL_IN_SECONDS = 5;
      private final Map<BeanFactory, Multimap<String, SpringValue>> registry = Maps.newConcurrentMap();
      private final AtomicBoolean initialized = new AtomicBoolean(false);
      private final Object LOCK = new Object();

      public void register(BeanFactory beanFactory, String key, SpringValue springValue) {
        if (!registry.containsKey(beanFactory)) {
          synchronized (LOCK) {
            if (!registry.containsKey(beanFactory)) {
              registry.put(beanFactory, LinkedListMultimap.<String, SpringValue>create());
            }
          }
        }

        registry.get(beanFactory).put(key, springValue);

        // lazy initialize
        if (initialized.compareAndSet(false, true)) {
          initialize();
        }
      }

这类其实就是个 map 来存放 springvalue，然后有`com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener`来监听更新操作，当有变更时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Override
     public void onChange(ConfigChangeEvent changeEvent) {
       Set<String> keys = changeEvent.changedKeys();
       if (CollectionUtils.isEmpty(keys)) {
         return;
       }
       for (String key : keys) {
         // 1. check whether the changed key is relevant
         Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
         if (targetValues == null || targetValues.isEmpty()) {
           continue;
         }

         // 2. check whether the value is really changed or not (since spring property sources have hierarchies)
         // 这里其实有一点比较绕，是因为 Apollo 里的 namespace 划分，会出现 key 相同，但是 namespace 不同的情况，所以会有个优先级存在，所以需要去校验 environment 里面的是否已经更新，如果未更新则表示不需要更新
         if (!shouldTriggerAutoUpdate(changeEvent, key)) {
           continue;
         }

         // 3. update the value
         for (SpringValue val : targetValues) {
           updateSpringValue(val);
         }
       }
     }

其实原理很简单，就是得了解知道下

## Leetcode 104 二叉树的最大深度(Maximum Depth of Binary Tree) 题解分析

Posted on 2020-10-25 In Java , leetcode , Binary Tree , java , Binary Tree , DFS Views: Disqus:

## 题目介绍

给定一个二叉树，找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例：
给定二叉树 [3,9,20,null,null,15,7]，

    1
    2
    3
    4
    5
      3
     / \
    9  20
      /  \
     15   7

返回它的最大深度 3 。

## 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 主体是个递归的应用
    public int maxDepth(TreeNode root) {
        // 节点的退出条件之一
        if (root == null) {
            return 0;
        }
        int left = 0;
        int right = 0;
        // 存在左子树，就递归左子树
        if (root.left != null) {
            left = maxDepth(root.left);
        }
        // 存在右子树，就递归右子树
        if (root.right != null) {
            right = maxDepth(root.right);
        }
        // 前面返回后，左右取大者
        return Math.max(left + 1, right + 1);
    }

## 分析

其实对于树这类题，一般是以递归形式比较方便，只是要注意退出条件

## 在老丈人家的小工记五

Posted on 2020-10-18 In 生活 , 运动 , 跑步 , 干活 Views: Disqus:

终于回忆起来了，年纪大了写这种东西真的要立马写，不然很容易想不起来，那天应该是 9 月 12 日，也就是上周六，因为我爸也去了，而且娘亲（丈母娘，LD 这么叫，我也就随了她这么叫，当然是背后，当面就叫妈）也在那，早上一到那二爹就给我爸指挥了活，要挖一条院子的出水道，自己想出来的词，因为觉得下水道是竖的，在那稍稍帮了一会会忙，然后我还是比较惯例的跟着 LD 还有娘亲去住的家里，主要是老丈人可能也不太想让我干太累的活，因为上次已经差不多把三楼都整理干净了，然后就是二楼了，二楼说实话我也帮不上什么忙，主要是衣服被子什么的，正好是有张以前小孩子睡过的那种摇篮床，看上去虽然有一些破损，整体还是不错的，所以打算拿过去，我就负责把它拆掉了，比较简单的是只要拧螺丝就行了，但是其实是用了好多好多工具才搞定的，一开始只要螺丝刀就行了，但是因为年代久了，后面的螺帽也有点锈住或者本身就会串着会一起动，所以拿来了个扳手，大部分的其实都被这两个工具给搞定了，但是后期大概还剩下四分之一的时候，有一颗完全锈住，并且螺纹跟之前那些都不一样，但是这个已经是最大的螺丝刀了，也没办法换个大的了，所以又去找来个一字的，因为十字的不是也可以用一字的拧嘛，结果可能是我买的工具箱里的一字螺丝刀太新了，口子那很锋利，直接把螺丝花纹给划掉了，大的小的都划掉，然后真的变成凹进去一个圆柱体了，然后就想能不能隔一层布去拧，然而因为的确是已经变成圆柱体了，布也不太给力，不放弃的我又去找来了个老虎钳，妄图把划掉的螺丝用老虎钳钳住，另一端用扳手拧开螺帽，但是这个螺丝跟螺帽真的是生锈的太严重了，外加上钳不太牢，完全是两边一起转，实在是没办法了，在征得同意之后，直接掰断了，火死了，一颗螺丝折腾得比我拆一张床还久，那天因为早上去的也比较晚了，然后就快吃午饭了，正好想着带一点东西过去，就把一些脸盆，泡脚桶啥的拿过去了，先是去吃了饭，还是在那家快餐店，菜的口味还是依然不错，就是人比较多，我爸旁边都是素菜，都没怎么吃远一点的荤菜，下次要早点去，把荤菜放我爸旁边😄（PS：他们家饭还是依然尴尬，需要等），吃完就开到在修的房子那把东西拿了出来，我爸已经动作很快的打了一小半的地沟了，说实话那玩意真的是很重，我之前把它从三楼拿下来，就觉得这个太重了，这次还要用起来，感觉我的手会分分钟废掉，不过一开始我还是跟着LD去了住的家里，惯例睡了午觉，那天睡得比较踏实，竟然睡了一个小时，醒了想了下，其实LD她们收拾也用不上我（没啥力气活），我还是去帮我爸他们，跟LD说了下就去了在修的老房子那，两位老爹在一起钻地，看着就很累，我连忙上去想换一会他们，因为刚好是钻到混凝土地线，特别难，力道不够就会滑开，用蛮力就是钻进去拔不出来，原理是因为本身浇的时候就是很紧实的，需要边钻边动，那家伙实在是太重了，真的是汗如雨下，基本是三个人轮流来，我是个添乱的，经常卡住，然后把地线，其实就是一条混凝土横梁，里面还有14跟18的钢筋，需要割断，这个割断也是很有技巧，钢筋本身在里面是受到挤压的，直接用切割的，到快断掉的时候就会崩一下，非常危险，还是老丈人比较有经验，要留一点点，然后直接用榔头敲断就好了，本来以为这个是最难的了，结果下面是一块非常大的青基石，而且也是石头跟石头挤一块，边上一点点打钻有点杯水车薪，后来是用那种螺旋的钻，钻四个洞，相对位置大概是个长方形，这样子把中间这个长方形钻出来就比较容易地能拿出来了，后面的也容易搞出来了，后面的其实难度不是特别大了，主要是地沟打好之后得看看高低是不是符合要求的，不能本来是往外排水的反而外面高，这个怎么看就又很有技巧了，一般在地上的只要侧着看一下就好了，考究点就用下水平尺，但是在地下的，不用水平尺，其实可以借助于地沟里正要放进去的水管，放点水进去，看水往哪流就行了，铺好水管后，就剩填埋的活了，不是太麻烦了，那天真的是累到了，打那个混凝土的时候我真的是把我整个人压上去了，不过也挺爽的，有点把平时无处发泄的蛮力发泄出去了。

## Leetcode 2 Add Two Numbers 题解分析

Posted on 2020-10-11 In Java , leetcode , java , linked list , linked list Views: Disqus:

又 roll 到了一个以前做过的题，不过现在用 Java 也来写一下，是 easy 级别的，所以就简单说下

## 简要介绍

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.
就是给了两个链表，用来表示两个非负的整数，在链表中倒序放着，每个节点包含一位的数字，把他们加起来以后也按照原来的链表结构输出

## 样例

#### example 1

    1
    2
    3
    Input: l1 = [2,4,3], l2 = [5,6,4]
    Output: [7,0,8]
    Explanation: 342 + 465 = 807.

#### example 2

    1
    2
    Input: l1 = [0], l2 = [0]
    Output: [0]

#### example 3

    1
    2
    Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
    Output: [8,9,9,9,0,0,0,1]

## 题解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            ListNode root = new ListNode();
            if (l1 == null && l2 == null) {
                return root;
            }
            ListNode tail = root;
            int entered = 0;
            // 这个条件加了 entered，就是还有进位的数
            while (l1 != null || l2 != null || entered != 0) {
                int temp = entered;
                if (l1 != null) {
                    temp += l1.val;
                    l1 = l1.next;
                }
                if (l2 != null) {
                    temp += l2.val;
                    l2 = l2.next;
                }
                entered = (temp - temp % 10) / 10;
                tail.val = temp % 10;
                // 循环内部的控制是为了排除最后的空节点
                if (l1 != null || l2 != null || entered != 0) {
                    tail.next = new ListNode();
                    tail = tail.next;
                }
            }
    //        tail = null;
            return root;
        }

这里唯二需要注意的就是两个点，一个是循环条件需要包含进位值还存在的情况，还有一个是最后一个节点，如果是空的了，就不要在 new 一个出来了，写的比较挫